Browse Source

- Work in Progress: Widget ID support added to RegionScripts, test zone is tutorial_island02 (queens colony, overlord outpost, isle of refuge)
- You can now add a new region identified by its grid and widget id (use /grid to identify when standing over it)
- New LUA Functions:
CreateWidgetRegion(Zone, Version, RegionName, EnvName, GridID, WidgetID, Dist)
* Dist is optional when set to 0.0f we rely only on the widget id, when dist is set we do a radius around the widgets locations
* RegionName and EnvName define the script file so if you are in QueensColony (tutorial_island02) and the RegionName is TestRegion, script is RegionScripts/tutorial_island02/TestRegion.lua
RemoveRegion(Zone, Version, RegionName)
By default Version is 0 on both, but you could specify a version like 546 if you had a DoF client mapped

Example campfire in the newbie area:
ZoneScripts/QueensColony.lua
function init_zone_script, add line:
CreateWidgetRegion(Zone, 0, "TestRegion", "", 924281492, 4117633379, 2.0)

Create new script, RegionScripts/tutorial_island02/TestRegion.lua with the following:
function TakeFireDamage(Spawn)
local invul = IsInvulnerable(Spawn)
if invul == true then
return 0
end

local hp = GetHP(Spawn)
local level = GetLevel(Spawn)
local damageToTake = level * 1
-- if we don't have enough HP make them die to pain and suffering not self
if hp <= damageToTake then
KillSpawn(Spawn, null, 1)
else
DamageSpawn(Spawn, Spawn, 192, 3, damageToTake, damageToTake, "Fire!", 0, 0, 1, 1)
end
end

function EnterRegion(Zone, Spawn, RegionType)
-- initial tick for hitting the fire
TakeFireDamage(Spawn)

-- 5 second Tick
return 5000
end

function Tick(Zone, Spawn, RegionType)

TakeFireDamage(Spawn)

-- returning 1 would stop the Tick process until Spawn leaves/re-enters region
return 0
end

Emagi 1 year ago
parent
commit
db123c93e9

+ 40 - 0
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -13071,5 +13071,45 @@ int EQ2Emu_lua_DamageEquippedItems(lua_State* state) {
 		lua_interface->SetBooleanValue(state, false);
 	}
 	
+	return 1;
+}
+
+int EQ2Emu_lua_CreateWidgetRegion(lua_State* state) {
+	ZoneServer* zone = lua_interface->GetZone(state);
+	int32 version = lua_interface->GetInt32Value(state, 2);
+	
+	RegionMap* region_map = world.GetRegionMap(std::string(zone->GetZoneFile()), version);
+	if(region_map == nullptr) {
+		lua_interface->LogError("%s: LUA CreateWidgetRegion command error: region map is not valid for version %u", lua_interface->GetScriptName(state), version);
+		return 0;
+	}
+	string region_name = lua_interface->GetStringValue(state, 3);
+	string env_name = lua_interface->GetStringValue(state, 4);
+	int32 grid_id = lua_interface->GetInt32Value(state, 5);
+	int32 widget_id = lua_interface->GetInt32Value(state, 6);
+	float dist = lua_interface->GetFloatValue(state, 7);
+	region_map->InsertRegionNode(zone, version, region_name, env_name, grid_id, widget_id, dist);
+	
+	lua_interface->ResetFunctionStack(state);
+
+	lua_interface->SetBooleanValue(state, true);
+	return 1;
+}
+
+int EQ2Emu_lua_RemoveRegion(lua_State* state) {
+	ZoneServer* zone = lua_interface->GetZone(state);
+	int32 version = lua_interface->GetInt32Value(state, 2);
+	
+	RegionMap* region_map = world.GetRegionMap(std::string(zone->GetZoneFile()), version);
+	if(region_map == nullptr) {
+		lua_interface->LogError("%s: LUA RemoveRegion command error: region map is not valid for version %u", lua_interface->GetScriptName(state), version);
+		return 0;
+	}
+	string region_name = lua_interface->GetStringValue(state, 3);
+	region_map->RemoveRegionNode(region_name);
+	
+	lua_interface->ResetFunctionStack(state);
+
+	lua_interface->SetBooleanValue(state, true);
 	return 1;
 }

+ 3 - 0
EQ2/source/WorldServer/LuaFunctions.h

@@ -623,4 +623,7 @@ int EQ2Emu_lua_SetLootDropType(lua_State* state);
 int EQ2Emu_lua_GetLootDropType(lua_State* state);
 
 int EQ2Emu_lua_DamageEquippedItems(lua_State* state);
+
+int EQ2Emu_lua_CreateWidgetRegion(lua_State* state);
+int EQ2Emu_lua_RemoveRegion(lua_State* state);
 #endif

+ 3 - 0
EQ2/source/WorldServer/LuaInterface.cpp

@@ -1485,6 +1485,9 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "SetLootDropType", EQ2Emu_lua_SetLootDropType);
 	
 	lua_register(state, "DamageEquippedItems", EQ2Emu_lua_DamageEquippedItems);
+	
+	lua_register(state, "CreateWidgetRegion", EQ2Emu_lua_CreateWidgetRegion);
+	lua_register(state, "RemoveRegion", EQ2Emu_lua_RemoveRegion);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {

+ 28 - 3
EQ2/source/WorldServer/Spawn.cpp

@@ -39,6 +39,7 @@ extern RuleManager rule_manager;
 extern World world;
 extern ZoneList zone_list;
 extern MasterRaceTypeList race_types_list;
+extern LuaInterface* lua_interface;
 
 Spawn::Spawn(){ 
 	loot_coins = 0;
@@ -135,6 +136,7 @@ Spawn::Spawn(){
 	loot_drop_type = 0;
 	deleted_spawn = false;
 	is_collector = false;
+	trigger_widget_id = 0;
 }
 
 Spawn::~Spawn(){
@@ -2049,6 +2051,7 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 	int16 version = packet->GetVersion();
 
 	int32 new_grid_id = 0;
+	int32 new_widget_id = 0;
 	if(player->GetMap() != nullptr && player->GetMap()->IsMapLoaded())
 	{
 		m_GridMutex.writelock(__FUNCTION__, __LINE__);
@@ -2059,12 +2062,14 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 				itr->second.timestamp = Timer::GetCurrentTime2()+100;
 				itr->second.npc_save = false;
 				new_grid_id = itr->second.grid_id;
+				new_widget_id = itr->second.widget_id;
 			}
 			else {
 				auto loc = glm::vec3(GetX(), GetZ(), GetY());
-				float new_z = player->GetMap()->FindBestZ(loc, nullptr, &new_grid_id);
+				float new_z = player->GetMap()->FindBestZ(loc, nullptr, &new_grid_id, &new_widget_id);
 				TimedGridData data;
 				data.grid_id = new_grid_id;
+				data.widget_id = new_widget_id;
 				data.x = GetX();
 				data.y = GetY();
 				data.z = GetZ();
@@ -2073,8 +2078,10 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 				established_grid_id.insert(make_pair(packet->GetVersion(), data));
 			}
 		}
-		else
+		else {
 			new_grid_id = itr->second.grid_id;
+			new_widget_id = itr->second.widget_id;
+		}
 		m_GridMutex.releasewritelock(__FUNCTION__, __LINE__);
 	}
 	
@@ -3928,12 +3935,15 @@ void Spawn::FixZ(bool forceUpdate) {
 	glm::vec3 current_loc(GetX(), GetZ(), GetY());
 
 	uint32 GridID = 0;
+	uint32 WidgetID = 0;
 	float new_z = GetY();
 	if(GetMap() != nullptr) {
-		float new_z = GetMap()->FindBestZ(current_loc, nullptr, &GridID);
+		float new_z = GetMap()->FindBestZ(current_loc, nullptr, &GridID, &WidgetID);
 
 		if ((IsTransportSpawn() || !IsFlyingCreature()) && GridID != 0 && GridID != appearance.pos.grid_id)
 			SetPos(&(appearance.pos.grid_id), GridID);
+		
+		trigger_widget_id = WidgetID;
 	}
 
 	// no need to go any further for players, flying creatures or objects, just needed the grid id set
@@ -4434,4 +4444,19 @@ void Spawn::SetAppearancePosition(float x, float y, float z) {
 		((Player*)this)->SetSideSpeed(0);
 		((Player*)this)->pos_packet_speed = 0;
 	}
+}
+
+
+int32 Spawn::InsertRegionToSpawn(Region_Node* node, ZBSP_Node* bsp_root, WaterRegionType regionType, bool in_region) {
+	std::map<Region_Node*, ZBSP_Node*> newMap;
+	newMap.insert(make_pair(node, bsp_root));
+	Region_Status status;
+	status.inRegion = in_region;
+	status.regionType = regionType;
+	int32 returnValue = 0;
+	lua_interface->RunRegionScript(node->regionScriptName, "EnterRegion", GetZone(), this, RegionTypeUntagged, &returnValue);
+	status.timerTic = returnValue;
+	status.lastTimerTic = returnValue ? Timer::GetCurrentTime2() : 0;
+	Regions.insert(make_pair(newMap, status));	
+	return returnValue;
 }

+ 6 - 1
EQ2/source/WorldServer/Spawn.h

@@ -248,6 +248,7 @@ struct TimedGridData {
 	float y;
 	float z;
 	bool npc_save;
+	int32 widget_id;
 };
 
 class Spawn {
@@ -1108,7 +1109,8 @@ public:
 	bool	forceMapCheck;
 	bool	is_water_creature;
 	bool	is_flying_creature;
-
+	int32	trigger_widget_id;
+	
 	std::atomic<bool> following;
 	bool	IsPet() { return is_pet; }
 	void	SetPet(bool val) { is_pet = val; }
@@ -1284,6 +1286,9 @@ public:
 	void SetDeletedSpawn(bool val) { deleted_spawn = val; }
 	bool IsDeletedSpawn() { return deleted_spawn; }
 	
+	
+	int32 InsertRegionToSpawn(Region_Node* node, ZBSP_Node* bsp_root, WaterRegionType regionType, bool in_region = true);
+	
 	EquipmentItemList equipment_list;
 	EquipmentItemList appearance_equipment_list;
 protected:

+ 181 - 13
EQ2/source/WorldServer/Zone/map.cpp

@@ -78,7 +78,7 @@ Map::~Map() {
 	}
 }
 
-float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result, uint32* GridID)
+float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result, uint32* GridID, uint32* WidgetID)
 {
 	if (!IsMapLoaded())
 		return BEST_Z_INVALID;
@@ -95,7 +95,7 @@ float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result, uint32* GridID)
 	float hit_distance;
 	bool hit = false;
 
-	hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID);
+	hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID);
 	if(hit) {
 		return result->z;
 	}
@@ -103,7 +103,7 @@ float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result, uint32* GridID)
 	// Find nearest Z above us
 	
 	to.z = -BEST_Z_INVALID;
-	hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID);
+	hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID);
 	if (hit)
 	{
 		return result->z;
@@ -112,7 +112,7 @@ float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result, uint32* GridID)
 	return BEST_Z_INVALID;
 }
 
-float Map::FindClosestZ(glm::vec3 &start, glm::vec3 *result) {
+float Map::FindClosestZ(glm::vec3 &start, glm::vec3 *result, uint32 *GridID, uint32* WidgetID) {
 	if (!IsMapLoaded())
 		return false;
 	// Unlike FindBestZ, this method finds the closest Z value above or below the specified point.
@@ -130,9 +130,8 @@ float Map::FindClosestZ(glm::vec3 &start, glm::vec3 *result) {
 	glm::vec3 to(start.x, start.y, BEST_Z_INVALID);
 	float hit_distance;
 	bool hit = false;
-	uint32 grid_id = 0;
 	// first check is below us
-	hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)grid_id);
+	hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID);
 	if (hit) {
 		ClosestZ = result->z;
 		
@@ -140,7 +139,7 @@ float Map::FindClosestZ(glm::vec3 &start, glm::vec3 *result) {
 	
 	// Find nearest Z above us
 	to.z = -BEST_Z_INVALID;
-	hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)grid_id);
+	hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID);
 	if (hit) {
 		if (std::abs(from.z - result->z) < std::abs(ClosestZ - from.z))
 			return result->z;
@@ -154,7 +153,7 @@ bool Map::LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, glm::ve
 		return false;
 	if(!imp)
 		return false;
-	return imp->rm->raycast((const RmReal*)&start, (const RmReal*)&end, (RmReal*)result, nullptr, nullptr, nullptr);
+	return imp->rm->raycast((const RmReal*)&start, (const RmReal*)&end, (RmReal*)result, nullptr, nullptr, nullptr, nullptr);
 }
 
 bool Map::LineIntersectsZoneNoZLeaps(glm::vec3 start, glm::vec3 end, float step_mag, glm::vec3 *result) {
@@ -251,7 +250,7 @@ bool Map::CheckLoS(glm::vec3 myloc, glm::vec3 oloc)
 	if(!imp)
 		return false;
 
-	return !imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, nullptr, nullptr, nullptr);
+	return !imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, nullptr, nullptr, nullptr, nullptr);
 }
 
 // returns true if a collision happens
@@ -261,7 +260,7 @@ bool Map::DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, glm::vec3 &outnorm,
 	if(!imp)
 		return false;
 
-	return imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, (RmReal *)&outnorm, (RmReal *)&distance, nullptr);
+	return imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, (RmReal *)&outnorm, (RmReal *)&distance, nullptr, nullptr);
 }
 
 Map *Map::LoadMapFile(std::string zonename, std::string file) {
@@ -370,6 +369,7 @@ bool Map::LoadV2(FILE* f) {
 	std::vector<glm::vec3> verts;
 	std::vector<uint32> indices;
 	std::vector<uint32> grids;
+	std::vector<uint32> widgets;
 
 	uint32 face_count = 0;
 	// Loop through the grids loading the face list
@@ -429,6 +429,7 @@ bool Map::LoadV2(FILE* f) {
 			indices.push_back((uint32)sz + 2);
 
 			grids.push_back((uint32)GridID);
+			widgets.push_back((uint32)0);
 		}
 	}
 	face_count = face_count / 3;
@@ -441,7 +442,7 @@ bool Map::LoadV2(FILE* f) {
 		imp = new impl;
 	}
 
-	imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0]);
+	imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0], &widgets[0]);
 
 	if (!imp->rm) {
 		delete imp;
@@ -452,6 +453,166 @@ bool Map::LoadV2(FILE* f) {
 	return true;
 }
 
+bool Map::LoadV3Deflated(std::ifstream* file, std::streambuf * const srcbuf) {
+	
+	std::vector<glm::vec3> verts;
+	std::vector<uint32> indices;
+	std::vector<uint32> grids;
+	std::vector<uint32> widgets;
+	
+	int8 strSize = 0;
+	char* buf = new char[1024];
+	
+	int32 mapVersion = 0;
+	srcbuf->sgetn(buf,sizeof(int32));
+	memcpy(&mapVersion,&buf[0],sizeof(int32));
+	LogWrite(MAP__DEBUG, 0, "Map", "MapVersion = %u", mapVersion);
+	
+	srcbuf->sgetn(buf,sizeof(int8));
+	memcpy(&strSize,&buf[0],sizeof(int8));
+	LogWrite(MAP__DEBUG, 0, "Map", "strSize = %u", strSize);
+
+	char name[256];
+	srcbuf->sgetn(&name[0],strSize);
+	name[strSize] = '\0';
+	LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name);
+	string fileName(name);
+	std::size_t found = fileName.find(m_ZoneName);
+	// Make sure file contents are for the correct zone
+	if (found == std::string::npos) {
+		file->close();
+		safe_delete_array(buf);
+		LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV3Deflated() map contents (%s) do not match its name (%s).", &name, m_ZoneFile.c_str());
+		return false;
+	}
+	// Read the min bounds
+	srcbuf->sgetn(buf,sizeof(float));
+	memcpy(&m_MinX,&buf[0],sizeof(float));
+	srcbuf->sgetn(buf,sizeof(float));
+	memcpy(&m_MinY,&buf[0],sizeof(float));
+	srcbuf->sgetn(buf,sizeof(float));
+	memcpy(&m_MinZ,&buf[0],sizeof(float));
+
+	srcbuf->sgetn(buf,sizeof(float));
+	memcpy(&m_MaxX,&buf[0],sizeof(float));
+	srcbuf->sgetn(buf,sizeof(float));
+	memcpy(&m_MaxY,&buf[0],sizeof(float));
+	srcbuf->sgetn(buf,sizeof(float));
+	memcpy(&m_MaxZ,&buf[0],sizeof(float));
+
+	// Read the number of grids
+	int32 NumGrids;
+	srcbuf->sgetn(buf,sizeof(int32));
+	memcpy(&NumGrids,&buf[0],sizeof(int32));
+
+	uint32 face_count = 0;
+	// Loop through the grids loading the face list
+	for (int32 i = 0; i < NumGrids; i++) {
+		// Read the grid id
+		int32 GridID;
+		srcbuf->sgetn(buf,sizeof(int32));
+		memcpy(&GridID,&buf[0],sizeof(int32));
+
+		// Read the number of vertices
+		int32 vertex_map_count;
+		srcbuf->sgetn(buf,sizeof(int32));
+		memcpy(&vertex_map_count,&buf[0],sizeof(int32));
+		
+		for(int32 m = 0; m < vertex_map_count; m++) {
+			int32 WidgetID;
+			srcbuf->sgetn(buf,sizeof(int32));
+			memcpy(&WidgetID,&buf[0],sizeof(int32));
+			
+			float w_x1, w_y1, w_z1;
+
+			// read widget coords
+			srcbuf->sgetn(buf,sizeof(float)*3);
+			memcpy(&w_x1,&buf[0],sizeof(float));
+			memcpy(&w_y1,&buf[4],sizeof(float));
+			memcpy(&w_z1,&buf[8],sizeof(float));
+			
+			glm::vec3 a(w_x1, w_y1, w_z1);
+			widget_map.insert(make_pair(WidgetID, a));
+			
+			int32 NumFaces;
+			srcbuf->sgetn(buf,sizeof(int32));
+			memcpy(&NumFaces,&buf[0],sizeof(int32));
+			
+			face_count += NumFaces;	
+			
+			for (int32 y = 0; y < NumFaces; ) {
+						// Each vertex need an x,y,z coordinate and 
+			// we will be reading 3 to create the face
+						float x1, x2, x3;
+						float y1, y2, y3;
+						float z1, z2, z3;
+
+						// Read the first vertex
+						srcbuf->sgetn(buf,sizeof(float)*3);
+						memcpy(&x1,&buf[0],sizeof(float));
+						memcpy(&y1,&buf[4],sizeof(float));
+						memcpy(&z1,&buf[8],sizeof(float));
+						y++;
+
+						// Read the second vertex
+						srcbuf->sgetn(buf,sizeof(float)*3);
+						memcpy(&x2,&buf[0],sizeof(float));
+						memcpy(&y2,&buf[4],sizeof(float));
+						memcpy(&z2,&buf[8],sizeof(float));
+						y++;
+
+						// Read the third (final) vertex
+						srcbuf->sgetn(buf,sizeof(float)*3);
+						memcpy(&x3,&buf[0],sizeof(float));
+						memcpy(&y3,&buf[4],sizeof(float));
+						memcpy(&z3,&buf[8],sizeof(float));
+						y++;
+
+						glm::vec3 a(x1, z1, y1);
+						glm::vec3 b(x2, z2, y2);
+						glm::vec3 c(x3, z3, y3);
+
+						size_t sz = verts.size();
+						verts.push_back(a);
+						indices.push_back((uint32)sz);
+
+						verts.push_back(b);
+						indices.push_back((uint32)sz + 1);
+
+						verts.push_back(c);
+						indices.push_back((uint32)sz + 2);
+									
+						grids.push_back(GridID);
+						widgets.push_back(WidgetID);
+					}
+
+		}
+		// Loop through the vertices list reading
+		// 3 at a time to creat a triangle (face)
+	}
+	face_count = face_count / 3;
+
+	if (imp) {
+		imp->rm->release();
+		imp->rm = nullptr;
+	}
+	else {
+		imp = new impl;
+	}
+
+	imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0], &widgets[0]);
+
+	file->close();
+	safe_delete_array(buf);
+	if (!imp->rm) {
+		delete imp;
+		imp = nullptr;
+		return false;
+	}
+
+	return true;
+}
+
 bool Map::LoadV2Deflated(FILE* f) {
     std::ifstream file(m_FileName.c_str(), ios_base::in | ios_base::binary);
     boost::iostreams::filtering_streambuf<boost::iostreams::input> inbuf;
@@ -477,8 +638,13 @@ bool Map::LoadV2Deflated(FILE* f) {
 	srcbuf->sgetn(&name[0],strSize);
 	name[strSize] = '\0';
 	LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name);
-
 	string fileName(name);
+	
+	if(fileName.find("EQ2EmuMapTool") != std::string::npos) {
+		return(LoadV3Deflated(&file, srcbuf));
+	}
+	
+	
 	std::size_t found = fileName.find(m_ZoneName);
 	// Make sure file contents are for the correct zone
 	if (found == std::string::npos) {
@@ -506,6 +672,7 @@ bool Map::LoadV2Deflated(FILE* f) {
 	std::vector<glm::vec3> verts;
 	std::vector<uint32> indices;
 	std::vector<uint32> grids;
+	std::vector<uint32> widgets;
 
 	uint32 face_count = 0;
 	// Loop through the grids loading the face list
@@ -570,6 +737,7 @@ bool Map::LoadV2Deflated(FILE* f) {
 			indices.push_back((uint32)sz + 2);
 
 			grids.push_back(GridID);
+			widgets.push_back((uint32)0);
 		}
 	}
 	face_count = face_count / 3;
@@ -582,7 +750,7 @@ bool Map::LoadV2Deflated(FILE* f) {
 		imp = new impl;
 	}
 
-	imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0]);
+	imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0], &widgets[0]);
 
 	file.close();
 	safe_delete_array(buf);

+ 4 - 2
EQ2/source/WorldServer/Zone/map.h

@@ -36,8 +36,8 @@ public:
 	Map(string zonename, string filename);
 	~Map();
 
-	float FindBestZ(glm::vec3 &start, glm::vec3 *result, uint32 *GridID = 0);
-	float FindClosestZ(glm::vec3 &start, glm::vec3 *result);
+	float FindBestZ(glm::vec3 &start, glm::vec3 *result, uint32 *GridID = 0, uint32* WidgetID = 0);
+	float FindClosestZ(glm::vec3 &start, glm::vec3 *result, uint32 *GridID = 0, uint32* WidgetID = 0);
 	bool LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, glm::vec3 *result);
 	bool LineIntersectsZoneNoZLeaps(glm::vec3 start, glm::vec3 end, float step_mag, glm::vec3 *result);
 	bool CheckLoS(glm::vec3 myloc, glm::vec3 oloc);
@@ -83,12 +83,14 @@ public:
 	void SetFileName(std::string newfile) { m_FileName = string(newfile); }
 	
 	void MapMinMaxY(float y);
+	std::map<int32, glm::vec3> widget_map;
 private:
 	void RotateVertex(glm::vec3 &v, float rx, float ry, float rz);
 	void ScaleVertex(glm::vec3 &v, float sx, float sy, float sz);
 	void TranslateVertex(glm::vec3 &v, float tx, float ty, float tz);
 	bool LoadV2(FILE *f);
 	bool LoadV2Deflated(FILE *f);
+	bool LoadV3Deflated(std::ifstream* file, std::streambuf * const srcbuf);
 
 	string m_FileName;
 	string m_ZoneFile;

+ 20 - 7
EQ2/source/WorldServer/Zone/raycast_mesh.cpp

@@ -651,9 +651,11 @@ public:
 							RmReal *hitNormal,
 							RmReal *hitDistance,
 							RmUint32 *GridID,
+							RmUint32 *WidgetID,
 							const RmReal *vertices,
 							const RmUint32 *indices,
 							const RmUint32 *grids,
+							const RmUint32 *widgets,
 							RmReal &nearestDistance,
 							NodeInterface *callback,
 							RmUint32 *raycastTriangles,
@@ -713,6 +715,9 @@ public:
 								if(GridID) {
 									*GridID = grids[tri];
 								}
+								if(WidgetID) {
+									*WidgetID = widgets[tri];
+								}
 								nearestTriIndex = tri;
 								hit = true;
 							}
@@ -724,11 +729,11 @@ public:
 			{
 				if ( mLeft )
 				{
-					mLeft->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,vertices,indices,grids,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex);
+					mLeft->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,WidgetID,vertices,indices,grids,widgets,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex);
 				}
 				if ( mRight )
 				{
-					mRight->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,vertices,indices,grids,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex);
+					mRight->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,WidgetID,vertices,indices,grids,widgets,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex);
 				}
 			}
 		}
@@ -743,7 +748,7 @@ class MyRaycastMesh : public RaycastMesh, public NodeInterface
 {
 public:
 
-	MyRaycastMesh(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,const RmUint32 *indices,const RmUint32 *grids,RmUint32 maxDepth,RmUint32 minLeafSize,RmReal minAxisSize)
+	MyRaycastMesh(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,const RmUint32 *indices,const RmUint32 *grids,const RmUint32 *widgets,RmUint32 maxDepth,RmUint32 minLeafSize,RmReal minAxisSize)
 	{
 		mRaycastFrame = 0;
 		if ( maxDepth < 2 )
@@ -772,6 +777,8 @@ public:
 		memset(mRaycastTriangles,0,tcount*sizeof(RmUint32));
 		mGrids = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount);
 		memcpy(mGrids,grids,sizeof(RmUint32)*tcount);
+		mWidgets = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount);
+		memcpy(mWidgets,widgets,sizeof(RmUint32)*tcount);
 		mRoot = getNode();
 		mFaceNormals = NULL;
 		new ( mRoot ) NodeAABB(mVcount,mVertices,mTcount,mIndices,maxDepth,minLeafSize,minAxisSize,this,mLeafTriangles);
@@ -785,9 +792,10 @@ public:
 		::free(mFaceNormals);
 		::free(mRaycastTriangles);
 		::free(mGrids);
+		::free(mWidgets);
 	}
 
-	virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance,RmUint32 *GridID)
+	virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance,RmUint32 *GridID,RmUint32 *WidgetID)
 	{
 		bool ret = false;
 
@@ -803,7 +811,7 @@ public:
 		dir[2]*=recipDistance;
 		mRaycastFrame++;
 		RmUint32 nearestTriIndex=TRI_EOF;
-		mRoot->raycast(ret,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,mVertices,mIndices,mGrids,distance,this,mRaycastTriangles,mRaycastFrame,mLeafTriangles,nearestTriIndex);
+		mRoot->raycast(ret,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,WidgetID,mVertices,mIndices,mGrids,mWidgets,distance,this,mRaycastTriangles,mRaycastFrame,mLeafTriangles,nearestTriIndex);
 		return ret;
 	}
 
@@ -852,7 +860,7 @@ public:
 		faceNormal[2] = src[2];
 	}
 
-	virtual bool bruteForceRaycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance,RmUint32 *GridID)
+	virtual bool bruteForceRaycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance,RmUint32 *GridID,RmUint32 *WidgetID)
 	{
 		bool ret = false;
 
@@ -908,6 +916,9 @@ public:
 					if(GridID) {
 						*GridID = mGrids[tri];
 					}
+					if(WidgetID) {
+						*WidgetID = mWidgets[tri];
+					}
 					ret = true;
 				}
 			}
@@ -928,6 +939,7 @@ public:
 	NodeAABB		*mNodes;
 	TriVector		mLeafTriangles;
 	RmUint32		*mGrids;
+	RmUint32		*mWidgets;
 };
 
 };
@@ -942,10 +954,11 @@ RaycastMesh * createRaycastMesh(RmUint32 vcount,		// The number of vertices in t
 								RmUint32 tcount,		// The number of triangles in the source triangle mesh
 								const RmUint32 *indices, // The triangle indices in the format of i1,i2,i3 ... i4,i5,i6, ...
 								const RmUint32 *grids,
+								const RmUint32 *widgets,
 								RmUint32 maxDepth,	// Maximum recursion depth for the triangle mesh.
 								RmUint32 minLeafSize,	// minimum triangles to treat as a 'leaf' node.
 								RmReal	minAxisSize )	// once a particular axis is less than this size, stop sub-dividing.
 {
-	auto m = new MyRaycastMesh(vcount, vertices, tcount, indices, grids, maxDepth, minLeafSize, minAxisSize);
+	auto m = new MyRaycastMesh(vcount, vertices, tcount, indices, grids, widgets, maxDepth, minLeafSize, minAxisSize);
 	return static_cast< RaycastMesh * >(m);
 }

+ 3 - 2
EQ2/source/WorldServer/Zone/raycast_mesh.h

@@ -36,8 +36,8 @@ typedef unsigned int RmUint32;
 class RaycastMesh
 {
 public:
-	virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance, RmUint32 *GridID) = 0;
-	virtual bool bruteForceRaycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance, RmUint32 *GridID) = 0;
+	virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance, RmUint32 *GridID, RmUint32 *WidgetID) = 0;
+	virtual bool bruteForceRaycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance, RmUint32 *GridID, RmUint32 *WidgetID) = 0;
 
 	virtual const RmReal * getBoundMin(void) const = 0; // return the minimum bounding box
 	virtual const RmReal * getBoundMax(void) const = 0; // return the maximum bounding box.
@@ -52,6 +52,7 @@ RaycastMesh * createRaycastMesh(RmUint32 vcount,		// The number of vertices in t
 								RmUint32 tcount,		// The number of triangles in the source triangle mesh
 								const RmUint32 *indices, // The triangle indices in the format of i1,i2,i3 ... i4,i5,i6, ...
 								const RmUint32 *grids,
+								const RmUint32 *widgets,
 								RmUint32 maxDepth=15,	// Maximum recursion depth for the triangle mesh.
 								RmUint32 minLeafSize=4,	// minimum triangles to treat as a 'leaf' node.
 								RmReal	minAxisSize=0.01f	// once a particular axis is less than this size, stop sub-dividing.

+ 6 - 0
EQ2/source/WorldServer/Zone/region_map.h

@@ -9,6 +9,9 @@
 
 class Client;
 class Spawn;
+class ZoneServer;
+class Region_Node;
+class ZBSP_Node;
 
 enum WaterRegionType : int {
 	RegionTypeUnsupported = -2,
@@ -50,6 +53,9 @@ public:
 	virtual void MapRegionsNearSpawn(Spawn* spawn, Client* client=0) const = 0;
 	virtual void UpdateRegionsNearSpawn(Spawn* spawn, Client* client=0) const = 0;
 	virtual void TicRegionsNearSpawn(Spawn* spawn, Client* client=0) const = 0;
+	
+	virtual void InsertRegionNode(ZoneServer* zone, int32 version, std::string regionName, std::string envName, uint32 gridID, uint32 triggerWidgetID, float dist = 0.0f) = 0;
+	virtual void RemoveRegionNode(std::string regionName) = 0;
 protected:
 	virtual bool Load(FILE *fp) { return false; }
 };

+ 151 - 26
EQ2/source/WorldServer/Zone/region_map_v1.cpp

@@ -3,17 +3,20 @@
 #include "../client.h"
 #include "../Spawn.h"
 #include "../LuaInterface.h"
+#include "../World.h"
 
 #undef snprintf
 #include <boost/filesystem.hpp>
 
 extern LuaInterface* lua_interface;
+extern World world;
 
 RegionMapV1::RegionMapV1() {
 	mVersion = 1;
 }
 
 RegionMapV1::~RegionMapV1() {
+    std::unique_lock lock(MRegions);
 	map<Region_Node*, ZBSP_Node*>::const_iterator itr;
 	int region_num = 0;
 	for (itr = Regions.begin(); itr != Regions.end();)
@@ -176,8 +179,10 @@ bool RegionMapV1::Load(FILE* fp, std::string inZoneNameLwr, int32 version) {
 		}
 		
 		tmpNode->vert_count = bsp_tree_size;
-
+		
+		MRegions.lock();
 		Regions.insert(make_pair(tmpNode, BSP_Root));
+		MRegions.unlock();
 	}
 
 	fclose(fp);
@@ -189,20 +194,29 @@ bool RegionMapV1::Load(FILE* fp, std::string inZoneNameLwr, int32 version) {
 
 void RegionMapV1::IdentifyRegionsInGrid(Client *client, const glm::vec3 &location) const
 {
+    std::shared_lock lock(MRegions);
 	map<Region_Node*, ZBSP_Node*>::const_iterator itr;
 	int region_num = 0;
 
 	int32 grid = 0;
+	int32 widget_id = 0;
+	float x =0.0f,y = 0.0f,z = 0.0f;
 	if (client->GetPlayer()->GetMap() != nullptr && client->GetPlayer()->GetMap()->IsMapLoaded())
 	{
 		auto loc = glm::vec3(location.x, location.z, location.y);
-		float new_z = client->GetPlayer()->GetMap()->FindBestZ(loc, nullptr, &grid);
+		float new_z = client->GetPlayer()->GetMap()->FindBestZ(loc, nullptr, &grid, &widget_id);
+		
+		std::map<int32, glm::vec3>::iterator itr = client->GetPlayer()->GetMap()->widget_map.find(widget_id);
+		if(itr != client->GetPlayer()->GetMap()->widget_map.end()) {
+			x = itr->second.x;
+			y = itr->second.y;
+			z = itr->second.z;
+		}
 	}
 	else
 		client->SimpleMessage(CHANNEL_COLOR_RED, "No map to establish grid id, using grid id 0 (attempt match all).");
 
-	client->Message(2, "Region check against location %f / %f / %f. Grid to try: %u, player grid is %u", location.x, location.y, location.z, grid, client->GetPlayer()->appearance.pos.grid_id);
-
+	client->Message(2, "Region check against location %f / %f / %f. Grid to try: %u, player grid is %u, widget id is %u.  Widget location is %f %f %f.", location.x, location.y, location.z, grid, client->GetPlayer()->appearance.pos.grid_id, widget_id, x, y, z);
 	for (itr = Regions.begin(); itr != Regions.end(); itr++)
 	{
 		Region_Node *node = itr->first;
@@ -215,7 +229,12 @@ void RegionMapV1::IdentifyRegionsInGrid(Client *client, const glm::vec3 &locatio
 			float z1 = node->z - location.z;
 			float dist = sqrt(x1 *x1 + y1 *y1 + z1 *z1);
 			glm::vec3 testLoc(location.x, location.y, location.z);
-			if (dist <= node->dist)
+			if(!BSP_Root) {
+				if(client)
+				client->Message(CHANNEL_COLOR_YELLOW, "[%s] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, Script: %s.  X: %f, Y: %f, Z: %f, Distance: %f, Widget ID Marker: %u", (widget_id == node->trigger_widget_id) ? "IN REGION" : "WIDGET MARKER", region_num, 
+				node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), node->regionScriptName.c_str(), node->x, node->y, node->z, node->dist, node->trigger_widget_id);
+			}
+			else if (dist <= node->dist)
 			{
 				WaterRegionType regionType = RegionTypeUntagged;
 
@@ -249,6 +268,7 @@ void RegionMapV1::IdentifyRegionsInGrid(Client *client, const glm::vec3 &locatio
 
 void RegionMapV1::MapRegionsNearSpawn(Spawn *spawn, Client *client) const
 {
+    std::shared_lock lock(MRegions);
 	map<Region_Node*, ZBSP_Node*>::const_iterator itr;
 	int region_num = 0;
 
@@ -263,6 +283,18 @@ void RegionMapV1::MapRegionsNearSpawn(Spawn *spawn, Client *client) const
 		if (node->regionScriptName.size() < 1)	// only track ones that are used with LUA scripting
 			continue;
 
+		if(!BSP_Root) {
+			int32 currentGridID = spawn->appearance.pos.grid_id;
+			bool inRegion = false;
+			if(!(inRegion = spawn->InRegion(node, nullptr)) && currentGridID == node->grid_id && 
+			( node->trigger_widget_id == spawn->trigger_widget_id || (node->dist > 0.0f && spawn->GetDistance(node->x, node->y, node->z) < node->dist)) ) {
+				int32 returnValue = spawn->InsertRegionToSpawn(node, nullptr, RegionTypeUntagged);
+				if (client)
+					client->Message(CHANNEL_COLOR_YELLOW, "[ENTER REGION %i %u] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f.  Script: %s", RegionTypeUntagged, returnValue, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(),
+						node->dist, node->x, node->y, node->z, node->regionScriptName.c_str());
+			}	
+			continue;
+		}
 		float x1 = node->x - testLoc.x;
 		float y1 = node->y - testLoc.y;
 		float z1 = node->z - testLoc.z;
@@ -281,17 +313,7 @@ void RegionMapV1::MapRegionsNearSpawn(Spawn *spawn, Client *client) const
 				if (!spawn->InRegion(node, BSP_Root))
 				{
 					spawn->DeleteRegion(node, BSP_Root);
-					std::map<Region_Node*, ZBSP_Node*> newMap;
-					newMap.insert(make_pair(node, BSP_Root));
-					Region_Status status;
-					status.inRegion = true;
-					status.regionType = regionType;
-					int32 returnValue = 0;
-					lua_interface->RunRegionScript(node->regionScriptName, "EnterRegion", spawn->GetZone(), spawn, regionType, &returnValue);
-					status.timerTic = returnValue;
-					status.lastTimerTic = returnValue ? Timer::GetCurrentTime2() : 0;
-					spawn->Regions.insert(make_pair(newMap, status));
-
+					int32 returnValue = spawn->InsertRegionToSpawn(node, BSP_Root, regionType);
 					if (client)
 						client->Message(CHANNEL_COLOR_YELLOW, "[ENTER REGION %i %u] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f.  Script: %s", regionType, returnValue, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(),
 							node->dist, node->x, node->y, node->z, node->regionScriptName.c_str());
@@ -309,14 +331,7 @@ void RegionMapV1::MapRegionsNearSpawn(Spawn *spawn, Client *client) const
 				}
 				spawn->DeleteRegion(node, BSP_Root);
 
-				std::map<Region_Node*, ZBSP_Node*> newMap;
-				newMap.insert(make_pair(node, BSP_Root));
-				Region_Status status;
-				status.inRegion = false;
-				status.timerTic = 0;
-				status.lastTimerTic = 0;
-				status.regionType = RegionTypeNormal;
-				spawn->Regions.insert(make_pair(newMap, status));
+				spawn->InsertRegionToSpawn(node, BSP_Root, RegionTypeNormal, false);
 				if (client)
 					client->Message(CHANNEL_COLOR_RED, "[NEAR REGION] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f.  Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(),
 						node->dist, node->x, node->y, node->z, node->regionScriptName.c_str());
@@ -341,11 +356,19 @@ void RegionMapV1::UpdateRegionsNearSpawn(Spawn *spawn, Client *client) const
 	map<Region_Node*, ZBSP_Node*> deleteNodes;
 	for (testitr = spawn->Regions.begin(); testitr != spawn->Regions.end(); testitr++)
 	{
-
 		map<Region_Node*, ZBSP_Node*>::const_iterator actualItr = testitr->first.begin();
 		Region_Node *node = actualItr->first;
 		ZBSP_Node *BSP_Root = actualItr->second;
 
+		std::map<Region_Node*, bool>::const_iterator dead_itr = dead_nodes.find(node);
+		if(dead_itr != dead_nodes.end()) {
+			deleteNodes.insert(make_pair(node, BSP_Root));
+			continue;
+		}
+		if(!BSP_Root) {
+			continue;
+		}
+		
 		float x1 = node->x - testLoc.x;
 		float y1 = node->y - testLoc.y;
 		float z1 = node->z - testLoc.z;
@@ -423,7 +446,39 @@ void RegionMapV1::TicRegionsNearSpawn(Spawn *spawn, Client *client) const
 		Region_Node *node = actualItr->first;
 		ZBSP_Node *BSP_Root = actualItr->second;
 
-		if (testitr->second.timerTic && testitr->second.inRegion && Timer::GetCurrentTime2() >= (testitr->second.lastTimerTic + testitr->second.timerTic))
+		std::map<Region_Node*, bool>::const_iterator dead_itr = dead_nodes.find(node);
+		if(dead_itr != dead_nodes.end()) {
+			continue;
+		}
+		
+		if(!BSP_Root) {
+			bool passDistCheck = false;
+			int32 currentGridID = spawn->appearance.pos.grid_id;
+			if(testitr->second.timerTic && currentGridID == node->grid_id && (node->trigger_widget_id == spawn->trigger_widget_id || (node->dist > 0.0f && spawn->GetDistance(node->x, node->y, node->z) <= node->dist && (passDistCheck = true)))
+				&& Timer::GetCurrentTime2() >= (testitr->second.lastTimerTic + testitr->second.timerTic)) {
+					testitr->second.lastTimerTic = Timer::GetCurrentTime2();
+					if (client)
+						client->Message(CHANNEL_COLOR_RED, "[TICK] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f.  Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(),
+							node->dist, node->x, node->y, node->z, node->regionScriptName.c_str());
+
+					int32 returnValue = 0;
+					lua_interface->RunRegionScript(node->regionScriptName, "Tick", spawn->GetZone(), spawn, RegionTypeUntagged, &returnValue);
+
+					if (returnValue == 1)
+					{
+						testitr->second.lastTimerTic = 0;
+						testitr->second.timerTic = 0;
+					}
+			}
+			else if(currentGridID != node->grid_id || (node->trigger_widget_id != spawn->trigger_widget_id && !passDistCheck)) {
+					if (client)
+						client->Message(CHANNEL_COLOR_RED, "[LEAVE REGION] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f.  Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(),
+							node->dist, node->x, node->y, node->z, node->regionScriptName.c_str());
+					lua_interface->RunRegionScript(node->regionScriptName, "LeaveRegion", spawn->GetZone(), spawn, RegionTypeUntagged);
+				spawn->DeleteRegion(node, nullptr);
+			}
+		}
+		else if (testitr->second.timerTic && testitr->second.inRegion && Timer::GetCurrentTime2() >= (testitr->second.lastTimerTic + testitr->second.timerTic))
 		{
 			testitr->second.lastTimerTic = Timer::GetCurrentTime2();
 			if (client)
@@ -453,6 +508,7 @@ void RegionMapV1::TicRegionsNearSpawn(Spawn *spawn, Client *client) const
 }
 
 WaterRegionType RegionMapV1::BSPReturnRegionType(int32 node_number, const glm::vec3& location, int32 gridid) const {
+    std::shared_lock lock(MRegions);
 	map<Region_Node*, ZBSP_Node*>::const_iterator itr;
 	int region_num = 0;
 	for (itr = Regions.begin(); itr != Regions.end(); itr++)
@@ -715,4 +771,73 @@ WaterRegionType RegionMapV1::EstablishDistanceAtAngle(const Region_Node* region_
 	}
 
 	return RegionTypeNormal;
+}
+
+void RegionMapV1::InsertRegionNode(ZoneServer* zone, int32 version, std::string regionName, std::string envName, uint32 gridID, uint32 triggerWidgetID, float dist)
+{
+		Region_Node* tmpNode = new Region_Node;
+		
+		tmpNode->x = 0.0f;
+		tmpNode->y = 0.0f;
+		tmpNode->z = 0.0f;
+		
+		if(!zone)
+			return;
+		
+		Map* current_map = world.GetMap(std::string(zone->GetZoneFile()), version);
+		if(current_map) {
+			std::map<int32, glm::vec3>::iterator itr = current_map->widget_map.find(triggerWidgetID);
+			if(itr != current_map->widget_map.end()) {
+				tmpNode->x = itr->second.x;
+				tmpNode->y = itr->second.y;
+				tmpNode->z = itr->second.z;
+			}
+		}
+		
+		tmpNode->dist = dist;
+		tmpNode->region_type = RegionTypeUntagged;
+		tmpNode->regionName = string(regionName);
+		tmpNode->regionEnvFileName = string(envName);
+		tmpNode->grid_id = gridID;
+		tmpNode->regionScriptName = string("");
+		tmpNode->trigger_widget_id = triggerWidgetID;
+		
+		tmpNode->regionScriptName = TestFile(regionName);
+		
+		if ( tmpNode->regionScriptName.size() < 1 )
+		{
+			tmpNode->regionScriptName = TestFile(envName);
+		}
+		if ( tmpNode->regionScriptName.size() < 1 )
+		{
+			tmpNode->regionScriptName = TestFile("default");
+		}
+		
+		tmpNode->vert_count = 0;
+
+		ZBSP_Node* BSP_Root = nullptr;
+		
+		MRegions.lock();
+		Regions.insert(make_pair(tmpNode, BSP_Root));
+		MRegions.unlock();
+}
+
+void RegionMapV1::RemoveRegionNode(std::string name) {
+	
+    std::unique_lock lock(MRegions);
+	map<Region_Node*, ZBSP_Node*>::const_iterator itr;
+	for (itr = Regions.begin(); itr != Regions.end();)
+	{
+		Region_Node *node = itr->first;
+		ZBSP_Node *BSP_Root = itr->second;
+		if(node->regionName.find(name) != node->regionName.npos) {
+			itr = Regions.erase(itr);
+			dead_nodes.insert(make_pair(node, true));
+			safe_delete(node);
+			safe_delete_array(BSP_Root);
+		}
+		else {
+			itr++;
+		}
+	}
 }

+ 11 - 2
EQ2/source/WorldServer/Zone/region_map_v1.h

@@ -1,9 +1,12 @@
 #ifndef EQ2EMU_REGION_MAP_V1_H
 #define EQ2EMU_REGION_MAP_V1_H
 
-#include "region_map.h"
+#include <mutex>
+#include <shared_mutex>
 #include <map>
 
+#include "region_map.h"
+
 class Client;
 class Spawn;
 
@@ -30,6 +33,7 @@ typedef struct Region_Node {
 	int32 grid_id;
 	string regionScriptName;
 	int32 vert_count;
+	int32 trigger_widget_id;
 } Region_Node;
 #pragma pack()
 
@@ -57,7 +61,9 @@ public:
 	virtual void MapRegionsNearSpawn(Spawn* spawn, Client* client=0) const;
 	virtual void UpdateRegionsNearSpawn(Spawn* spawn, Client* client=0) const;
 	virtual void TicRegionsNearSpawn(Spawn* spawn, Client* client=0) const;
-
+	
+	virtual void InsertRegionNode(ZoneServer* zone, int32 version, std::string regionName, std::string envName, uint32 gridID, uint32 triggerWidgetID, float dist = 0.0f);
+	virtual void RemoveRegionNode(std::string regionName);
 protected:
 	virtual bool Load(FILE *fp, std::string inZoneLowerName, int32 regionVersion);
 
@@ -76,6 +82,9 @@ private:
 
 	int32 mVersion;
 	std::string mZoneNameLower;
+	
+	mutable std::shared_mutex MRegions;
+	std::map<Region_Node*, bool> dead_nodes;
 };
 
 #endif

+ 7 - 7
EQ2/source/WorldServer/client.cpp

@@ -215,6 +215,7 @@ Client::Client(EQStream* ieqs) : underworld_cooldown_timer(5000), pos_update(125
 	disable_save = false;
 	SetZoningDestination(nullptr);
 	underworld_cooldown_timer.Disable();
+	player_pos_change_count = 0;
 }
 
 Client::~Client() {
@@ -3216,20 +3217,19 @@ bool Client::Process(bool zone_process) {
 			//GetPlayer()->CalculateLocation();
 			client_list.CheckPlayersInvisStatus(this);
 			GetCurrentZone()->SendPlayerPositionChanges(GetPlayer());
+			
 			player_pos_changed = false;
+			
 			GetCurrentZone()->CheckTransporters(this);
 			
 			if(GetPlayer()->GetRegionMap())
 			{
-				if((lastRegionRemapTime+1000) < Timer::GetCurrentTime2())
-				{
-					lastRegionRemapTime = Timer::GetCurrentTime2() + 1000;
-					GetPlayer()->GetRegionMap()->MapRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr);
-				}
-				else
-					GetPlayer()->GetRegionMap()->UpdateRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr);
+				GetPlayer()->GetRegionMap()->MapRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr);
 			}
 		}
+		else if (IsReadyForUpdates() && GetPlayer()->GetRegionMap()) {
+			GetPlayer()->GetRegionMap()->UpdateRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr);
+		}
 	}
 	if (lua_interface && lua_debug && lua_debug_timer.Check())
 		lua_interface->UpdateDebugClients(this);

+ 5 - 2
EQ2/source/WorldServer/client.h

@@ -20,8 +20,10 @@
 #ifndef CLIENT_H
 #define CLIENT_H
 
-#include "../common/EQStream.h"
 #include <list>
+#include <atomic>
+
+#include "../common/EQStream.h"
 #include "../common/timer.h"
 #include "Items/Items.h"
 #include "zoneserver.h"
@@ -643,7 +645,8 @@ private:
 	Timer	lua_debug_timer;
 	Timer	temp_placement_timer;
 	Timer	spawn_removal_timer;
-	bool	player_pos_changed;
+	std::atomic<bool> player_pos_changed;
+	std::atomic<int8> player_pos_change_count;
 	bool HandlePacket(EQApplicationPacket *app);
 	EQStream* eqs;
 	bool quickbar_changed;

BIN
server/Maps/tutorial_island02.EQ2MapDeflated