Browse Source

RegionScripts, lava/death region support

WorldServer side of Addressing issue #203 and issue #202
Image 3 years ago
parent
commit
66505ccea3
35 changed files with 1371 additions and 229 deletions
  1. 7 4
      EQ2/source/WorldServer/Combat.cpp
  2. 48 12
      EQ2/source/WorldServer/Commands/Commands.cpp
  3. 3 1
      EQ2/source/WorldServer/Commands/Commands.h
  4. 1 1
      EQ2/source/WorldServer/Entity.h
  5. 0 2
      EQ2/source/WorldServer/LoginServer.cpp
  6. 63 1
      EQ2/source/WorldServer/LuaFunctions.cpp
  7. 5 0
      EQ2/source/WorldServer/LuaFunctions.h
  8. 180 1
      EQ2/source/WorldServer/LuaInterface.cpp
  9. 14 1
      EQ2/source/WorldServer/LuaInterface.h
  10. 2 2
      EQ2/source/WorldServer/NPC.cpp
  11. 1 1
      EQ2/source/WorldServer/NPC.h
  12. 124 49
      EQ2/source/WorldServer/Spawn.cpp
  13. 23 1
      EQ2/source/WorldServer/Spawn.h
  14. 1 13
      EQ2/source/WorldServer/VisualStates.h
  15. 78 0
      EQ2/source/WorldServer/World.cpp
  16. 11 0
      EQ2/source/WorldServer/World.h
  17. 10 2
      EQ2/source/WorldServer/WorldDatabase.cpp
  18. 1 0
      EQ2/source/WorldServer/WorldDatabase.h
  19. 52 23
      EQ2/source/WorldServer/Zone/SPGrid.cpp
  20. 1 9
      EQ2/source/WorldServer/Zone/SPGrid.h
  21. 112 8
      EQ2/source/WorldServer/Zone/map.cpp
  22. 24 6
      EQ2/source/WorldServer/Zone/map.h
  23. 4 4
      EQ2/source/WorldServer/Zone/mob_movement_manager.cpp
  24. 61 10
      EQ2/source/WorldServer/Zone/region_map.cpp
  25. 81 5
      EQ2/source/WorldServer/Zone/region_map.h
  26. 367 11
      EQ2/source/WorldServer/Zone/region_map_v1.cpp
  27. 31 6
      EQ2/source/WorldServer/Zone/region_map_v1.h
  28. 33 13
      EQ2/source/WorldServer/client.cpp
  29. 6 1
      EQ2/source/WorldServer/client.h
  30. 1 1
      EQ2/source/WorldServer/makefile
  31. 1 1
      EQ2/source/WorldServer/makefile.a64
  32. 2 4
      EQ2/source/WorldServer/net.cpp
  33. 10 33
      EQ2/source/WorldServer/zoneserver.cpp
  34. 0 3
      EQ2/source/WorldServer/zoneserver.h
  35. 13 0
      EQ2/source/common/MiscFunctions.h

+ 7 - 4
EQ2/source/WorldServer/Combat.cpp

@@ -840,7 +840,7 @@ Skill* Entity::GetSkillByWeaponType(int8 type, bool update) {
 	return 0;
 }
 
-bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs) {
+bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker) {
 	if(!victim || victim->GetHP() == 0)
 		return false;
 
@@ -952,8 +952,11 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 	if(victim->IsNPC() && victim->GetHP() > 0)
 		((Entity*)victim)->AddHate(this, damage);
 
+	Entity* attacker = nullptr;
+	if(!ignore_attacker)
+		attacker = this;
 	if (damage > 0) {
-		GetZone()->SendDamagePacket(this, victim, type, hit_result, damage_type, damage, spell_name);
+		GetZone()->SendDamagePacket(attacker, victim, type, hit_result, damage_type, damage, spell_name);
 		if (IsStealthed() || IsInvis())
 			CancelAllStealth();
 
@@ -962,7 +965,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 	}
 	else if (useWards)
 	{
-		GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, DAMAGE_PACKET_RESULT_NO_DAMAGE, damage_type, 0, spell_name);
+		GetZone()->SendDamagePacket(attacker, victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, DAMAGE_PACKET_RESULT_NO_DAMAGE, damage_type, 0, spell_name);
 	}
 
 	if (victim->GetHP() <= 0)
@@ -974,7 +977,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 		else
 			victim->CheckProcs(PROC_TYPE_PHYSICAL_DEFENSIVE, this);
 	}
-
+	
 	return crit;
 }
 

+ 48 - 12
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -2172,6 +2172,14 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Success!");
 			break;
 										}
+		case COMMAND_RELOADREGIONSCRIPTS:{
+			client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Region Scripts....");
+			if(lua_interface) {
+				lua_interface->DestroyRegionScripts();
+			}
+			client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Success!");
+			break;
+										}
 		case COMMAND_RELOADLUASYSTEM:{
 			client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Attempting to reload entire LUA system....");
 			map<Client*, int32> debug_clients = lua_interface->GetDebugClients();
@@ -3015,6 +3023,20 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					else
 						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Vision Disabled!");
 				}
+				else if (strcmp(sep->arg[0], "regiondebug") == 0)
+				{
+					client->SetRegionDebug(onOff);
+					#if defined(__GNUC__)
+						database.insertCharacterProperty(client, CHAR_PROPERTY_REGIONDEBUG, (onOff) ? (char*)"1" : (char*)"0");
+					#else
+						database.insertCharacterProperty(client, CHAR_PROPERTY_REGIONDEBUG, (onOff) ? "1" : "0");
+					#endif
+
+					if (onOff)
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Enabled!");
+					else
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Disabled!");
+				}
 				else if (strcmp(sep->arg[0], "luadebug") == 0)
 				{
 					client->SetLuaDebugClient(onOff);
@@ -3576,14 +3598,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					}
 					else if (ToLower(string(sep->arg[0])) == "inwater")
 					{
-						glm::vec3 targPosZ(cmdTarget->GetX(), cmdTarget->GetZ(), cmdTarget->GetY());
-						float bestZ = client->GetPlayer()->FindDestGroundZ(targPosZ, cmdTarget->GetYOffset());
-						if ( bestZ == BEST_Z_INVALID )
-							bestZ = -999999.0f;
-							
-						glm::vec3 targPos(cmdTarget->GetY(), cmdTarget->GetX(), cmdTarget->GetZ());
-
-						if (client->GetCurrentZone()->regionmap == nullptr)
+						if (cmdTarget->GetRegionMap() == nullptr)
 							client->SimpleMessage(CHANNEL_COLOR_RED, "No water map for zone.");
 						else
 						{
@@ -3592,14 +3607,35 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 						}
 						break;
 					}
-					else if (ToLower(string(sep->arg[0])) == "pathto")
+					else if (ToLower(string(sep->arg[0])) == "inlava")
 					{
+						if (cmdTarget->GetRegionMap() == nullptr)
+							client->SimpleMessage(CHANNEL_COLOR_RED, "No region map for zone.");
+						else
+						{
+							bool inLava = cmdTarget->InLava();
+							client->Message(CHANNEL_COLOR_YELLOW, "%s is %s.", cmdTarget->GetName(), inLava ? "in lava" : "out of lava");
+						}
+						break;
+					}
+					else if (ToLower(string(sep->arg[0])) == "regions")
+					{
+						glm::vec3 targPos(cmdTarget->GetX(), cmdTarget->GetY(), cmdTarget->GetZ());
 
+						if (cmdTarget->GetRegionMap() == nullptr)
+							client->SimpleMessage(CHANNEL_COLOR_RED, "No region map for zone.");
+						else
+						{
+							cmdTarget->GetRegionMap()->IdentifyRegionsInGrid(client, targPos);
+						}
+						break;
+					}
+					else if (ToLower(string(sep->arg[0])) == "pathto")
+					{
 						break;
 					}
 					else if (ToLower(string(sep->arg[0])) == "pathfrom")
 					{
-
 						break;
 					}
 				}
@@ -4921,8 +4957,8 @@ void Commands::Command_Grid(Client* client)
 {
 	client->Message(CHANNEL_COLOR_YELLOW, "Your Grid ID is %u", client->GetPlayer()->appearance.pos.grid_id);
 
-	if (client->GetCurrentZone()->Grid != nullptr) {
-		int32 grid = client->GetCurrentZone()->Grid->GetGridID(client->GetPlayer());
+	if (client->GetPlayer()->GetMap() != nullptr) {
+		int32 grid = client->GetPlayer()->GetMap()->GetGrid()->GetGridID(client->GetPlayer());
 		client->Message(CHANNEL_COLOR_YELLOW, "SPGrid result is %u", grid);
 	}
 }

+ 3 - 1
EQ2/source/WorldServer/Commands/Commands.h

@@ -859,11 +859,13 @@ private:
 
 #define COMMAND_RELOAD_RULES			519
 #define COMMAND_RELOAD_TRANSPORTERS		520
+#define COMMAND_FINDSPAWN				521
 #define COMMAND_RELOAD_STARTABILITIES	522
 
-#define COMMAND_FINDSPAWN				521
 #define COMMAND_WAYPOINT				523
 
+#define COMMAND_RELOADREGIONSCRIPTS		524
+
 #define GET_AA_XML						751
 #define ADD_AA							752
 #define COMMIT_AA_PROFILE				753				

+ 1 - 1
EQ2/source/WorldServer/Entity.h

@@ -476,7 +476,7 @@ public:
 	int8			DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, bool spell);
 	float			GetDamageTypeResistPercentage(int8 damage_type);
 	Skill*			GetSkillByWeaponType(int8 type, bool update);
-	bool			DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false);
+	bool			DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false);
 	void			AddHate(Entity* attacker, sint32 hate);
 	bool			CheckInterruptSpell(Entity* attacker);
 	void			KillSpawn(Spawn* dead, int8 damage_type = 0, int16 kill_blow_type = 0);

+ 0 - 2
EQ2/source/WorldServer/LoginServer.cpp

@@ -35,8 +35,6 @@ using namespace std;
 #include <WinSock2.h>
 #include <windows.h>
 
-#define snprintf	_snprintf
-#define vsnprintf	_vsnprintf
 #define strncasecmp	_strnicmp
 #define strcasecmp	_stricmp
 #else // Pyro: fix for linux

+ 63 - 1
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -1645,7 +1645,7 @@ int EQ2Emu_lua_SetCurrentHP(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	sint32 value = lua_interface->GetSInt32Value(state, 2);
 	lua_interface->ResetFunctionStack(state);
-	if (spawn && value > 0) {
+	if (spawn) {
 		spawn->SetHP(value);
 		if (value > spawn->GetTotalHPBase())
 			spawn->SetTotalHP(value);
@@ -11044,3 +11044,65 @@ int EQ2Emu_lua_InWater(lua_State* state) {
 	}
 	return 0;
 }
+
+int EQ2Emu_lua_InLava(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	if (spawn) {
+		lua_interface->SetBooleanValue(state, spawn->InLava());
+		return 1;
+	}
+	return 0;
+}
+
+int EQ2Emu_lua_DamageSpawn(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* attacker = lua_interface->GetSpawn(state);
+	Spawn* victim = lua_interface->GetSpawn(state, 2);
+	int8 type = lua_interface->GetInt8Value(state, 3);
+	int8 dmg_type = lua_interface->GetInt8Value(state, 4);
+	int32 low_damage = lua_interface->GetInt32Value(state, 5);
+	int32 high_damage = lua_interface->GetInt32Value(state, 6);
+	string spell_name = lua_interface->GetStringValue(state, 7);
+	int8 crit_mod = lua_interface->GetInt8Value(state, 8);
+	bool is_tick = (lua_interface->GetInt8Value(state, 9) == 1);
+	bool no_calcs = (lua_interface->GetInt8Value(state, 10) == 1);
+	bool ignore_attacker = (lua_interface->GetInt8Value(state, 11) == 1);
+
+	if (!attacker) {
+		lua_interface->LogError("%s: LUA ProcDamage command error: caster is not a valid spawn", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	if (!attacker->IsEntity()) {
+		lua_interface->LogError("%s: LUA ProcDamage command error: caster is not an entity", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	if (!victim) {
+		lua_interface->LogError("%s: LUA ProcDamage command error: target is not a valid spawn", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	if (!victim->IsEntity()) {
+		lua_interface->LogError("%s: LUA ProcDamage command error: target is not an entity", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	((Entity*)attacker)->DamageSpawn((Entity*)victim, type, dmg_type, low_damage, high_damage, spell_name.c_str(), crit_mod, is_tick, no_calcs, ignore_attacker);
+	return 0;
+}
+
+int EQ2Emu_lua_IsInvulnerable(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	if (spawn) {
+		lua_interface->SetBooleanValue(state, spawn->GetInvulnerable());
+		return 1;
+	}
+	return 0;
+}

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

@@ -514,4 +514,9 @@ int EQ2Emu_lua_SetSpellDisplayEffect(lua_State* state);
 int EQ2Emu_lua_GetSpellDisplayEffect(lua_State* state);
 
 int EQ2Emu_lua_InWater(lua_State* state);
+int EQ2Emu_lua_InLava(lua_State* state);
+
+int EQ2Emu_lua_DamageSpawn(lua_State* state);
+
+int EQ2Emu_lua_IsInvulnerable(lua_State* state);
 #endif

+ 180 - 1
EQ2/source/WorldServer/LuaInterface.cpp

@@ -49,6 +49,7 @@ LuaInterface::LuaInterface() {
 	MItemScripts.SetName("LuaInterface::MItemScripts");
 	MSpellDelete.SetName("LuaInterface::MSpellDelete");
 	MCustomSpell.SetName("LuaInterface::MCustomSpell");
+	MRegionScripts.SetName("LuaInterface::MRegionScripts");
 	user_data_timer = new Timer(20000);
 	user_data_timer->Start();
 	spell_delete_timer = new Timer(5000);
@@ -117,6 +118,7 @@ LuaInterface::~LuaInterface() {
 	DestroyQuests();
 	DestroyItemScripts();
 	DestroyZoneScripts();
+	DestroyRegionScripts();
 	DeleteUserDataPtrs(true);
 	DeletePendingSpells(true);
 	safe_delete(user_data_timer);
@@ -225,6 +227,25 @@ void LuaInterface::DestroyZoneScripts()  {
 	MZoneScripts.releasewritelock(__FUNCTION__, __LINE__);
 }
 
+void LuaInterface::DestroyRegionScripts()  {
+	map<string, map<lua_State*, bool> >::iterator itr;
+	map<lua_State*, bool>::iterator state_itr;
+	Mutex* mutex = 0;
+	MRegionScripts.writelock(__FUNCTION__, __LINE__);
+	for (itr = region_scripts.begin(); itr != region_scripts.end(); itr++){
+		mutex = GetRegionScriptMutex(itr->first.c_str());
+		mutex->writelock(__FUNCTION__, __LINE__);
+		for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++)
+			lua_close(state_itr->first);
+		mutex->releasewritelock(__FUNCTION__, __LINE__);
+		safe_delete(mutex);
+	}
+	region_scripts.clear();
+	region_inverse_scripts.clear();
+	region_scripts_mutex.clear();
+	MRegionScripts.releasewritelock(__FUNCTION__, __LINE__);
+}
+
 void LuaInterface::ReloadSpells() {
 	DestroySpells();
 	database.LoadSpellScriptData();
@@ -319,6 +340,20 @@ bool LuaInterface::LoadZoneScript(const char* name)  {
 	return ret;
 }
 
+bool LuaInterface::LoadRegionScript(const char* name)  {
+	bool ret = false;
+	if (name) {
+		lua_State* state = LoadLuaFile(name);
+		if (state) {
+			MRegionScripts.writelock(__FUNCTION__, __LINE__);
+			region_scripts[name][state] = false;
+			MRegionScripts.releasewritelock(__FUNCTION__, __LINE__);
+			ret = true;
+		}
+	}
+	return ret;
+}
+
 void LuaInterface::ProcessErrorMessage(const char* message) {
 	MDebugClients.lock();
 	vector<Client*> delete_clients;
@@ -459,6 +494,16 @@ const char* LuaInterface::GetScriptName(lua_State* state)
 		return scriptName;
 	}
 	MZoneScripts.releasewritelock(__FUNCTION__, __LINE__);
+	
+	MRegionScripts.writelock(__FUNCTION__, __LINE__);
+	itr = region_inverse_scripts.find(state);
+	if (itr != region_inverse_scripts.end())
+	{
+		const char* scriptName = itr->second.c_str();
+		MRegionScripts.releasewritelock(__FUNCTION__, __LINE__);
+		return scriptName;
+	}
+	MRegionScripts.releasewritelock(__FUNCTION__, __LINE__);
 
 	MSpells.lock();
 	LuaSpell* spell = GetCurrentSpell(state);
@@ -481,6 +526,10 @@ bool LuaInterface::LoadZoneScript(string name) {
 	return LoadZoneScript(name.c_str());
 }
 
+bool LuaInterface::LoadRegionScript(string name) {
+	return LoadRegionScript(name.c_str());
+}
+
 void LuaInterface::AddSpawnPointers(LuaSpell* spell, bool first_cast, bool precast, const char* function, SpellScriptTimer* timer, bool passLuaSpell) {
 	if (function)
 		lua_getglobal(spell->state, function);
@@ -511,13 +560,14 @@ void LuaInterface::AddSpawnPointers(LuaSpell* spell, bool first_cast, bool preca
 	if (temp_spawn)
 		SetSpawnValue(spell->state, temp_spawn);
 	else {
+		if(spell->caster && spell->initial_target)
 		if(spell->caster && spell->initial_target)
 		{
 			// easier to debug target id as ptr
 			Spawn* new_target = spell->caster->GetZone()->GetSpawnByID(spell->initial_target);
 			SetSpawnValue(spell->state, new_target);
 		}
-		else if(spell->caster && spell->caster->GetTarget())
+				else if(spell->caster && spell->caster->GetTarget())
 			SetSpawnValue(spell->state, spell->caster->GetTarget());
 		else
 			SetSpawnValue(spell->state, 0);
@@ -602,6 +652,32 @@ bool LuaInterface::CallZoneScript(lua_State* state, int8 num_parameters) {
 	return true;
 }
 
+bool LuaInterface::CallRegionScript(lua_State* state, int8 num_parameters, int32* returnValue) {
+	if(shutting_down)
+		return false;
+	if (!state || lua_pcall(state, num_parameters, 1, 0) != 0) {
+		if (state){
+			const char* err = lua_tostring(state, -1);
+			LogError("%s: %s", GetScriptName(state), err);
+			lua_pop(state, 1);
+		}
+		return false;
+	}
+	
+	int32 result = 0;
+	
+	if (lua_isnumber(state, -1))
+	{
+		result = (int32)lua_tonumber(state, -1);
+		lua_pop(state, 1);
+	}
+	
+	if(returnValue)
+		*returnValue = result;
+	
+	return true;
+}
+
 lua_State* LuaInterface::LoadLuaFile(const char* name) {
 	if(shutting_down)
 		return 0;
@@ -1154,6 +1230,10 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "GetSpellDisplayEffect", EQ2Emu_lua_GetSpellDisplayEffect);
 
 	lua_register(state, "InWater", EQ2Emu_lua_InWater);
+	lua_register(state, "InLava", EQ2Emu_lua_InLava);
+	
+	lua_register(state, "DamageSpawn", EQ2Emu_lua_DamageSpawn);
+	lua_register(state, "IsInvulnerable", EQ2Emu_lua_IsInvulnerable);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {
@@ -1617,6 +1697,17 @@ Mutex* LuaInterface::GetZoneScriptMutex(const char* name) {
 	return mutex;
 }
 
+Mutex* LuaInterface::GetRegionScriptMutex(const char* name) {
+	Mutex* mutex = 0;
+	if(region_scripts_mutex.count(name) > 0)
+		mutex = region_scripts_mutex[name];
+	if(!mutex){
+		mutex = new Mutex();
+		region_scripts_mutex[name] = mutex;
+	}
+	return mutex;
+}
+
 void LuaInterface::UseItemScript(const char* name, lua_State* state, bool val) {
 	MItemScripts.writelock(__FUNCTION__, __LINE__);
 	item_scripts[name][state] = val;
@@ -1639,6 +1730,14 @@ void LuaInterface::UseZoneScript(const char* name, lua_State* state, bool val) {
 	MZoneScripts.releasewritelock(__FUNCTION__, __LINE__);
 }
 
+void LuaInterface::UseRegionScript(const char* name, lua_State* state, bool val) {
+
+	MRegionScripts.writelock(__FUNCTION__, __LINE__);
+	region_scripts[name][state] = val;
+	region_inverse_scripts[state] = name;
+	MRegionScripts.releasewritelock(__FUNCTION__, __LINE__);
+}
+
 lua_State* LuaInterface::GetItemScript(const char* name, bool create_new, bool use) {
 	map<string, map<lua_State*, bool> >::iterator itr;
 	map<lua_State*, bool>::iterator item_script_itr;
@@ -1743,6 +1842,40 @@ lua_State* LuaInterface::GetZoneScript(const char* name, bool create_new, bool u
 	return ret;
 }
 
+lua_State* LuaInterface::GetRegionScript(const char* name, bool create_new, bool use)  {
+	map<string, map<lua_State*, bool> >::iterator itr;
+	map<lua_State*, bool>::iterator region_script_itr;
+	lua_State* ret = 0;
+	Mutex* mutex = 0;
+
+	itr = region_scripts.find(name);
+	if(itr != region_scripts.end()) {
+		mutex = GetRegionScriptMutex(name);
+		mutex->readlock(__FUNCTION__, __LINE__);
+		for(region_script_itr = itr->second.begin(); region_script_itr != itr->second.end(); region_script_itr++){
+			if(!region_script_itr->second){ //not in use
+				ret = region_script_itr->first;
+
+				if (use)
+				{
+					region_script_itr->second = true;
+					break; // don't keep iterating, we already have our result
+				}
+			}
+		}
+		mutex->releasereadlock(__FUNCTION__, __LINE__);
+	}
+	if(!ret && create_new){
+		if(name && LoadRegionScript(name))
+			ret = GetRegionScript(name);
+		else{
+			LogError("Error LUA Zone Script '%s'", name);
+			return 0;
+		}
+	}
+	return ret;
+}
+
 bool LuaInterface::RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn) {
 	if(!item)
 		return false;
@@ -1896,6 +2029,52 @@ bool LuaInterface::RunZoneScript(string script_name, const char* function_name,
 		return false;
 }
 
+
+bool LuaInterface::RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, sint32 int32_arg1, int32* returnValue) {
+	if (!zone)
+		return false;
+	lua_State* state = GetRegionScript(script_name.c_str(), true, true);
+	if (state) {
+		Mutex* mutex = GetRegionScriptMutex(script_name.c_str());
+		if (mutex)
+			mutex->readlock(__FUNCTION__, __LINE__);
+		else {
+			LogError("Error getting lock for '%s'", script_name.c_str());
+			UseRegionScript(script_name.c_str(), state, false);
+			return false;
+		}
+		lua_getglobal(state, function_name);
+		if (!lua_isfunction(state, lua_gettop(state))) {
+			lua_pop(state, 1);
+			mutex->releasereadlock(__FUNCTION__);
+			UseRegionScript(script_name.c_str(), state, false);
+			return false;
+		}
+		SetZoneValue(state, zone);
+		int8 num_params = 1;
+		if (spawn) {
+			SetSpawnValue(state, spawn);
+			num_params++;
+		}
+		if (int32_arg1 > 0) {
+			SetSInt32Value(state, int32_arg1);
+			num_params++;
+		}
+		if (!CallRegionScript(state, num_params, returnValue)) {
+			if (mutex)
+				mutex->releasereadlock(__FUNCTION__, __LINE__);
+			UseRegionScript(script_name.c_str(), state, false);
+			return false;
+		}
+		if (mutex)
+			mutex->releasereadlock(__FUNCTION__, __LINE__);
+		UseRegionScript(script_name.c_str(), state, false);
+		return true;
+	}
+	else
+		return false;
+}
+
 void LuaInterface::AddPendingSpellDelete(LuaSpell* spell) {
 	MSpellDelete.lock();
 	if ( spells_pending_delete.count(spell) == 0 )

+ 14 - 1
EQ2/source/WorldServer/LuaInterface.h

@@ -188,6 +188,8 @@ public:
 	bool			LoadSpawnScript(const char* name);
 	bool			LoadZoneScript(string name);
 	bool			LoadZoneScript(const char* name);
+	bool			LoadRegionScript(string name);
+	bool			LoadRegionScript(const char* name);
 	void			RemoveSpell(LuaSpell* spell, bool call_remove_function = true, bool can_delete = true, string reason = "");
 	Spawn*			GetSpawn(lua_State* state, int8 arg_num = 1);
 	Item*			GetItem(lua_State* state, int8 arg_num = 1);
@@ -230,9 +232,11 @@ public:
 	void			UseItemScript(const char* name, lua_State* state, bool val);
 	void			UseSpawnScript(const char* name, lua_State* state, bool val);
 	void			UseZoneScript(const char* name, lua_State* state, bool val);
+	void			UseRegionScript(const char* name, lua_State* state, bool val);
 	lua_State*		GetItemScript(const char* name, bool create_new = true, bool use = false);
 	lua_State*		GetSpawnScript(const char* name, bool create_new = true, bool use = false);
 	lua_State*		GetZoneScript(const char* name, bool create_new = true, bool use = false);
+	lua_State*		GetRegionScript(const char* name, bool create_new = true, bool use = false);
 	Quest*			LoadQuest(int32 id, const char* name, const char* type, const char* zone, int8 level, const char* description, char* script_name);
 
 	const char*		GetScriptName(lua_State* state);
@@ -244,6 +248,8 @@ public:
 	bool			CallSpawnScript(lua_State* state, int8 num_parameters);
 	bool			RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, int32 int32_arg1 = 0, const char* str_arg1 = 0, Spawn* spawn_arg1 = 0, int32 int32_arg2 = 0, const char* str_arg2 = 0, Spawn* spawn_arg2 = 0);
 	bool			CallZoneScript(lua_State* state, int8 num_parameters);
+	bool			RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, sint32 int32_arg1 = 0, int32* returnValue = 0);
+	bool			CallRegionScript(lua_State* state, int8 num_parameters, int32* returnValue);
 	void			ResetFunctionStack(lua_State* state);
 	void			DestroySpells();
 	void			DestroySpawnScripts();
@@ -251,6 +257,7 @@ public:
 	void			ReloadSpells();
 	void			DestroyQuests(bool reload = false);
 	void			DestroyZoneScripts();
+	void			DestroyRegionScripts();
 	void			SimpleLogError(const char* error);
 	void			LogError(const char* error, ...);
 
@@ -267,6 +274,7 @@ public:
 	Mutex*			GetSpawnScriptMutex(const char* name);
 	Mutex*			GetItemScriptMutex(const char* name);
 	Mutex*			GetZoneScriptMutex(const char* name);
+	Mutex*			GetRegionScriptMutex(const char* name);
 	Mutex*			GetQuestMutex(Quest* quest);
 
 	void			SetSpawnScriptsReloading(bool val) { spawn_scripts_reloading = val; }
@@ -301,6 +309,7 @@ private:
 	map<string, map<lua_State*, bool> > item_scripts;
 	map<string, map<lua_State*, bool> > spawn_scripts;
 	map<string, map<lua_State*, bool> > zone_scripts;
+	map<string, map<lua_State*, bool> > region_scripts;
 
 	map<int32, LuaSpell*> custom_spells;
 	std::deque<int32> custom_free_spell_ids;
@@ -308,11 +317,14 @@ private:
 	map<lua_State*, string> item_inverse_scripts;
 	map<lua_State*, string> spawn_inverse_scripts;
 	map<lua_State*, string> zone_inverse_scripts;
+	map<lua_State*, string> region_inverse_scripts;
 
 	map<string, Mutex*> item_scripts_mutex;
 	map<string, Mutex*> spawn_scripts_mutex;
 	map<string, Mutex*> zone_scripts_mutex;
-	map<int32, Mutex*> quests_mutex;	
+	map<int32, Mutex*> quests_mutex;
+	map<string, Mutex*> region_scripts_mutex;
+
 	Mutex			MDebugClients;
 	Mutex			MSpells;
 	Mutex			MSpawnScripts;
@@ -323,5 +335,6 @@ private:
 	Mutex			MLUAMain;
 	Mutex			MSpellDelete;
 	Mutex			MCustomSpell;
+	Mutex			MRegionScripts;
 };
 #endif

+ 2 - 2
EQ2/source/WorldServer/NPC.cpp

@@ -879,8 +879,8 @@ Entity*	NPC::GetOwner() {
 	return (Entity*)GetZone()->GetSpawnByID(owner);
 }
 
-void NPC::SetZone(ZoneServer* in_zone) {
-	Spawn::SetZone(in_zone);
+void NPC::SetZone(ZoneServer* in_zone, int32 version) {
+	Spawn::SetZone(in_zone, version);
 	if (in_zone){
 		GetZone()->SetNPCEquipment(this);
 		SetSkills(GetZone()->GetNPCSkills(primary_skill_list, secondary_skill_list));

+ 1 - 1
EQ2/source/WorldServer/NPC.h

@@ -118,7 +118,7 @@ public:
 	void	ClearRunback();
 	void	AddSkillBonus(int32 spell_id, int32 skill_id, float value);
 	virtual void RemoveSkillBonus(int32 spell_id);
-	virtual void SetZone(ZoneServer* zone);
+	virtual void SetZone(ZoneServer* zone, int32 version=0);
 
 	void	SetOwner(Entity* owner) { if (owner) { this->owner = owner->GetID(); } else { owner = 0; } }
 	Entity*	GetOwner();

+ 124 - 49
EQ2/source/WorldServer/Spawn.cpp

@@ -116,6 +116,9 @@ Spawn::Spawn(){
 	has_quests_required = false;
 	is_flying_creature = false;
 	is_water_creature = false;
+	region_map = nullptr;
+	current_map = nullptr;
+	RegionMutex.SetName("Spawn::RegionMutex");
 }
 
 Spawn::~Spawn(){
@@ -169,6 +172,8 @@ Spawn::~Spawn(){
 
 	// just in case to make sure data is destroyed
 	RemoveSpawnProximities();
+	
+	Regions.clear();
 }
 
 void Spawn::RemovePrimaryCommands()
@@ -797,7 +802,6 @@ uchar* Spawn::spawn_pos_changes(Player* player, int16 version) {
 		return nullptr;
 	}
 
-
 	uchar* tmp;
 	if (IsPlayer() && version > 283)
 		tmp = new uchar[size + 14];
@@ -1325,8 +1329,19 @@ ZoneServer*	Spawn::GetZone(){
 	return zone;
 }
 
-void Spawn::SetZone(ZoneServer* in_zone){
+void Spawn::SetZone(ZoneServer* in_zone, int32 version){
 	zone = in_zone;
+	
+	if(in_zone)
+	{
+		region_map = world.GetRegionMap(std::string(in_zone->GetZoneFile()), version);
+		current_map = world.GetMap(std::string(in_zone->GetZoneFile()), version);
+	}
+	else
+	{
+		region_map = nullptr;
+		current_map = nullptr;
+	}
 }
 
 
@@ -1350,6 +1365,9 @@ void Spawn::SetHP(sint32 new_val, bool setUpdateFlags){
 			world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id);
 	}
 
+	if ( IsPlayer() && new_val == 0 ) // fixes on death not showing hp update for players
+		((Player*)this)->SetCharSheetChanged(true);
+
 	if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner()->IsPlayer()) {
 		Player* player = (Player*)((NPC*)this)->GetOwner();
 		if (player->GetPet() && player->GetCharmedPet()) {
@@ -1977,7 +1995,24 @@ int32 Spawn::GetTransporterID(){
 
 void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool bSpawnUpdate) {
 	int16 version = packet->GetVersion();
-	packet->setDataByName("pos_grid_id", appearance.pos.grid_id);
+
+	int32 new_grid_id = 0;
+	if(player->GetMap() != nullptr)
+	{
+		std::map<int32,TimedGridData>::iterator itr = established_grid_id.find(version);
+		if ( itr == established_grid_id.end() || itr->second.timestamp <= (Timer::GetCurrentTime2()))
+		{
+			new_grid_id = player->GetMap()->GetGrid()->GetGridIDByLocation(GetX(),GetY(),GetZ());
+			TimedGridData data;
+			data.grid_id = new_grid_id;
+			data.timestamp = Timer::GetCurrentTime2()+100;
+			established_grid_id.insert(make_pair(packet->GetVersion(), data));
+		}
+		else
+			new_grid_id = itr->second.grid_id;
+	}
+	
+	packet->setDataByName("pos_grid_id", new_grid_id != 0 ? new_grid_id : appearance.pos.grid_id);
 	bool include_heading = true;
 	if (IsWidget() && ((Widget*)this)->GetIncludeHeading() == false)
 		include_heading = false;
@@ -2661,11 +2696,11 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 		return;
 	}
 
-	if (forceMapCheck && GetZone() != nullptr && zone->zonemap != nullptr && zone->zonemap->IsMapLoaded())
+	if (forceMapCheck && GetZone() != nullptr && GetMap() != nullptr && GetMap()->IsMapLoaded())
 	{
 		FixZ(true);
 
-		int32 newGrid = GetZone()->Grid->GetGridID(this);
+		int32 newGrid = GetMap()->GetGrid()->GetGridID(this);
 		if (!IsFlyingCreature() && newGrid != 0 && newGrid != appearance.pos.grid_id)
 			SetPos(&(appearance.pos.grid_id), newGrid);
 
@@ -3082,14 +3117,9 @@ bool Spawn::CalculateChange(){
 					SetY(ny + tar_vy, false);
 			}
 
-			if (GetZone()->Grid != nullptr) {
-				Cell* newCell = GetZone()->Grid->GetCell(GetX(), GetZ());
-				if (newCell != Cell_Info.CurrentCell) {
-					GetZone()->Grid->RemoveSpawnFromCell(this);
-					GetZone()->Grid->AddSpawn(this, newCell);
-				}
-
-				int32 newGrid = GetZone()->Grid->GetGridID(this);
+			if (GetMap() != nullptr) {
+				Cell* newCell = GetMap()->GetGrid()->GetCell(GetX(), GetZ());
+				int32 newGrid = GetMap()->GetGrid()->GetGridID(this);
 				if (!IsFlyingCreature() && newGrid != 0 && newGrid != appearance.pos.grid_id)
 					SetPos(&(appearance.pos.grid_id), newGrid);
 			}
@@ -3540,10 +3570,10 @@ void Spawn::SetY(float y, bool updateFlags, bool disableYMapCheck)
 float Spawn::FindDestGroundZ(glm::vec3 dest, float z_offset)
 {
 	float best_z = BEST_Z_INVALID;
-	if (GetZone() != nullptr && GetZone()->zonemap != nullptr)
+	if (GetZone() != nullptr && GetMap() != nullptr)
 	{
 		dest.z += z_offset;
-		best_z = zone->zonemap->FindBestZ(dest, nullptr);
+		best_z = GetMap()->FindBestZ(dest, nullptr);
 	}
 	return best_z;
 }
@@ -3554,7 +3584,7 @@ float Spawn::GetFixedZ(const glm::vec3& destination, int32 z_find_offset) {
 
 	float new_z = destination.z;
 
-	if (GetZone() != nullptr && zone->zonemap != nullptr) {
+	if (GetZone() != nullptr && GetMap() != nullptr) {
 
 /*		if (flymode == GravityBehavior::Flying)
 			return new_z;
@@ -3603,20 +3633,11 @@ void Spawn::FixZ(bool forceUpdate) {
 	glm::vec3 current_loc(GetX(), GetZ(), GetY());
 	float new_z = GetFixedZ(current_loc, 1);
 	
-	if ( GetZone()->regionmap != nullptr )
+	if ( region_map != nullptr )
 	{
-		glm::vec3 targPos(GetY(), GetX(), GetZ());
-		
-		if ( IsGroundSpawn() )
-			targPos.x -= 1.0f;
-		else
-			targPos.x -= .5f; // standard offset to better assess shallow water
-			
-		float bestZ = -999999.0f;
-		if ( new_z != BEST_Z_INVALID )
-			bestZ = new_z - 1.0f;
+		glm::vec3 targPos(GetX(), GetY(), GetZ());
 		
-		if(GetZone()->regionmap->InWater(targPos, bestZ))
+		if(region_map->InWater(targPos, appearance.pos.grid_id))
 			return;
 	}
 	
@@ -3644,10 +3665,10 @@ bool Spawn::CheckLoS(Spawn* target)
 bool Spawn::CheckLoS(glm::vec3 myloc, glm::vec3 oloc)
 {
 	ZoneServer* zone = GetZone();
-	if (zone == NULL || zone->zonemap == NULL || !zone->zonemap->IsMapLoaded())
+	if (zone == NULL || GetMap() == NULL || !GetMap()->IsMapLoaded())
 		return true;
 	else
-		return zone->zonemap->CheckLoS(myloc, oloc);
+		return GetMap()->CheckLoS(myloc, oloc);
 
 	return false;
 }
@@ -3832,28 +3853,82 @@ bool Spawn::InWater()
 {
 	bool inWater = false;
 
-	if (GetZone()->regionmap != nullptr)
-	{
-		glm::vec3 current_loc(GetX(), GetZ(), GetY());
-		float new_z = GetFixedZ(current_loc, 1);
-				
-		if ( GetZone()->regionmap != nullptr )
+		if ( region_map != nullptr )
 		{
-			glm::vec3 targPos(GetY(), GetX(), GetZ());
+			glm::vec3 targPos(GetX(), GetY(), GetZ());
 			if ( IsGroundSpawn() )
-				targPos.x -= 1.0f;
-			else
-				targPos.x -= .5f; // standard offset to better assess shallow water
-				
-			
-			float bestZ = -999999.0f;
-			if ( new_z != BEST_Z_INVALID )
-				bestZ = new_z;
+				targPos.y -= .5f;
 					
-			if(GetZone()->regionmap->InWater(targPos, bestZ))
-			inWater = true;
+			if(region_map->InWater(targPos, appearance.pos.grid_id))
+				inWater = true;
 		}
-	}
 
 	return inWater;
-}
+}
+
+bool Spawn::InLava()
+{
+	bool inLava = false;
+
+		if ( region_map != nullptr )
+		{
+			glm::vec3 targPos(GetX(), GetY(), GetZ());
+			if ( IsGroundSpawn() )
+				targPos.y -= .5f;
+					
+			if(region_map->InLava(targPos, appearance.pos.grid_id))
+				inLava = true;
+		}
+
+	return inLava;
+}
+
+void Spawn::DeleteRegion(Region_Node* inNode, ZBSP_Node* rootNode)
+{
+	map<map<Region_Node*, ZBSP_Node*>, Region_Status>::iterator testitr;
+	for (testitr = Regions.begin(); testitr != 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;
+		if(inNode == node && rootNode == BSP_Root )
+		{
+			testitr = Regions.erase(testitr);
+			break;
+		}
+	}
+}
+
+bool Spawn::InRegion(Region_Node* inNode, ZBSP_Node* rootNode)
+{
+	map<map<Region_Node*, ZBSP_Node*>, Region_Status>::iterator testitr;
+	for (testitr = Regions.begin(); testitr != 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;
+		if(inNode == node && rootNode == BSP_Root )
+		{
+			return testitr->second.inRegion;
+		}
+	}
+		
+	return false;
+}
+
+int32 Spawn::GetRegionType(Region_Node* inNode, ZBSP_Node* rootNode)
+{
+	map<map<Region_Node*, ZBSP_Node*>, Region_Status>::iterator testitr;
+	for (testitr = Regions.begin(); testitr != 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;
+		if(inNode == node && rootNode == BSP_Root )
+		{
+			return testitr->second.regionType;
+		}
+	}
+	
+	return false;
+}

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

@@ -33,6 +33,8 @@
 #include "../common/ConfigReader.h"
 #include "Items/Items.h"
 #include "Zone/map.h"
+#include "Zone/region_map.h"
+#include "Zone/region_map_v1.h"
 #include "../common/Mutex.h"
 #include "MutexList.h"
 #include <deque>
@@ -231,6 +233,11 @@ struct SpawnData {
 	int32 size;
 };
 
+struct TimedGridData {
+	int32 timestamp;
+	int32 grid_id;
+};
+
 class Spawn {
 public:
 	Spawn();
@@ -773,6 +780,7 @@ public:
 	bool IsFlyingCreature() { return is_flying_creature; }
 	bool IsWaterCreature() { return is_water_creature; }
 	bool InWater();
+	bool InLava();
 
 	void SetFlyingCreature() {
 		is_flying_creature = false;
@@ -923,7 +931,7 @@ public:
 	bool			TakeDamage(int32 damage);
 	void			TakeDamage(Spawn* attacker, int32 damage);
 	ZoneServer*		GetZone();
-	virtual void	SetZone(ZoneServer* in_zone);
+	virtual void	SetZone(ZoneServer* in_zone, int32 version=0);
 	void			SetFactionID(int32 val) { faction_id = val; }
 	int32			GetFactionID(){
 		return faction_id;
@@ -1174,6 +1182,17 @@ public:
 
 	bool	IsSoundsDisabled() { return disable_sounds; }
 	void	SetSoundsDisabled(bool val) { disable_sounds = val; }
+
+	RegionMap* GetRegionMap() { return region_map; }
+	Map* GetMap() { return current_map; }
+	std::map<int32,TimedGridData> established_grid_id;
+	
+	void DeleteRegion(Region_Node* inNode, ZBSP_Node* rootNode);
+	bool InRegion(Region_Node* inNode, ZBSP_Node* rootNode);
+	int32 GetRegionType(Region_Node* inNode, ZBSP_Node* rootNode);
+	
+	std::map<std::map<Region_Node*, ZBSP_Node*>, Region_Status> Regions;
+	Mutex RegionMutex;
 protected:
 
 	bool	has_quests_required;
@@ -1265,6 +1284,9 @@ private:
 	Mutex m_Update;
 
 	bool disable_sounds;
+
+	RegionMap* region_map;
+	Map* current_map;
 };
 
 #endif

+ 1 - 13
EQ2/source/WorldServer/VisualStates.h

@@ -18,6 +18,7 @@
     along with EQ2Emulator.  If not, see <http://www.gnu.org/licenses/>.
 */
 #include "../common/Log.h"
+#include "../../common/MiscFunctions.h"
 #include <map>
 
 using namespace std;
@@ -74,19 +75,6 @@ private:
 	string message;
 	string targeted_message;
 };
-class VersionRange {
-public:
-	VersionRange(int32 in_min_version, int32 in_max_version)
-	{
-		min_version = in_min_version;
-		max_version = in_max_version;
-	}
-	int32 GetMinVersion() { return min_version; }
-	int32 GetMaxVersion() { return max_version; }
-private:
-	int32 min_version;
-	int32 max_version;
-};
 
 class EmoteVersionRange {
 public:

+ 78 - 0
EQ2/source/WorldServer/World.cpp

@@ -49,6 +49,8 @@
 #include "HeroicOp/HeroicOp.h"
 #include "RaceTypes/RaceTypes.h"
 
+#include <boost/algorithm/string.hpp>
+
 MasterQuestList master_quest_list;
 MasterItemList master_item_list;
 MasterSpellList master_spell_list;
@@ -128,6 +130,14 @@ World::~World(){
 	tov_itemstat_conversion.clear();
 
 	PurgeStartingLists();
+
+	map<std::string, RegionMapRange*>::iterator itr3;
+	for (itr3 = region_maps.begin(); itr3 != region_maps.end(); itr3++)
+		safe_delete(itr3->second);
+	
+	map<std::string, MapRange*>::iterator itr4;
+	for (itr4 = maps.begin(); itr4 != maps.end(); itr4++)
+		safe_delete(itr4->second);
 }
 
 void World::init(){
@@ -2378,3 +2388,71 @@ void ZoneList::WatchdogHeartbeat()
 		MZoneList.releasewritelock(__FUNCTION__, __LINE__);
 }
 
+void World::LoadRegionMaps(std::string zoneFile)
+{
+	string zoneToLower(zoneFile);
+	boost::algorithm::to_lower(zoneToLower);
+
+	std::map<std::string, RegionMapRange*>::iterator itr;
+	itr = region_maps.find(zoneToLower);
+	if (itr == region_maps.end())
+	{
+		RegionMapRange* newRange = new RegionMapRange();
+		newRange->AddVersionRange(zoneFile);
+
+		region_maps.insert(make_pair(zoneToLower, newRange));
+	}
+}
+
+RegionMap* World::GetRegionMap(std::string zoneFile, int32 client_version)
+{
+	string zoneToLower(zoneFile);
+	boost::algorithm::to_lower(zoneToLower);
+
+	std::map<std::string, RegionMapRange*>::iterator itr;
+	itr = region_maps.find(zoneToLower);
+	if ( itr != region_maps.end())
+	{
+		std::map<VersionRange*, RegionMap*>::iterator rmitr;
+		rmitr = itr->second->FindRegionByVersion(client_version);
+		if ( rmitr != itr->second->GetRangeEnd())
+			return rmitr->second;
+	}
+
+	return nullptr;
+}
+
+
+void World::LoadMaps(std::string zoneFile)
+{
+	string zoneToLower(zoneFile);
+	boost::algorithm::to_lower(zoneToLower);
+
+	std::map<std::string, MapRange*>::iterator itr;
+	itr = maps.find(zoneToLower);
+	if (itr == maps.end())
+	{
+		MapRange* newRange = new MapRange();
+		newRange->AddVersionRange(zoneFile);
+
+		maps.insert(make_pair(zoneToLower, newRange));
+	}
+}
+
+Map* World::GetMap(std::string zoneFile, int32 client_version)
+{
+	string zoneToLower(zoneFile);
+	boost::algorithm::to_lower(zoneToLower);
+
+	std::map<std::string, MapRange*>::iterator itr;
+	itr = maps.find(zoneToLower);
+	if ( itr != maps.end())
+	{
+		std::map<VersionRange*, Map*>::iterator rmitr;
+		rmitr = itr->second->FindMapByVersion(client_version);
+		if ( rmitr != itr->second->GetRangeEnd())
+			return rmitr->second;
+	}
+
+	return nullptr;
+}

+ 11 - 0
EQ2/source/WorldServer/World.h

@@ -36,6 +36,9 @@
 
 #include "PlayerGroups.h"
 
+#include "./Zone/region_map.h"
+#include "./Zone/map.h"
+
 using namespace std;
 struct MerchantInfo{
 	vector<int32> inventory_ids;
@@ -623,6 +626,11 @@ public:
 	void SetSuppressedWarning() { suppressed_warning = Timer::GetCurrentTime2(); }
 	map<string, int32> GetOldestReloadingSubsystem();
 
+	void LoadRegionMaps(std::string zoneFile);
+	RegionMap* GetRegionMap(std::string zoneFile, int32 client_version);
+	
+	void LoadMaps(std::string zoneFile);
+	Map* GetMap(std::string zoneFile, int32 client_version);
 private:
 	int32 suppressed_warning = 0;
 	map<string, int32> reloading_subsystems;
@@ -681,5 +689,8 @@ private:
 
 	Mutex				MBugReport;
 	map<sint32, bool> bug_report_crc;
+
+	std::map<std::string, RegionMapRange*> region_maps;
+	std::map<std::string, MapRange*> maps;
 };
 #endif

+ 10 - 2
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -1776,6 +1776,14 @@ bool WorldDatabase::loadCharacterProperties(Client* client) {
 			if (val)
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Vision Enabled!");
 		}
+		else if (!stricmp(prop_name, CHAR_PROPERTY_REGIONDEBUG))
+		{
+			int8 val = atoi(prop_value);
+			
+			client->SetRegionDebug(val == 1);
+			if (val)
+				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Enabled!");
+		}
 		else if (!stricmp(prop_name, CHAR_PROPERTY_LUADEBUG))
 		{
 			int8 val = atoi(prop_value);
@@ -6877,9 +6885,9 @@ void WorldDatabase::GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn)
 		spawn->SetPickupItemID(result.GetInt32(0));
 		spawn->SetPickupUniqueItemID(result.GetInt32(1));
 
-		if (spawn->GetZone() != nullptr && spawn->GetZone()->zonemap != nullptr && spawn->GetZone()->zonemap->IsMapLoaded())
+		if (spawn->GetZone() != nullptr && spawn->GetMap() != nullptr && spawn->GetMap()->IsMapLoaded())
 		{
-			int32 newGrid = spawn->GetZone()->Grid->GetGridID(spawn);
+			int32 newGrid = spawn->GetMap()->GetGrid()->GetGridID(spawn);
 			spawn->SetPos(&(spawn->appearance.pos.grid_id), newGrid);
 		}
 	}

+ 1 - 0
EQ2/source/WorldServer/WorldDatabase.h

@@ -105,6 +105,7 @@ using namespace std;
 #define CHAR_PROPERTY_SPEED			"modify_speed"
 #define CHAR_PROPERTY_FLYMODE		"modify_flymode"
 #define CHAR_PROPERTY_INVUL			"modify_invul"
+#define CHAR_PROPERTY_REGIONDEBUG	"modify_regiondebug"
 #define CHAR_PROPERTY_GMVISION		"modify_gmvision"
 #define CHAR_PROPERTY_LUADEBUG		"modify_luadebug"
 

+ 52 - 23
EQ2/source/WorldServer/Zone/SPGrid.cpp

@@ -393,36 +393,65 @@ int32 SPGrid::GetGridID(Spawn * spawn) {
 	return Grid;
 }
 
-void SPGrid::AddSpawn(Spawn * spawn) {
-	Cell* cell = GetCell(spawn->GetX(), spawn->GetZ());
-	AddSpawn(spawn, cell);
-}
+int32 SPGrid::GetGridIDByLocation(float x, float y, float z) {
+	FaceCell* cell = GetFaceCell(x, z);
+	
 
-void SPGrid::AddSpawn(Spawn * spawn, Cell * cell) {
-	cell->SpawnList.push_back(spawn);
-	spawn->Cell_Info.CurrentCell = cell;
-	spawn->Cell_Info.CellListIndex = cell->SpawnList.size() - 1;
-}
+	/*if (cell->GridBounds.size() == 1)
+		return cell->FaceList.begin()->first;*/
 
-void SPGrid::RemoveSpawnFromCell(Spawn * spawn) {
-	if (spawn->Cell_Info.CurrentCell) {
+	// Create the starting point for the trace
+	float point[3];
+	point[0] = x;
+	point[1] = y + 3.0f; // Small bump to make sure we are above ground when we do the trace
+	point[2] = z;
+
+	// Create the direction for the trace, as we want what
+	// is below it will just be -1 in the y direction
+	float direction[3];
+	direction[0] = 0.0f;
+	direction[1] = -1.0f;
+	direction[2] = 0.0f;
 
-		vector<Spawn*>& spawns = spawn->Cell_Info.CurrentCell->SpawnList;
+	float MinDistance = 0.0f;
+	int32 Grid = 0;
 
-		// Only do the vector swap if the vector has more than 1 spawn in it
-		if (spawns.size() > 1) {
-			// Swap the last spawn in this list to our position and update its stored index to match its new index
-			spawns[spawn->Cell_Info.CellListIndex] = spawns.back();
-			spawns[spawn->Cell_Info.CellListIndex]->Cell_Info.CellListIndex = spawn->Cell_Info.CellListIndex;
-		}
+	/*map<int32, GridBounds*>::iterator itr;
+	for (itr = cell->GridBounds.begin(); itr != cell->GridBounds.end(); itr++) {
+		GridBounds* bounds = (*itr).second;
 
-		// Remove the last spawn from the list which should now be the spawn passed as a parameter
-		spawns.pop_back();
+		if (point[0] >= bounds->MinBounds[0] && point[1] >= bounds->MinBounds[1] && point[2] >= bounds->MinBounds[2]
+			&& point[0] <= bounds->MaxBounds[0] && point[1] <= bounds->MaxBounds[1] && point[2] <= bounds->MaxBounds[2]) {
 
-		// Reset the spawns CellInfo to default values now that it is no longer in a cell
-		spawn->Cell_Info.CellListIndex = -1;
-		spawn->Cell_Info.CurrentCell = nullptr;
+			vector<Face*>::iterator itr2;
+			for (itr2 = cell->FaceList[(*itr).first].begin(); itr2 != cell->FaceList[(*itr).first].end(); itr2++) {
+				Face* face = *itr2;
+				float distance;
+				if ((distance = rayIntersectsTriangle(point, direction, face->Vertex1, face->Vertex2, face->Vertex3)) != 0) {
+					if (MinDistance == 0.0f || distance < MinDistance) {
+						MinDistance = distance;
+						Grid = (*itr).first;
+					}
+				}
+			}
+		}
+	}*/
+	map<int32, vector<Face*> >::iterator mapitr;
+	for (mapitr = cell->FaceList.begin(); mapitr != cell->FaceList.end(); mapitr++) {
+		vector<Face*>::iterator itr;
+		for (itr = (*mapitr).second.begin(); itr != (*mapitr).second.end(); itr++) {
+			Face* face = *itr;
+			float distance;
+			if ((distance = rayIntersectsTriangle(point, direction, face->Vertex1, face->Vertex2, face->Vertex3)) != 0) {
+				if (MinDistance == 0.0f || distance < MinDistance) {
+					MinDistance = distance;
+					Grid = (*mapitr).first;
+				}
+			}
+		}
 	}
+
+	return Grid;
 }
 
 float SPGrid::GetBestY(float x, float y, float z)

+ 1 - 9
EQ2/source/WorldServer/Zone/SPGrid.h

@@ -71,15 +71,7 @@ public:
 
 	// Checks the faces below the given spawn to determine the GridID
 	int32 GetGridID(Spawn* spawn);
-
-	// Calculate a cell from spawn's position and add the spawn to it
-	void AddSpawn(Spawn* spawn);
-
-	// Adds the spawn to a specific spell
-	void AddSpawn(Spawn* spawn, Cell* cell);
-
-	// Removes the spawn from the cell it is currently in
-	void RemoveSpawnFromCell(Spawn* spawn);
+	int32 GetGridIDByLocation(float x, float y, float z);
 
 	// Get cell based on cell coordinates
 	FaceCell* GetFaceCell(int32 x, int32 z);

+ 112 - 8
EQ2/source/WorldServer/Zone/map.cpp

@@ -15,6 +15,10 @@
 #include <vector>
 #include <fstream>
 #include <iostream>
+
+#include <boost/regex.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/foreach.hpp> 
 #include <boost/asio.hpp>
 #include <boost/iostreams/filtering_streambuf.hpp>
 #include <boost/iostreams/copy.hpp>
@@ -56,9 +60,10 @@ ThreadReturnType LoadMapAsync(void* mapToLoad)
 	THREAD_RETURN(NULL);
 }
 
-Map::Map(string file, SPGrid* grid) {
+Map::Map(string zonename, string file, SPGrid* grid) {
 	CheckMapMutex.SetName(file + "MapMutex");
 	SetMapLoaded(false);
+	m_ZoneName = zonename;
 	m_ZoneFile = file;
 	imp = nullptr;
 	mGrid = grid;
@@ -71,6 +76,7 @@ Map::~Map() {
 		imp->rm->release();
 		safe_delete(imp);
 	}
+	safe_delete(mGrid);
 }
 
 float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result)
@@ -259,7 +265,7 @@ bool Map::DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, glm::vec3 &outnorm,
 	return imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, (RmReal *)&outnorm, (RmReal *)&distance);
 }
 
-Map *Map::LoadMapFile(std::string file, SPGrid* grid) {
+Map *Map::LoadMapFile(std::string zonename, std::string file, SPGrid* grid) {
 
 	std::string filename = "Maps/";
 	filename += file;
@@ -273,7 +279,7 @@ Map *Map::LoadMapFile(std::string file, SPGrid* grid) {
 
 	LogWrite(MAP__INFO, 7, "Map", "Attempting to load Map File [{%s}]", filename.c_str());
 
-	auto m = new Map(file, grid);
+	auto m = new Map(zonename, file, grid);
 	m->SetMapLoading(true);
 	m->SetFileName(filename);
 #ifdef WIN32
@@ -343,11 +349,11 @@ bool Map::LoadV2(FILE* f) {
 	LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name);
 
 	string fileName(name);
-	std::size_t found = fileName.find(m_ZoneFile);
+	std::size_t found = fileName.find(m_ZoneName);
 	// Make sure file contents are for the correct zone
 	if (found == std::string::npos) {
 		fclose(f);
-		LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV2() map contents (%s) do not match its name (%s).", &name, m_ZoneFile.c_str());
+		LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV2() map contents (%s) do not match its name (%s).", &name, m_ZoneName.c_str());
 		return false;
 	}
 	// Read the min bounds
@@ -469,7 +475,7 @@ bool Map::LoadV2(FILE* f) {
 }
 
 bool Map::LoadV2Deflated(FILE* f) {
-    ifstream file(m_FileName.c_str(), ios_base::in | ios_base::binary);
+    std::ifstream file(m_FileName.c_str(), ios_base::in | ios_base::binary);
     boost::iostreams::filtering_streambuf<boost::iostreams::input> inbuf;
     inbuf.push(boost::iostreams::gzip_decompressor());
     inbuf.push(file);
@@ -495,7 +501,7 @@ bool Map::LoadV2Deflated(FILE* f) {
 	LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name);
 
 	string fileName(name);
-	std::size_t found = fileName.find(m_ZoneFile);
+	std::size_t found = fileName.find(m_ZoneName);
 	// Make sure file contents are for the correct zone
 	if (found == std::string::npos) {
 		file.close();
@@ -662,4 +668,102 @@ void Map::TranslateVertex(glm::vec3 &v, float tx, float ty, float tz) {
 	v.x = v.x + tx;
 	v.y = v.y + ty;
 	v.z = v.z + tz;
-}
+}
+
+void MapRange::AddVersionRange(std::string zoneName) {
+  boost::filesystem::path targetDir("Maps/");
+
+  boost::filesystem::recursive_directory_iterator iter(targetDir), eod;
+  boost::smatch base_match;
+  std::string formula = "(.*\\/|.*\\\\)((" + zoneName + ")(\\-([0-9]+)\\-([0-9]+))?)(\\.EQ2Map|\\.EQ2MapDeflated)$";
+  boost::regex re(formula.c_str());
+  LogWrite(MAP__INFO, 0, "Map", "Map Formula to match: %s", formula.c_str());
+  BOOST_FOREACH(boost::filesystem::path
+    const & i, make_pair(iter, eod)) {
+    if (is_regular_file(i)) {
+		std::string fileName(i.string());
+
+      if (boost::regex_match(fileName, base_match, re)) {
+        boost::ssub_match base_sub_match = base_match[2];
+        boost::ssub_match base_sub_match2 = base_match[5];
+		boost::ssub_match base_sub_match3 = base_match[6];
+		std::string baseMatch(base_sub_match.str().c_str());
+		std::string baseMatch2(base_sub_match2.str().c_str());
+		std::string baseMatch3(base_sub_match3.str().c_str());
+        LogWrite(MAP__INFO, 0, "Map", "Map To Load: %s, size: %i, string: %s, min: %s, max: %s\n", i.string().c_str(), base_match.size(), baseMatch.c_str(), baseMatch2.c_str(), baseMatch3.c_str());
+
+        SPGrid * Grid = new SPGrid(base_sub_match.str().c_str(), 0);
+        Map * zonemap = Map::LoadMapFile(zoneName, base_sub_match.str().c_str(), Grid);
+
+        int32 min_version = 0, max_version = 0;
+        if (strlen(base_sub_match2.str().c_str()) > 0)
+          min_version = atoul(base_sub_match2.str().c_str());
+
+        if (strlen(base_sub_match2.str().c_str()) > 0)
+          max_version = atoul(base_sub_match3.str().c_str());
+        version_map.insert(std::make_pair(new VersionRange(min_version, max_version), zonemap));
+      }
+    }
+  }
+}
+
+MapRange::MapRange()
+{
+	
+}
+
+MapRange::~MapRange()
+{
+	Clear();
+}
+
+void MapRange::Clear()
+{
+	map<VersionRange*, Map*>::iterator itr;
+	for (itr = version_map.begin(); itr != version_map.end(); itr++)
+	{
+		VersionRange* range = itr->first;
+		Map* map = itr->second;
+		delete range;
+		delete map;
+	}
+
+	version_map.clear();
+}
+
+map<VersionRange*, Map*>::iterator MapRange::FindVersionRange(int32 min_version, int32 max_version)
+{
+	map<VersionRange*, Map*>::iterator itr;
+	for (itr = version_map.begin(); itr != version_map.end(); itr++)
+	{
+		VersionRange* range = itr->first;
+		// if min and max version are both in range
+		if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion())
+			return itr;
+		// if the min version is in range, but max range is 0
+		else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0)
+			return itr;
+		// if min version is 0 and max_version has a cap
+		else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion())
+			return itr;
+	}
+
+	return version_map.end();	
+}
+
+map<VersionRange*, Map*>::iterator MapRange::FindMapByVersion(int32 version)
+{
+	map<VersionRange*, Map*>::iterator enditr = version_map.end();
+	map<VersionRange*, Map*>::iterator itr;
+	for (itr = version_map.begin(); itr != version_map.end(); itr++)
+	{
+		VersionRange* range = itr->first;
+		// if min and max version are both in range
+		if(range->GetMinVersion() == 0 && range->GetMaxVersion() == 0)
+			enditr = itr;
+		else if (version >= range->GetMinVersion() && version <= range->GetMaxVersion())
+			return itr;
+	}
+
+	return enditr;
+}

+ 24 - 6
EQ2/source/WorldServer/Zone/map.h

@@ -23,6 +23,7 @@
 #define ZONE_MAP_H
 
 #include "../../common/types.h"
+#include "../../common/MiscFunctions.h"
 #include "../../common/Mutex.h"
 #include "position.h"
 #include <stdio.h>
@@ -33,7 +34,7 @@
 class Map
 {
 public:
-	Map(string filename, SPGrid* grid=nullptr);
+	Map(string zonename, string filename, SPGrid* grid=nullptr);
 	~Map();
 
 	float FindBestZ(glm::vec3 &start, glm::vec3 *result);
@@ -43,13 +44,9 @@ public:
 	bool CheckLoS(glm::vec3 myloc, glm::vec3 oloc);
 	bool DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, glm::vec3 &outnorm, float &distance);
 
-#ifdef USE_MAP_MMFS
-	bool Load(std::string filename, bool force_mmf_overwrite = false);
-#else
 	bool Load(const std::string& filename);
-#endif
 
-	static Map *LoadMapFile(std::string file, SPGrid* grid=nullptr);
+	static Map *LoadMapFile(std::string zonename, std::string file, SPGrid* grid=nullptr);
 
 	std::string GetFileName() { return m_ZoneFile; }
 	void SetMapLoaded(bool val) {
@@ -83,6 +80,8 @@ public:
 	float GetMaxZ() { return m_MaxZ; }
 
 	void SetFileName(std::string newfile) { m_FileName = string(newfile); }
+	
+	SPGrid* GetGrid() { return mGrid; }
 private:
 	void RotateVertex(glm::vec3 &v, float rx, float ry, float rz);
 	void ScaleVertex(glm::vec3 &v, float sx, float sy, float sz);
@@ -92,6 +91,7 @@ private:
 
 	string m_FileName;
 	string m_ZoneFile;
+	string m_ZoneName;
 	int32 m_CellSize;
 
 	float m_MinX;
@@ -113,4 +113,22 @@ private:
 	SPGrid* mGrid;
 };
 
+class MapRange {
+public:
+	MapRange();
+
+	~MapRange();
+
+	void Clear();
+	
+	void AddVersionRange(std::string zoneName);
+
+	map<VersionRange*, Map*>::iterator FindVersionRange(int32 min_version, int32 max_version);
+	map<VersionRange*, Map*>::iterator FindMapByVersion(int32 version);
+	map<VersionRange*, Map*>::iterator GetRangeEnd() { return version_map.end(); }
+private:
+	std::map<VersionRange*, Map*> version_map;
+	string name;
+};
+
 #endif

+ 4 - 4
EQ2/source/WorldServer/Zone/mob_movement_manager.cpp

@@ -476,7 +476,7 @@ struct MobMovementEntry {
 
 void AdjustRoute(std::list<IPathfinder::IPathNode> &nodes, Entity *who)
 {
-	if (who->GetZone() == nullptr || !who->GetZone()->zonemap /*|| !zone->HasWaterMap()*/) {
+	if (who->GetZone() == nullptr || !who->GetMap() /*|| !zone->HasWaterMap()*/) {
 		return;
 	}
 
@@ -484,7 +484,7 @@ void AdjustRoute(std::list<IPathfinder::IPathNode> &nodes, Entity *who)
 
 	for (auto &node : nodes) {
 		//if (!zone->watermap->InLiquid(node.pos)) {
-			auto best_z = who->GetZone()->zonemap->FindBestZ(node.pos, nullptr);
+			auto best_z = who->GetMap()->FindBestZ(node.pos, nullptr);
 			if (best_z != BEST_Z_INVALID) {
 				node.pos.z = best_z + offset;
 			}
@@ -818,7 +818,7 @@ void MobMovementManager::ClearStats()
  */
 void MobMovementManager::UpdatePath(Entity *who, float x, float y, float z, MobMovementMode mob_movement_mode)
 {
-	if (!who->GetZone()->zonemap /*|| !zone->HasWaterMap()*/) {
+	if (!who->GetMap() /*|| !zone->HasWaterMap()*/) {
 		MobListMutex.readlock();
 		auto iter = _impl->Entries.find(who);
 
@@ -1018,7 +1018,7 @@ void MobMovementManager::UpdatePathUnderwater(Entity *who, float x, float y, flo
 
 	auto &ent  = (*eiter);
 	if (/*zone->watermap->InLiquid(who->GetPosition()) && zone->watermap->InLiquid(glm::vec3(x, y, z)) &&*/
-		who->GetZone()->zonemap->CheckLoS(glm::vec3(who->GetX(),who->GetZ(),who->GetY()), glm::vec3(x, y, z))) {
+		who->GetMap()->CheckLoS(glm::vec3(who->GetX(),who->GetZ(),who->GetY()), glm::vec3(x, y, z))) {
 		PushSwimTo(ent.second, x, y, z, movement_mode);
 		PushStopMoving(ent.second);
 		MobListMutex.releasereadlock();

+ 61 - 10
EQ2/source/WorldServer/Zone/region_map.cpp

@@ -4,11 +4,22 @@
 #include "region_map_v1.h"
 #include "../../common/Log.h"
 
+
+#ifdef WIN32
+#define _snprintf snprintf
+#include <WinSock2.h>
+#include <windows.h>
+#endif
+
+#include <boost/regex.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/foreach.hpp> 
+#include <boost/algorithm/string.hpp>
 #include <algorithm>
 #include <cctype>
 #include <stdio.h>
-#include <string.h>
 #include <fstream>
+#include <string.h>
 
 /**
  * @param name
@@ -23,11 +34,11 @@ inline bool file_exists(const std::string& name) {
  * @param zone_name
  * @return
  */
-RegionMap* RegionMap::LoadRegionMapfile(std::string zone_name) {
-	std::string filename = "Regions/";
-	filename += zone_name;
-	filename += ".EQ2Region";
-	FILE* f = fopen(filename.c_str(), "rb");
+RegionMap* RegionMap::LoadRegionMapfile(std::string filename, std::string zone_name) {
+	std::string loadedFile = "Regions/";
+	loadedFile += filename;
+	loadedFile += ".EQ2Region";
+	FILE* f = fopen(loadedFile.c_str(), "rb");
 
 	LogWrite(REGION__DEBUG, 7, "Region", "Attempting load of %s", filename.c_str());
 
@@ -47,12 +58,16 @@ RegionMap* RegionMap::LoadRegionMapfile(std::string zone_name) {
 	name[len] = '\0';
 	LogWrite(REGION__DEBUG, 7, "Region", "name = %s", name);
 
-	string fileName(name);
-	std::size_t found = fileName.find(zone_name);
+	string inFileName(name);
+	boost::algorithm::to_lower(inFileName);
+	string zoneNameLwr(zone_name);
+	boost::algorithm::to_lower(zoneNameLwr);
+
+	std::size_t found = inFileName.find(zoneNameLwr);
 	// Make sure file contents are for the correct zone
 	if (found == std::string::npos) {
 		fclose(f);
-		LogWrite(REGION__ERROR, 0, "Region", "WaterMap::LoadWaterMapfile() map contents (%s) do not match its name (%s).", &name, zone_name.c_str());
+		LogWrite(REGION__ERROR, 0, "Region", "RegionMap::LoadRegionMapfile() map contents (%s) do not match its name (%s).", inFileName, zoneNameLwr.c_str());
 		return nullptr;
 	}
 	
@@ -61,7 +76,43 @@ RegionMap* RegionMap::LoadRegionMapfile(std::string zone_name) {
 	LogWrite(REGION__INFO, 0, "Region", "Loading %s RegionMapVersion = %u", name, regionMapVersion);
 
 	RegionMapV1* regionmap = new RegionMapV1();
-	regionmap->Load(f);
+	regionmap->Load(f, zoneNameLwr, regionMapVersion);
 
 	return regionmap;
 }
+
+
+void RegionMapRange::AddVersionRange(std::string zoneName) {
+  boost::filesystem::path targetDir("Regions/");
+
+  boost::filesystem::recursive_directory_iterator iter(targetDir), eod;
+  boost::smatch base_match;
+  std::string formula = "(.*\\/|.*\\\\)((" + zoneName + ")(\\-([0-9]+)\\-([0-9]+))?)\\.EQ2Region$";
+  boost::regex re(formula.c_str());
+  LogWrite(REGION__INFO, 0, "Region", "Region Formula to match: %s\n", formula.c_str());
+
+  BOOST_FOREACH(boost::filesystem::path
+    const & i, make_pair(iter, eod)) {
+    if (is_regular_file(i)) {
+		std::string fileName(i.string());
+      if (boost::regex_match(fileName, base_match, re)) {
+        boost::ssub_match base_sub_match = base_match[2];
+		boost::ssub_match base_sub_match2 = base_match[5];
+		boost::ssub_match base_sub_match3 = base_match[6];
+		std::string baseMatch(base_sub_match.str().c_str());
+		std::string baseMatch2(base_sub_match2.str().c_str());
+		std::string baseMatch3(base_sub_match3.str().c_str());
+        LogWrite(REGION__INFO, 0, "Region", "Region to Load: %s, size: %i, string: %s, min: %s, max: %s\n", fileName.c_str(), base_match.size(), baseMatch.c_str(), baseMatch2.c_str(), baseMatch3.c_str());
+        RegionMap * regionmap = RegionMap::LoadRegionMapfile(base_sub_match.str().c_str(), zoneName);
+
+        int32 min_version = 0, max_version = 0;
+        if (strlen(base_sub_match2.str().c_str()) > 0)
+          min_version = atoul(base_sub_match2.str().c_str());
+
+        if (strlen(base_sub_match2.str().c_str()) > 0)
+          max_version = atoul(base_sub_match3.str().c_str());
+        version_map.insert(std::make_pair(new VersionRange(min_version, max_version), regionmap));
+      }
+    }
+  }
+}

+ 81 - 5
EQ2/source/WorldServer/Zone/region_map.h

@@ -2,9 +2,14 @@
 #define EQ2EMU_REGION_MAP_H
 
 #include "../../common/types.h"
+#include "../../common/MiscFunctions.h"
+
 #include "position.h"
 #include <string>
 
+class Client;
+class Spawn;
+
 enum WaterRegionType : int {
 	RegionTypeUnsupported = -2,
 	RegionTypeUntagged = -1,
@@ -33,16 +38,87 @@ public:
 	RegionMap() { }
 	virtual ~RegionMap() { }
 
-	static RegionMap* LoadRegionMapfile(std::string zone_name);
-	virtual WaterRegionType ReturnRegionType(const glm::vec3& location, float belowY = -999999.0f) const = 0;
-	virtual bool InWater(const glm::vec3& location, float belowY = -999999.0f) const = 0;
-	virtual bool InLava(const glm::vec3& location) const = 0;
+	static RegionMap* LoadRegionMapfile(std::string filename, std::string zone_name);
+	virtual WaterRegionType ReturnRegionType(const glm::vec3& location, int32 gridid=0) const = 0;
+	virtual bool InWater(const glm::vec3& location, int32 gridid=0) const = 0;
+	virtual bool InLava(const glm::vec3& location, int32 gridid=0) const = 0;
 	virtual bool InLiquid(const glm::vec3& location) const = 0;
 	virtual bool InPvP(const glm::vec3& location) const = 0;
 	virtual bool InZoneLine(const glm::vec3& location) const = 0;
-
+	
+	virtual void IdentifyRegionsInGrid(Client* client, const glm::vec3& location) const = 0;
+	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;
 protected:
 	virtual bool Load(FILE *fp) { return false; }
 };
 
+
+class RegionMapRange {
+public:
+	RegionMapRange()
+	{
+		
+	}
+
+	~RegionMapRange()
+	{
+		map<VersionRange*, RegionMap*>::iterator itr;
+		for (itr = version_map.begin(); itr != version_map.end(); itr++)
+		{
+			VersionRange* range = itr->first;
+			RegionMap* map = itr->second;
+			delete range;
+			delete map;
+		}
+
+		version_map.clear();
+	}
+
+	void AddVersionRange(std::string zoneName);
+
+	map<VersionRange*, RegionMap*>::iterator FindVersionRange(int32 min_version, int32 max_version)
+	{
+		map<VersionRange*, RegionMap*>::iterator itr;
+		for (itr = version_map.begin(); itr != version_map.end(); itr++)
+		{
+			VersionRange* range = itr->first;
+			// if min and max version are both in range
+			if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion())
+				return itr;
+			// if the min version is in range, but max range is 0
+			else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0)
+				return itr;
+			// if min version is 0 and max_version has a cap
+			else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion())
+				return itr;
+		}
+
+		return version_map.end();
+	}
+
+	map<VersionRange*, RegionMap*>::iterator FindRegionByVersion(int32 version)
+	{
+		map<VersionRange*, RegionMap*>::iterator enditr = version_map.end();
+		map<VersionRange*, RegionMap*>::iterator itr;
+		for (itr = version_map.begin(); itr != version_map.end(); itr++)
+		{
+			VersionRange* range = itr->first;
+			// if min and max version are both in range
+			if(range->GetMinVersion() == 0 && range->GetMaxVersion() == 0)
+				enditr = itr;
+			else if (version >= range->GetMinVersion() && version <= range->GetMaxVersion())
+				return itr;
+		}
+
+		return enditr;
+	}
+
+	map<VersionRange*, RegionMap*>::iterator GetRangeEnd() { return version_map.end(); }
+private:
+	std::map<VersionRange*, RegionMap*> version_map;
+	string name;
+};
+
 #endif

+ 367 - 11
EQ2/source/WorldServer/Zone/region_map_v1.cpp

@@ -1,8 +1,15 @@
 #include "region_map_v1.h"
 #include "../../common/Log.h"
+#include "../client.h"
+#include "../Spawn.h"
+#include "../LuaInterface.h"
 
-RegionMapV1::RegionMapV1() {
+#include <boost/filesystem.hpp>
+
+extern LuaInterface* lua_interface;
 
+RegionMapV1::RegionMapV1() {
+	mVersion = 1;
 }
 
 RegionMapV1::~RegionMapV1() {
@@ -22,16 +29,16 @@ RegionMapV1::~RegionMapV1() {
 	Regions.clear();
 }
 
-WaterRegionType RegionMapV1::ReturnRegionType(const glm::vec3& location, float belowY) const {
-	return BSPReturnRegionType(1, glm::vec3(location.y, location.x + 0.5f, location.z));
+WaterRegionType RegionMapV1::ReturnRegionType(const glm::vec3& location, int32 gridid) const {
+	return BSPReturnRegionType(1, glm::vec3(location.x, location.y, location.z), gridid);
 }
 
-bool RegionMapV1::InWater(const glm::vec3& location, float belowY) const {
-	return ReturnRegionType(location, belowY) == RegionTypeWater;
+bool RegionMapV1::InWater(const glm::vec3& location, int32 gridid) const {
+	return ReturnRegionType(location, gridid) == RegionTypeWater;
 }
 
-bool RegionMapV1::InLava(const glm::vec3& location) const {
-	return ReturnRegionType(location) == RegionTypeLava;
+bool RegionMapV1::InLava(const glm::vec3& location, int32 gridid) const {
+	return ReturnRegionType(location, gridid) == RegionTypeLava;
 }
 
 bool RegionMapV1::InLiquid(const glm::vec3& location) const {
@@ -46,7 +53,25 @@ bool RegionMapV1::InZoneLine(const glm::vec3& location) const {
 	return ReturnRegionType(location) == RegionTypeZoneLine;
 }
 
-bool RegionMapV1::Load(FILE* fp) {
+std::string RegionMapV1::TestFile(std::string testFile)
+{
+	std::string tmpStr(testFile);
+	std::size_t pos = tmpStr.find("."); 
+	if ( pos != testFile.npos )
+		tmpStr = testFile.substr (0, pos);
+			
+	string tmpScript("RegionScripts/");
+	tmpScript.append(mZoneNameLower);
+	tmpScript.append("/" + tmpStr + ".lua");
+	printf("File to test : %s\n",tmpScript.c_str());
+	std::ifstream f(tmpScript.c_str());
+	return f.good() ? tmpScript : string("");
+}
+
+
+bool RegionMapV1::Load(FILE* fp, std::string inZoneNameLwr, int32 version) {
+	mZoneNameLower = string(inZoneNameLwr.c_str());
+	
 	uint32 region_size;
 	if (fread(&region_size, sizeof(region_size), 1, fp) != 1) {
 		return false;
@@ -80,6 +105,41 @@ bool RegionMapV1::Load(FILE* fp) {
 			return false;
 		}
 
+		int8 strSize;
+		char envName[256] = {""};
+		char regionName[256] = {""};
+		uint32 grid_id = 0;
+
+		if ( version > 1 )
+		{
+			fread(&strSize, sizeof(int8), 1, fp);
+			LogWrite(REGION__DEBUG, 7, "Region", "Region environment strSize = %u", strSize);
+
+			if(strSize)
+			{
+				size_t len = fread(&envName, sizeof(char), strSize, fp);
+				envName[len] = '\0';
+			}
+
+			LogWrite(REGION__DEBUG, 7, "Region", "Region environment file name = %s", envName);
+
+			fread(&strSize, sizeof(int8), 1, fp);
+			LogWrite(REGION__DEBUG, 7, "Region", "Region name strSize = %u", strSize);
+
+			if(strSize)
+			{
+				size_t len = fread(&regionName, sizeof(char), strSize, fp);
+				regionName[len] = '\0';
+			}
+			
+			LogWrite(REGION__DEBUG, 7, "Region", "Region name file name = %s", regionName);
+
+			if (fread(&grid_id, sizeof(grid_id), 1, fp) != 1) {
+				return false;
+			}
+
+		}
+
 		uint32 bsp_tree_size;
 		if (fread(&bsp_tree_size, sizeof(bsp_tree_size), 1, fp) != 1) {
 			return false;
@@ -99,6 +159,22 @@ bool RegionMapV1::Load(FILE* fp) {
 		tmpNode->z = z;
 		tmpNode->dist = dist;
 		tmpNode->region_type = region_type;
+		tmpNode->regionName = string(regionName);
+		tmpNode->regionEnvFileName = string(envName);
+		tmpNode->grid_id = grid_id;
+		tmpNode->regionScriptName = string("");
+		
+		tmpNode->regionScriptName = TestFile(regionName);
+		
+		if ( tmpNode->regionScriptName.size() < 1 )
+		{
+			tmpNode->regionScriptName = TestFile(envName);
+		}
+		if ( tmpNode->regionScriptName.size() < 1 )
+		{
+			tmpNode->regionScriptName = TestFile("default");
+		}
+		
 		Regions.insert(make_pair(tmpNode, BSP_Root));
 	}
 
@@ -109,12 +185,282 @@ bool RegionMapV1::Load(FILE* fp) {
 	return true;
 }
 
-WaterRegionType RegionMapV1::BSPReturnRegionType(int32 node_number, const glm::vec3& location) const {
+void RegionMapV1::IdentifyRegionsInGrid(Client *client, const glm::vec3 &location) const
+{
+	map<Region_Node*, ZBSP_Node*>::const_iterator itr;
+	int region_num = 0;
+
+	int32 grid = 0;
+	if (client->GetPlayer()->GetMap() != nullptr)
+	{
+		grid = client->GetPlayer()->GetMap()->GetGrid()->GetGridIDByLocation(location.x, location.y, location.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);
+
+	for (itr = Regions.begin(); itr != Regions.end(); itr++)
+	{
+		Region_Node *node = itr->first;
+		ZBSP_Node *BSP_Root = itr->second;
+
+		if (grid == 0 || node->grid_id == grid)
+		{
+			float x1 = node->x - location.x;
+			float y1 = node->y - location.y;
+			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)
+			{
+				WaterRegionType regionType = RegionTypeUntagged;
+
+				if (node->region_type == ClassWaterRegion)
+					regionType = BSPReturnRegionWaterRegion(node, BSP_Root, 1, testLoc, dist);
+				else
+					regionType = BSPReturnRegionTypeNode(node, BSP_Root, 1, testLoc, dist);
+
+				if (regionType != RegionTypeNormal)
+				{
+					client->Message(CHANNEL_COLOR_YELLOW, "[DETECTED IN REGION %i] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f.  Script: %s", regionType, 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());
+				}
+				else
+				{
+					client->Message(CHANNEL_COLOR_RED, "[IN DIST RANGE] 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());
+				}
+			}
+			else
+				client->Message(CHANNEL_COLOR_RED, "[OUT OF RANGE] 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());
+		}
+		else
+			client->Message(CHANNEL_COLOR_RED, "[OUT OF GRID] 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());
+
+		region_num++;
+	}
+}
+
+void RegionMapV1::MapRegionsNearSpawn(Spawn *spawn, Client *client) const
+{
+	map<Region_Node*, ZBSP_Node*>::const_iterator itr;
+	int region_num = 0;
+
+	spawn->RegionMutex.writelock();
+
+	glm::vec3 testLoc(spawn->GetX(), spawn->GetY(), spawn->GetZ());
+	for (itr = Regions.begin(); itr != Regions.end(); itr++)
+	{
+		Region_Node *node = itr->first;
+		ZBSP_Node *BSP_Root = itr->second;
+
+		if (node->regionScriptName.size() < 1)	// only track ones that are used with LUA scripting
+			continue;
+
+		float x1 = node->x - testLoc.x;
+		float y1 = node->y - testLoc.y;
+		float z1 = node->z - testLoc.z;
+		float dist = sqrt(x1 *x1 + y1 *y1 + z1 *z1);
+		if (dist <= node->dist)
+		{
+			WaterRegionType regionType = RegionTypeUntagged;
+
+			if (node->region_type == ClassWaterRegion)
+				regionType = BSPReturnRegionWaterRegion(node, BSP_Root, 1, testLoc, dist);
+			else
+				regionType = BSPReturnRegionTypeNode(node, BSP_Root, 1, testLoc, dist);
+
+			if (regionType != RegionTypeNormal)
+			{
+				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));
+
+					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());
+				}
+			}
+			else
+			{
+				if (spawn->InRegion(node, BSP_Root))
+				{
+					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());
+					WaterRegionType whatWasRegionType = (WaterRegionType) spawn->GetRegionType(node, BSP_Root);
+					lua_interface->RunRegionScript(node->regionScriptName, "LeaveRegion", spawn->GetZone(), spawn, whatWasRegionType);
+				}
+				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));
+				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());
+			}
+		}
+
+		region_num++;
+	}
+
+	spawn->RegionMutex.releasewritelock();
+}
+
+void RegionMapV1::UpdateRegionsNearSpawn(Spawn *spawn, Client *client) const
+{
+	map<map<Region_Node*, ZBSP_Node*>, Region_Status>::iterator testitr;
+	int region_num = 0;
+
+	spawn->RegionMutex.writelock();
+
+	glm::vec3 testLoc(spawn->GetX(), spawn->GetY(), spawn->GetZ());
+
+	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;
+
+		float x1 = node->x - testLoc.x;
+		float y1 = node->y - testLoc.y;
+		float z1 = node->z - testLoc.z;
+		float dist = sqrt(x1 *x1 + y1 *y1 + z1 *z1);
+		if (dist <= node->dist)
+		{
+			WaterRegionType regionType = RegionTypeUntagged;
+
+			if (node->region_type == ClassWaterRegion)
+				regionType = BSPReturnRegionWaterRegion(node, BSP_Root, 1, testLoc, dist);
+			else
+				regionType = BSPReturnRegionTypeNode(node, BSP_Root, 1, testLoc, dist);
+
+			if (regionType != RegionTypeNormal)
+			{
+				if (!testitr->second.inRegion)
+				{
+					testitr->second.inRegion = true;
+					int32 returnValue = 0;
+					lua_interface->RunRegionScript(node->regionScriptName, "EnterRegion", spawn->GetZone(), spawn, regionType, &returnValue);
+					if (client)
+						client->Message(CHANNEL_COLOR_YELLOW, "[ENTER RANGE %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());
+					testitr->second.timerTic = returnValue;
+					testitr->second.lastTimerTic = returnValue ? Timer::GetCurrentTime2() : 0;
+				}
+			}
+			else
+			{
+				if (testitr->second.inRegion)
+				{
+					testitr->second.inRegion = false;
+					testitr->second.timerTic = 0;
+					testitr->second.lastTimerTic = 0;
+					if (client)
+						client->Message(CHANNEL_COLOR_RED, "[LEAVE RANGE] 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());
+					WaterRegionType whatWasRegionType = (WaterRegionType) spawn->GetRegionType(node, BSP_Root);
+					lua_interface->RunRegionScript(node->regionScriptName, "LeaveRegion", spawn->GetZone(), spawn, whatWasRegionType);
+				}
+			}
+		}
+		else
+		{
+			if (client)
+				client->Message(CHANNEL_COLOR_RED, "[LEAVE RANGE - OOR] 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());
+			deleteNodes.insert(make_pair(node, BSP_Root));
+		}
+
+		region_num++;
+	}
+
+	map<Region_Node*, ZBSP_Node*>::const_iterator deleteItr;
+	for (deleteItr = deleteNodes.begin(); deleteItr != deleteNodes.end(); deleteItr++)
+	{
+		Region_Node *tmpNode = deleteItr->first;
+		ZBSP_Node *bspNode = deleteItr->second;
+		spawn->DeleteRegion(tmpNode, bspNode);
+	}
+
+	spawn->RegionMutex.releasewritelock();
+}
+
+void RegionMapV1::TicRegionsNearSpawn(Spawn *spawn, Client *client) const
+{
+	map<map<Region_Node*, ZBSP_Node*>, Region_Status>::iterator testitr;
+	int region_num = 0;
+
+	spawn->RegionMutex.writelock();
+
+	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;
+
+		if (testitr->second.timerTic && testitr->second.inRegion && 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());
+			WaterRegionType whatWasRegionType = RegionTypeNormal;	// default will be 0
+
+			if (BSP_Root->special == -3)
+				whatWasRegionType = RegionTypeLava;	// 2
+			else if (BSP_Root->special == 1)
+				whatWasRegionType = RegionTypeWater;	// 1
+
+			int32 returnValue = 0;
+			lua_interface->RunRegionScript(node->regionScriptName, "Tick", spawn->GetZone(), spawn, whatWasRegionType, &returnValue);
+
+			if (returnValue == 1)
+			{
+				testitr->second.lastTimerTic = 0;
+				testitr->second.timerTic = 0;
+			}
+		}
+
+		region_num++;
+	}
+
+	spawn->RegionMutex.releasewritelock();
+}
+
+WaterRegionType RegionMapV1::BSPReturnRegionType(int32 node_number, const glm::vec3& location, int32 gridid) const {
 	map<Region_Node*, ZBSP_Node*>::const_iterator itr;
 	int region_num = 0;
 	for (itr = Regions.begin(); itr != Regions.end(); itr++)
 	{
+
 		Region_Node* node = itr->first;
+
+		// did not match grid id of current region, skip
+		//if ( gridid > 0 && gridid != node->grid_id)
+		//	continue;
+
 		ZBSP_Node* BSP_Root = itr->second;
 
 		float x1 = node->x - location.x;
@@ -129,7 +475,6 @@ WaterRegionType RegionMapV1::BSPReturnRegionType(int32 node_number, const glm::v
 		if (dist <= node->dist)
 		{
 			ZBSP_Node* BSP_Root = itr->second;
-			const ZBSP_Node* current_node = &BSP_Root[node_number - 1];
 
 			WaterRegionType regionType = RegionTypeUntagged;
 
@@ -295,7 +640,18 @@ WaterRegionType RegionMapV1::BSPReturnRegionWaterRegion(const Region_Node* regio
 			return(RegionTypeNormal);
 		}
 		else if (current_node->left == -2) {
-			return(RegionTypeWater);
+			switch(current_node->special)
+			{
+				case -3:
+					return(RegionTypeLava);
+					break;
+				case 1:
+					return(RegionTypeWater);
+					break;
+				default:
+					return(RegionTypeUntagged);
+					break;
+			}
 		}
 		return BSPReturnRegionWaterRegion(region_node, BSP_Root, current_node->left + 1, location, distToNode);
 	}

+ 31 - 6
EQ2/source/WorldServer/Zone/region_map_v1.h

@@ -4,6 +4,9 @@
 #include "region_map.h"
 #include <map>
 
+class Client;
+class Spawn;
+
 #pragma pack(1)
 typedef struct ZBSP_Node {
 	int32 node_number;
@@ -19,34 +22,56 @@ typedef struct Region_Node {
 	float y;
 	float z;
 	float dist;
+	string regionEnvFileName;
+	string regionName;
+	int32 grid_id;
+	string regionScriptName;
 } Region_Node;
 #pragma pack()
 
+struct Region_Status {
+	bool inRegion;
+	int32 timerTic;
+	int32 lastTimerTic;
+	int32 regionType;
+};
+
 class RegionMapV1 : public RegionMap
 {
 public:
 	RegionMapV1();
 	~RegionMapV1();
 	
-	virtual WaterRegionType ReturnRegionType(const glm::vec3& location, float belowY = -999999.0f) const;
-	virtual bool InWater(const glm::vec3& location, float belowY = -999999.0f) const;
-	virtual bool InLava(const glm::vec3& location) const;
+	virtual WaterRegionType ReturnRegionType(const glm::vec3& location, int32 grid_id=0) const;
+	virtual bool InWater(const glm::vec3& location, int32 grid_id=0) const;
+	virtual bool InLava(const glm::vec3& location, int32 grid_id=0) const;
 	virtual bool InLiquid(const glm::vec3& location) const;
 	virtual bool InPvP(const glm::vec3& location) const;
 	virtual bool InZoneLine(const glm::vec3& location) const;
-	
+
+	virtual void IdentifyRegionsInGrid(Client* client, const glm::vec3& location) const;
+	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;
+
 protected:
-	virtual bool Load(FILE *fp);
+	virtual bool Load(FILE *fp, std::string inZoneLowerName, int32 regionVersion);
 
 private:
-	WaterRegionType BSPReturnRegionType(int32 node_number, const glm::vec3& location) const;
+	WaterRegionType BSPReturnRegionType(int32 node_number, const glm::vec3& location, int32 gridid=0) const;
 	WaterRegionType BSPReturnRegionTypeNode(const Region_Node* node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode=0.0f) const;
 
 	WaterRegionType BSPReturnRegionWaterRegion(const Region_Node* node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode=0.0f) const;
 	map<Region_Node*, ZBSP_Node*> Regions;
 
 	WaterRegionType EstablishDistanceAtAngle(const Region_Node* region_node, const ZBSP_Node* current_node, float distance, float absDistance, float absSplitDist, bool checkEdgedAngle=false) const;
+	
+	std::string TestFile(std::string testFile);
+
 	friend class RegionMap;
+
+	int32 mVersion;
+	std::string mZoneNameLower;
 };
 
 #endif

+ 33 - 13
EQ2/source/WorldServer/client.cpp

@@ -196,6 +196,8 @@ Client::Client(EQStream* ieqs) : pos_update(125), quest_pos_timer(2000), lua_deb
 	SetHasOwnerOrEditAccess(false);
 	temporary_transport_id = 0;
 	rejoin_group_id = 0;
+	lastRegionRemapTime = 0;
+	regionDebugMessaging = false;
 }
 
 Client::~Client() {
@@ -2842,12 +2844,29 @@ bool Client::Process(bool zone_process) {
 		LogWrite(CCLIENT__DEBUG, 1, "Client", "%s, CheckQuestQueue", __FUNCTION__, __LINE__);
 		CheckQuestQueue();
 	}
-	if (pos_update.Check() && player_pos_changed && IsReadyForUpdates() && ( !disconnect_timer || !disconnect_timer->Enabled())) {
-		//GetPlayer()->CalculateLocation();
-		client_list.CheckPlayersInvisStatus(this);
-		GetCurrentZone()->SendPlayerPositionChanges(GetPlayer());
-		player_pos_changed = false;
-		GetCurrentZone()->CheckTransporters(this);
+	if (pos_update.Check())
+	{
+		if(GetPlayer()->GetRegionMap())
+			GetPlayer()->GetRegionMap()->TicRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr);
+		
+		if(player_pos_changed && IsReadyForUpdates() && ( !disconnect_timer || !disconnect_timer->Enabled())) {
+			//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);
+			}
+		}
 	}
 	if (lua_interface && lua_debug && lua_debug_timer.Check())
 		lua_interface->UpdateDebugClients(this);
@@ -5262,7 +5281,7 @@ void Client::SetPlayerQuest(Quest* quest, map<int32, int32>* progress) {
 	}
 	map<int32, int32>::iterator itr;
 	QuestStep* step = 0;
-	player->SetZone(GetCurrentZone());
+	player->SetZone(GetCurrentZone(), GetVersion());
 	for (itr = progress->begin(); itr != progress->end(); itr++) {
 		step = quest->GetQuestStep(itr->first);
 		if (step && itr->second > 0) {
@@ -8144,13 +8163,13 @@ void Client::ClearWaypoint() {
 
 bool Client::ShowPathToTarget(float x, float y, float z, float y_offset) {
 	if (current_zone->pathing) {
-		if (current_zone->zonemap) {
-			if (x < current_zone->zonemap->GetMinX() || x > current_zone->zonemap->GetMaxX())
+		if (GetPlayer()->GetMap()) {
+			if (x < GetPlayer()->GetMap()->GetMinX() || x > GetPlayer()->GetMap()->GetMaxX())
 				return false;
-			if (z < current_zone->zonemap->GetMinZ() || z > current_zone->zonemap->GetMaxZ())
+			if (z < GetPlayer()->GetMap()->GetMinZ() || z > GetPlayer()->GetMap()->GetMaxZ())
 				return false;
 			auto loc = glm::vec3(x, z, y);
-			float new_z = current_zone->zonemap->FindBestZ(loc, nullptr);
+			float new_z = GetPlayer()->GetMap()->FindBestZ(loc, nullptr);
 			if (new_z != BEST_Z_INVALID) //this is actually y
 				y = new_z;
 		}
@@ -9199,7 +9218,7 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code)
 			Client* client = zone_list.GetClientByCharName(player->GetName());
 			if (client) {
 				if (client->getConnection())
-					client->getConnection()->SendDisconnect();
+					client->getConnection()->SendDisconnect(true);
 				if (client->GetCurrentZone() && !client->IsZoning()) {
 					//swap players, allowing the client to resume his LD'd player (ONLY if same version of the client)
 					if (client->GetVersion() == version) {
@@ -9213,7 +9232,8 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code)
 						GetPlayer()->SetReturningFromLD(true);
 						GetPlayer()->GetZone()->RemoveDelayedSpawnRemove(GetPlayer());
 					}
-					client->GetCurrentZone()->RemoveClientImmediately(client);
+					ZoneServer* tmpZone = client->GetCurrentZone();
+					tmpZone->RemoveClientImmediately(client);
 				}
 			}
 			MDeletePlayer.releasewritelock(__FUNCTION__, __LINE__);

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

@@ -213,7 +213,7 @@ public:
 	void	SetCurrentZone(int32 id);
 	void	SetCurrentZone(ZoneServer* zone) { 
 		current_zone = zone;
-		player->SetZone(zone);
+		player->SetZone(zone, GetVersion());
 	}
 	Player*	GetPlayer(){ return player; }
 	EQStream*	getConnection(){ return eqs; }
@@ -459,6 +459,7 @@ public:
 	bool ShowPathToTarget(float x, float y, float z, float y_offset);
 	bool ShowPathToTarget(Spawn* spawn);
 
+	void SetRegionDebug(bool val) { regionDebugMessaging = val; }
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -564,6 +565,10 @@ private:
 	int32 temporary_transport_id;
 
 	int32 rejoin_group_id;
+	
+	int32 lastRegionRemapTime;
+	
+	bool regionDebugMessaging;
 };
 
 class ClientList {

+ 1 - 1
EQ2/source/WorldServer/makefile

@@ -21,7 +21,7 @@ Lua_W_Flags	= -Wall
 
 # World flags
 C_Flags		= -I/usr/include/mariadb -I../depends/fmt/include -I../depends/recastnavigation/Detour/Include -I/usr/local/include/boost -I../depends/glm/ -march=native -pipe -pthread -std=c++0x
-LD_Flags	= -L/usr/lib/x86_64-linux-gnu -lz -lpthread -lmariadbclient -L../depends/recastnavigation/RecastDemo/Build/gmake/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast -L/usr/local/lib -rdynamic -lm -Wl,-E -ldl -lreadline -lboost_system -lboost_filesystem -lboost_iostreams
+LD_Flags	= -L/usr/lib/x86_64-linux-gnu -lz -lpthread -lmariadbclient -L../depends/recastnavigation/RecastDemo/Build/gmake/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast -L/usr/local/lib -rdynamic -lm -Wl,-E -ldl -lreadline -lboost_system -lboost_filesystem -lboost_iostreams -lboost_regex
 W_Flags		= -Wall -Wno-reorder
 D_Flags		= -DEQ2 -DWORLD -D_GNU_SOURCE
 

+ 1 - 1
EQ2/source/WorldServer/makefile.a64

@@ -166,7 +166,7 @@ all: $(APP)
 	
 $(APP): $(SRC)
 	$(BUILDLUA)
-	@echo Linking...; $(LINKER) $(CFLAGS) $(WFLAGS) $(DFLAGS) $(CXXFLAGS) $(OBJS) $^ $(LDFLAGS) -o $@ -lm -Wl,-E -ldl -lreadline -lboost_system -lboost_filesystem -lboost_iostreams
+	@echo Linking...; $(LINKER) $(CFLAGS) $(WFLAGS) $(DFLAGS) $(CXXFLAGS) $(OBJS) $^ $(LDFLAGS) -o $@ -lm -Wl,-E -ldl -lreadline -lboost_system -lboost_filesystem -lboost_iostreams -lboost_regex
 	@echo Finished building world.
 	
 %.o: %.cpp

+ 2 - 4
EQ2/source/WorldServer/net.cpp

@@ -62,8 +62,6 @@ double frame_time = 0.0;
 
 #ifdef WIN32
 	#include <process.h>
-	#define snprintf	_snprintf
-	#define vsnprintf	_vsnprintf
 	#define strncasecmp	_strnicmp
 	#define strcasecmp  _stricmp
 	#include <conio.h>
@@ -165,7 +163,7 @@ int main(int argc, char** argv) {
 		return 0;
 	}
 	#endif
-
+	
 	LogWrite(WORLD__DEBUG, 0, "World", "Randomizing World...");
 	srand(time(NULL));
 
@@ -648,7 +646,7 @@ void CatchSignal(int sig_num) {
 	// when the world shuts down, if this happens again comment out the LogWrite and uncomment the printf
 	if (last_signal != sig_num){
 		static Mutex lock;
-		static ofstream signal_out;
+		static std::ofstream signal_out;
 
 		lock.lock();
 		if (!signal_out.is_open())

+ 10 - 33
EQ2/source/WorldServer/zoneserver.cpp

@@ -86,8 +86,6 @@ extern int errno;
 #include "Bots/Bot.h"
 
 #ifdef WIN32
-#define snprintf	_snprintf
-#define vsnprintf	_vsnprintf
 #define strncasecmp	_strnicmp
 #define strcasecmp  _stricmp
 #endif
@@ -155,10 +153,7 @@ ZoneServer::ZoneServer(const char* name) {
 	holiday_flag = 0;
 	MMasterZoneLock = new CriticalSection(MUTEX_ATTRIBUTE_RECURSIVE);
 	
-	Grid = nullptr;
-	zonemap = nullptr;
 	pathing = nullptr;
-	regionmap = nullptr;
 	strcpy(zonesky_file,"");
 	
 	reloading = true;
@@ -204,20 +199,12 @@ ZoneServer::~ZoneServer() {
 		database.DeleteInstance(instanceID);
 	}
 
-	if (Grid != nullptr)
-		delete Grid;
-
-	if (zonemap != nullptr)
-		delete zonemap;
-
 	if (pathing != nullptr)
 		delete pathing;
 
 	if (movementMgr != nullptr)
 		delete movementMgr;
 
-	if (regionmap != nullptr)
-		delete regionmap;
 	LogWrite(ZONE__INFO, 0, "Zone", "Completed zone shutdown of '%s'", zone_name);
 	--numzones;
 	UpdateWindowTitle(0);
@@ -279,15 +266,16 @@ void ZoneServer::Init()
 	UpdateWindowTitle(0);
 
 	string zoneName(GetZoneFile());
-	if (Grid == nullptr) {
+			
+	world.LoadRegionMaps(zoneName);
+	
+	world.LoadMaps(zoneName);
+/*	if (Grid == nullptr) {
 		Grid = new SPGrid(string(GetZoneFile()), 0);
 	}
 	if (zonemap == nullptr) {
 		zonemap = Map::LoadMapFile(zoneName, Grid);
-	}
-	if (regionmap == nullptr) {
-		regionmap = RegionMap::LoadRegionMapfile(zoneName);
-	}
+	}*/
 
 	pathing = IPathfinder::Load(zoneName);
 	movementMgr = new MobMovementManager();
@@ -1353,7 +1341,7 @@ bool ZoneServer::Process()
 			MSpawnGroupChances.writelock(__FUNCTION__, __LINE__);
 			spawn_group_chances.clear();
 			MSpawnGroupChances.releasewritelock(__FUNCTION__, __LINE__);
-
+			Map* zonemap = world.GetMap(std::string(GetZoneName()),0);
 			while (zonemap != nullptr && zonemap->IsMapLoading())
 			{
 				SetWatchdogTime(Timer::GetCurrentTime2());
@@ -1362,12 +1350,6 @@ bool ZoneServer::Process()
 				Sleep(10);
 			}
 
-			if (zonemap != nullptr && Grid != nullptr && !zonemap->IsMapLoaded())
-			{
-				delete Grid;
-				Grid = nullptr;
-			}
-
 			DeleteTransporters();
 			ReloadTransporters();
 
@@ -2927,7 +2909,8 @@ GroundSpawn* ZoneServer::AddGroundSpawn(SpawnLocation* spawnlocation, SpawnEntry
 }
 
 void ZoneServer::AddSpawn(Spawn* spawn) {
-	spawn->SetZone(this);
+	if(!spawn->IsPlayer()) // we already set it on loadCharacter
+		spawn->SetZone(this);
 
 	spawn->position_changed = false;
 	spawn->info_changed = false;
@@ -2962,10 +2945,7 @@ void ZoneServer::AddSpawn(Spawn* spawn) {
 		((Player*)spawn)->GetInfoStruct()->rain = rain;
 		((Player*)spawn)->SetCharSheetChanged(true);
 	}
-
-	if (Grid != nullptr) {
-		Grid->AddSpawn(spawn);
-	}
+	
 	if (movementMgr != nullptr && spawn->IsEntity()) {
 		movementMgr->AddMob((Entity*)spawn);
 	}
@@ -3849,9 +3829,6 @@ void ZoneServer::RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spa
 	spawn->RemoveSpawnProximities();
 	RemoveSpawnProximities(spawnListLocked, spawn);
 
-	if (Grid != nullptr) {
-		Grid->RemoveSpawnFromCell(spawn);
-	}
 	if (movementMgr != nullptr && spawn->IsEntity()) {
 		movementMgr->RemoveMob((Entity*)spawn);
 	}

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

@@ -443,9 +443,6 @@ public:
 
 	map<int32, int32>* GetSpawnLocationsByGroup(int32 group_id);
 
-	SPGrid* Grid;
-	Map* zonemap;
-	RegionMap* regionmap;
 	IPathfinder* pathing;
 	MobMovementManager* movementMgr;
 

+ 13 - 0
EQ2/source/common/MiscFunctions.h

@@ -139,6 +139,19 @@ private:
 	T** pVar;
 };
 
+class VersionRange {
+public:
+	VersionRange(int32 in_min_version, int32 in_max_version)
+	{
+		min_version = in_min_version;
+		max_version = in_max_version;
+	}
+	int32 GetMinVersion() { return min_version; }
+	int32 GetMaxVersion() { return max_version; }
+private:
+	int32 min_version;
+	int32 max_version;
+};
 #endif