Browse Source

/gm tag implemented, fixed escape code again, implemented zoning redundancy

Fix #392 - /gm tag implemented
eg.
/gm tag faction x icon_id (where x is the faction id)
/gm tag spawngroup x icon_id (where x is 1 for spawngroups, 0 for non spawngroups)
/gm tag race x icon_id (where x is the race id or base race id)
/gm tag groundspawn icon_id (tags all groundspawns with the icon id)

icon ids:
1 = skull, 2 = shield half dark blue / half light blue, 3 = purple? star, 4 = yellow sword, 5 = red X, 6 = green flame
7 = Number "1", 8 = Number "2", 9 = Number "3", 10 = Number "4", 11 = Number "5", 12 = Number "6"
25 = shield, 26 = green plus, 27 = crossed swords, 28 = bow with arrow in it, 29 = light blue lightning bolt
30 = bard instrument (hard to see), 31 = writ with shield, 32 = writ with green +, 33 = writ with crossed sword
34 = writ with bow, 35 = writ with light blue lightning bolt, 36 = same as 30, 37 = party with crossed sword, shield and lightning bolt
38 = shaking hands green background, 39 = shaking hands dark green background, unlocked keylock
40 = red aura icon with black shadow of person and big red aura, 41 = green aura icon with black shadow of person big green aura

"tag1" added to info struct as a UInt8

- More LUA Interface cleanup

- Zoning spruced up, we track incoming clients to a zone and also can emergency bootup a zone should it not be ready for incoming clients.
Image 2 years ago
parent
commit
77616c791e

+ 5 - 3
EQ2/source/WorldServer/ClientPacketFunctions.cpp

@@ -92,9 +92,11 @@ void ClientPacketFunctions::SendGameWorldTime ( Client* client ){
 
 void ClientPacketFunctions::SendCharacterData ( Client* client ){
 	client->GetPlayer()->SetCharacterID(client->GetCharacterID());
-	EQ2Packet* outapp = client->GetPlayer()->serialize(client->GetPlayer(), client->GetVersion());
-	//DumpPacket(outapp);
-	client->QueuePacket(outapp);
+	if(!client->IsReloadingZone()) {
+		EQ2Packet* outapp = client->GetPlayer()->serialize(client->GetPlayer(), client->GetVersion());
+		//DumpPacket(outapp);
+		client->QueuePacket(outapp);
+	}
 }
 
 void ClientPacketFunctions::SendCharacterSheet ( Client* client ){

+ 92 - 0
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -3790,6 +3790,98 @@ 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], "tag") == 0)
+				{
+					int32 value = 0;
+					int16 tag_icon = 0;
+					// groundspawn has special exception -1 argument so it needs to be handled here
+					if(sep->arg[2] && sep->arg[3] && ((tag_icon = atoul(sep->arg[3])) > 0) || (strncasecmp(sep->arg[1], "groundspawn", 11) == 0)) {
+						value = atoul(sep->arg[2]);
+						if(strncasecmp(sep->arg[1], "faction", 7) == 0){
+							if(!value) {
+								client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid faction id.");
+								break;
+							}
+							if(!tag_icon) {
+								client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id.");
+								break;
+							}
+							client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_FACTION, value, "", tag_icon);
+							client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false);
+							client->Message(CHANNEL_COLOR_RED, "Adding faction id %u with tag icon %u.", value, tag_icon);
+						}
+						else if(strncasecmp(sep->arg[1], "spawngroup", 10) == 0){
+							if(value>0) {
+								value = 1;
+							}
+							if(!tag_icon) {
+								client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id.");
+								break;
+							}
+							client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, value, "", tag_icon);
+							client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false);
+							client->Message(CHANNEL_COLOR_RED, "Adding spawn group tag \"%s\" with tag icon %u.", (value == 1) ? "on" : "off", tag_icon);
+						}
+						else if(strncasecmp(sep->arg[1], "race", 5) == 0){
+							if(!value) {
+								client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid race id.");
+								break;
+							}
+							if(!tag_icon) {
+								client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id.");
+								break;
+							}
+							client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, value, "", tag_icon);
+							client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false);
+							client->Message(CHANNEL_COLOR_RED, "Adding race id %u with tag icon %u.", value, tag_icon);
+						}
+						else if(strncasecmp(sep->arg[1], "groundspawn", 11) == 0){
+							// one less argument value field not tag_icon for this one
+							if(!value) {
+								client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id.");
+								break;
+							}
+							client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_GROUNDSPAWN, 1, "", value);
+							client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false);
+							client->Message(CHANNEL_COLOR_RED, "Adding groundspawns with tag icon %u.", value);
+						}
+					}
+					else {
+						if(strncasecmp(sep->arg[1], "clear", 5) == 0){
+							client->GetPlayer()->ClearGMVisualFilters();
+							client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Tags Cleared!");
+							client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false);
+						}
+						else {
+						client->SimpleMessage(CHANNEL_COLOR_RED, "GM Tagging Missing Arguments:");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/gm tag [type] [value] [visual_icon_display]");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/gm tag clear - Clears all GM visual tags");
+
+						client->SimpleMessage(CHANNEL_COLOR_RED, "Visual Type Options:");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: faction, value: faction id of spawn(s)");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: spawngroup, value: 1 to show grouped, 0 to show not grouped");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: race, value: race id (either against base race or race id in spawn details)");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: groundspawn, value: (not used)");
+
+						client->SimpleMessage(CHANNEL_COLOR_RED, "Visual Icon Options:");
+
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, 
+						"1 = skull, 2 = shield half dark blue / half light blue, 3 = purple? star, 4 = yellow sword, 5 = red X, 6 = green flame");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, 
+						"7 = Number \"1\", 8 = Number \"2\", 9 = Number \"3\", 10 = Number \"4\", 11 = Number \"5\", 12 = Number \"6\"");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, 
+						"25 = shield, 26 = green plus, 27 = crossed swords, 28 = bow with arrow in it, 29 = light blue lightning bolt");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, 
+						"30 = bard instrument (hard to see), 31 = writ with shield, 32 = writ with green +, 33 = writ with crossed sword");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, 
+						"34 = writ with bow, 35 = writ with light blue lightning bolt, 36 = same as 30, 37 = party with crossed sword, shield and lightning bolt");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, 
+						"38 = shaking hands green background, 39 = shaking hands dark green background, unlocked keylock");
+						client->SimpleMessage(CHANNEL_COLOR_YELLOW, 
+						"40 = red aura icon with black shadow of person and big red aura, 41 = green aura icon with black shadow of person big green aura");
+						}
+					}
+				}
 				else if (strcmp(sep->arg[0], "regiondebug") == 0)
 				{
 					client->SetRegionDebug(onOff);

+ 2 - 1
EQ2/source/WorldServer/Entity.cpp

@@ -302,7 +302,7 @@ void Entity::MapInfoStruct()
 	get_int8_funcs["no_interrupt"] = l::bind(&InfoStruct::get_no_interrupt, &info_struct);
 
 	get_int8_funcs["interaction_flag"] = l::bind(&InfoStruct::get_interaction_flag, &info_struct);
-
+	get_int8_funcs["tag1"] = l::bind(&InfoStruct::get_tag1, &info_struct);
 
 /** SETS **/
 	set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1);
@@ -449,6 +449,7 @@ void Entity::MapInfoStruct()
 	set_int8_funcs["no_interrupt"] = l::bind(&InfoStruct::set_no_interrupt, &info_struct, l::_1);
 
 	set_int8_funcs["interaction_flag"] = l::bind(&InfoStruct::set_interaction_flag, &info_struct, l::_1);
+	set_int8_funcs["tag1"] = l::bind(&InfoStruct::set_tag1, &info_struct, l::_1);
 
 }
 

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

@@ -258,6 +258,7 @@ struct InfoStruct{
 		no_interrupt_ = 0;
 
 		interaction_flag_ = 0;
+		tag1_ = 0;
 	}
 
 
@@ -411,6 +412,7 @@ struct InfoStruct{
 		no_interrupt_ = oldStruct->get_no_interrupt();
 
 		interaction_flag_ = oldStruct->get_interaction_flag();
+		tag1_ = oldStruct->get_tag1();
 	}
 
 	//mutable std::shared_mutex mutex_;
@@ -574,6 +576,7 @@ struct InfoStruct{
 	int8	get_no_interrupt() { std::lock_guard<std::mutex> lk(classMutex); return no_interrupt_; }
 
 	int8	get_interaction_flag() { std::lock_guard<std::mutex> lk(classMutex); return interaction_flag_; }
+	int8	get_tag1() { std::lock_guard<std::mutex> lk(classMutex); return tag1_; }
 
 	void	set_name(std::string value) { std::lock_guard<std::mutex> lk(classMutex); name_ = value; }
 	
@@ -821,6 +824,7 @@ struct InfoStruct{
 	void	set_no_interrupt(int8 value) { std::lock_guard<std::mutex> lk(classMutex); no_interrupt_ = value; }
 
 	void	set_interaction_flag(int8 value) { std::lock_guard<std::mutex> lk(classMutex); interaction_flag_ = value; }
+	void	set_tag1(int8 value) { std::lock_guard<std::mutex> lk(classMutex); tag1_ = value; }
 
 	void	ResetEffects(Spawn* spawn)
 	{
@@ -991,6 +995,7 @@ private:
 	int8			no_interrupt_;
 
 	int8			interaction_flag_;
+	int8			tag1_;
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		classMutex;
 };

+ 116 - 17
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -164,6 +164,7 @@ int EQ2Emu_lua_PlayFlavor(lua_State* state) {
 	int32 key2 = lua_interface->GetInt32Value(state, 6);
 	Spawn* player = lua_interface->GetSpawn(state, 7);
 	int8 language = lua_interface->GetInt8Value(state, 8);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		const char* mp3 = 0;
 		const char* text = 0;
@@ -196,6 +197,7 @@ int EQ2Emu_lua_PlaySound(lua_State* state) {
 	float y = lua_interface->GetFloatValue(state, 4);
 	float z = lua_interface->GetFloatValue(state, 5);
 	Spawn* player = lua_interface->GetSpawn(state, 6);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn && sound_string.length() > 0) {
 		Client* client = 0;
 		if (player && player->IsPlayer())
@@ -217,6 +219,7 @@ int EQ2Emu_lua_SetRequiredQuest(lua_State* state) {
 	bool private_spawn = (lua_interface->GetInt8Value(state, 4) == 1);
 	bool continued_access = (lua_interface->GetInt8Value(state, 5) == 1);
 	int16 flag_override = lua_interface->GetInt16Value(state, 6);
+	lua_interface->ResetFunctionStack(state);
 
 	if (!spawn) {
 		lua_interface->LogError("%s: LUA SetRequiredQuest command error: spawn is not valid", lua_interface->GetScriptName(state));
@@ -249,6 +252,7 @@ int EQ2Emu_lua_SpawnSetByDistance(lua_State* state) {
 	float max_distance = lua_interface->GetFloatValue(state, 2);
 	string variable = lua_interface->GetStringValue(state, 3);
 	string value = lua_interface->GetStringValue(state, 4);
+	lua_interface->ResetFunctionStack(state);
 	if (max_distance > 0 && spawn && value.length() > 0 && variable.length() > 0 && spawn->GetZone())
 		spawn->GetZone()->SpawnSetByDistance(spawn, max_distance, variable, value);
 	return 0;
@@ -278,6 +282,7 @@ int	EQ2Emu_lua_PerformCameraShake(lua_State* state) {
 	}
 	float intensity = lua_interface->GetFloatValue(state, 2);
 	int8 direction = lua_interface->GetInt8Value(state, 3);
+	lua_interface->ResetFunctionStack(state);
 	PacketStruct* packet = configReader.getStruct("WS_PerformCameraShakeMsg", client->GetVersion());
 	if (packet) {
 		/* Client Intensity Logic (does not restrict service side, but expect .01 - 1.0 range)
@@ -302,6 +307,7 @@ int EQ2Emu_lua_KillSpawn(lua_State* state) {
 	Spawn* dead = lua_interface->GetSpawn(state);
 	Spawn* killer = lua_interface->GetSpawn(state, 2);
 	bool send_packet = (lua_interface->GetInt8Value(state, 3) == 1);
+	lua_interface->ResetFunctionStack(state);
 	if (dead && dead->Alive() && dead->GetZone())
 		dead->GetZone()->KillSpawn(false, dead, killer, send_packet);
 	return 0;
@@ -314,6 +320,7 @@ int EQ2Emu_lua_KillSpawnByDistance(lua_State* state) {
 	float max_distance = lua_interface->GetFloatValue(state, 2);
 	bool include_players = lua_interface->GetInt8Value(state, 3);
 	bool send_packet = (lua_interface->GetInt8Value(state, 4) == 1);
+	lua_interface->ResetFunctionStack(state);
 	if (max_distance > 0 && spawn && spawn->GetZone())
 		spawn->GetZone()->KillSpawnByDistance(spawn, max_distance, include_players, send_packet);
 	return 0;
@@ -324,6 +331,7 @@ int EQ2Emu_lua_Despawn(lua_State* state) {
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	int32 delay = lua_interface->GetInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn && spawn->GetZone())
 		spawn->GetZone()->Despawn(spawn, delay);
 	return 0;
@@ -334,6 +342,7 @@ int EQ2Emu_lua_ChangeHandIcon(lua_State* state) {
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	int8 displayHandIcon = lua_interface->GetInt8Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		spawn->info_changed = true;
 		spawn->SetShowHandIcon(displayHandIcon);
@@ -347,6 +356,7 @@ int EQ2Emu_lua_SetVisualFlag(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		spawn->vis_changed = true;
 		spawn->GetZone()->AddChangedSpawn(spawn);
@@ -360,6 +370,7 @@ int EQ2Emu_lua_SetInfoFlag(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		spawn->info_changed = true;
 		spawn->GetZone()->AddChangedSpawn(spawn);
@@ -425,6 +436,8 @@ int EQ2Emu_lua_SpawnSet(lua_State* state) {
 		index = lua_interface->GetInt8Value(state, 6);
 	}
 	
+	lua_interface->ResetFunctionStack(state);
+
 	int32 type = commands.GetSpawnSetType(variable);
 	if (type != 0xFFFFFFFF && value.length() > 0 && spawn)
 		commands.SetSpawnCommand(0, spawn, type, value.c_str(), !no_update, temporary_flag, nullptr, index);
@@ -436,6 +449,7 @@ int EQ2Emu_lua_GetSpawn(lua_State* state) {
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	int32 spawn_id = lua_interface->GetInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn && spawn_id > 0) {
 		Spawn* closest_spawn = spawn->GetZone()->GetClosestSpawn(spawn, spawn_id);
 		if (closest_spawn) {
@@ -451,6 +465,7 @@ int EQ2Emu_lua_GetSpawnFromList(lua_State* state) {
 		return 0;
 	vector<Spawn*>* spawnList = lua_interface->GetSpawnList(state);
 	int32 position = lua_interface->GetInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (spawnList) {
 		if (spawnList->size() > position) {
 			lua_interface->SetSpawnValue(state, spawnList->at(position));
@@ -468,6 +483,7 @@ int EQ2Emu_lua_GetSpawnListSize(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	vector<Spawn*>* spawnList = lua_interface->GetSpawnList(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawnList) {
 		return spawnList->size();
 	}
@@ -487,6 +503,7 @@ int EQ2Emu_lua_AddSpawnToSpawnList(lua_State* state) {
 		return 0;
 	vector<Spawn*>* spawnList = lua_interface->GetSpawnList(state);
 	Spawn* spawn = lua_interface->GetSpawn(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (spawnList) {
 		auto it = std::find(spawnList->begin(), spawnList->end(), spawn);
 		if (it == spawnList->end())
@@ -500,6 +517,7 @@ int EQ2Emu_lua_RemoveSpawnFromSpawnList(lua_State* state) {
 		return 0;
 	vector<Spawn*>* spawnList = lua_interface->GetSpawnList(state);
 	Spawn* spawn = lua_interface->GetSpawn(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (spawnList) {
 		auto it = std::find(spawnList->begin(), spawnList->end(), spawn);
 		if(it != spawnList->end())
@@ -513,6 +531,7 @@ int EQ2Emu_lua_GetSpawnListBySpawnID(lua_State* state) {
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	int32 spawn_id = lua_interface->GetInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		vector<Spawn*> spawns = spawn->GetZone()->GetSpawnsByID(spawn_id);
 		if (spawns.size() > 0) {
@@ -533,6 +552,7 @@ int EQ2Emu_lua_GetSpawnListByRailID(lua_State* state) {
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	sint64 rail_id = lua_interface->GetSInt64Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		vector<Spawn*> spawns = spawn->GetZone()->GetSpawnsByRailID(rail_id);
 		if (spawns.size() > 0) {
@@ -552,6 +572,7 @@ int EQ2Emu_lua_GetPassengerSpawnList(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		vector<Spawn*> spawns = spawn->GetPassengersOnRail();
 		if (spawns.size() > 0) {
@@ -571,6 +592,7 @@ int EQ2Emu_lua_GetVariableValue(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	string variable_name = lua_interface->GetStringValue(state);
+	lua_interface->ResetFunctionStack(state);
 	Variable* var = variables.FindVariable(variable_name);
 	if (var) {
 		lua_interface->SetStringValue(state, var->GetValue());
@@ -583,6 +605,7 @@ int EQ2Emu_lua_GetCoinMessage(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	int32 total_coins = lua_interface->GetInt32Value(state);
+	lua_interface->ResetFunctionStack(state);
 	if (total_coins == 0) {
 		lua_interface->SetStringValue(state, "0 copper");
 		return 1;
@@ -622,6 +645,7 @@ int EQ2Emu_lua_GetCoinMessage(lua_State* state) {
 int EQ2Emu_lua_GetSpawnByGroupID(lua_State* state) {
 	ZoneServer* zone = lua_interface->GetZone(state);
 	int32 group_id = lua_interface->GetInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 
 	if (zone) {
 		Spawn* spawn = zone->GetSpawnGroup(group_id);
@@ -636,6 +660,7 @@ int EQ2Emu_lua_GetSpawnByGroupID(lua_State* state) {
 int EQ2Emu_lua_GetSpawnByLocationID(lua_State* state) {
 	ZoneServer* zone = lua_interface->GetZone(state);
 	int32 location_id = lua_interface->GetInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (zone) {
 		Spawn* spawn = zone->GetSpawnByLocationID(location_id);
 		if (spawn) {
@@ -648,6 +673,7 @@ int EQ2Emu_lua_GetSpawnByLocationID(lua_State* state) {
 
 int EQ2Emu_lua_GetSpawnID(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		lua_interface->SetInt32Value(state, spawn->GetDatabaseID());
 		return 1;
@@ -657,6 +683,7 @@ int EQ2Emu_lua_GetSpawnID(lua_State* state) {
 
 int EQ2Emu_lua_GetSpawnGroupID(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		lua_interface->SetInt32Value(state, spawn->GetSpawnGroupID());
 		return 1;
@@ -698,6 +725,7 @@ int EQ2Emu_lua_AddSpawnToGroup(lua_State* state) {
 
 int EQ2Emu_lua_GetSpawnLocationID(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		lua_interface->SetInt32Value(state, spawn->GetSpawnLocationID());
 		return 1;
@@ -707,6 +735,7 @@ int EQ2Emu_lua_GetSpawnLocationID(lua_State* state) {
 
 int EQ2Emu_lua_GetSpawnLocationPlacementID(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		lua_interface->SetInt32Value(state, spawn->GetSpawnLocationPlacementID());
 		return 1;
@@ -717,6 +746,7 @@ int EQ2Emu_lua_GetSpawnLocationPlacementID(lua_State* state) {
 int EQ2Emu_lua_GetFactionAmount(lua_State* state) {
 	Player* player = (Player*)lua_interface->GetSpawn(state);
 	int32 faction_id = lua_interface->GetInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (player && player->IsPlayer() && faction_id > 0) {
 		lua_interface->SetSInt32Value(state, player->GetFactions()->GetFactionValue(faction_id));
 		return 1;
@@ -729,6 +759,7 @@ int EQ2Emu_lua_SetFactionID(lua_State* state) {
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	int32 value = lua_interface->GetInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		spawn->SetFactionID(value);
 	}
@@ -737,6 +768,7 @@ int EQ2Emu_lua_SetFactionID(lua_State* state) {
 
 int EQ2Emu_lua_GetFactionID(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		lua_interface->SetInt32Value(state, spawn->GetFactionID());
 		return 1;
@@ -746,6 +778,7 @@ int EQ2Emu_lua_GetFactionID(lua_State* state) {
 
 int EQ2Emu_lua_GetGender(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		lua_interface->SetInt32Value(state, spawn->GetGender());
 		return 1;
@@ -755,6 +788,7 @@ int EQ2Emu_lua_GetGender(lua_State* state) {
 
 int EQ2Emu_lua_GetTarget(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn && spawn->IsEntity() && ((Entity*)spawn)->GetTarget()) {
 		lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetTarget());
 		return 1;
@@ -770,6 +804,7 @@ int EQ2Emu_lua_PlayVoice(lua_State* state) {
 	int32 key1 = lua_interface->GetInt32Value(state, 3);
 	int32 key2 = lua_interface->GetInt32Value(state, 4);
 	Spawn* player = lua_interface->GetSpawn(state, 5);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn && mp3_string.length() > 0) {
 		Client* client = 0;
 		if (player && player->IsPlayer())
@@ -788,6 +823,7 @@ int EQ2Emu_lua_GetCurrentZoneSafeLocation(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		lua_interface->SetFloatValue(state, spawn->GetZone()->GetSafeX());
 		lua_interface->SetFloatValue(state, spawn->GetZone()->GetSafeY());
@@ -803,9 +839,11 @@ int EQ2Emu_lua_HasLootItem(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	if (spawn) {
 		int32 item_id = lua_interface->GetInt32Value(state, 2);
+		lua_interface->ResetFunctionStack(state);
 		lua_interface->SetBooleanValue(state, spawn->HasLootItemID(item_id));
 		return 1;
 	}
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -820,6 +858,7 @@ int EQ2Emu_lua_AddLootItem(lua_State* state) {
 			charges = 1;
 		((Entity*)spawn)->AddLootItem(item_id, charges);
 	}
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -832,6 +871,7 @@ int EQ2Emu_lua_RemoveLootItem(lua_State* state) {
 		Item* item = spawn->LootItem(item_id);
 		safe_delete(item);
 	}
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -843,6 +883,7 @@ int EQ2Emu_lua_AddLootCoin(lua_State* state) {
 		int32 val = lua_interface->GetInt32Value(state, 2);
 		spawn->AddLootCoins(val);
 	}
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -871,6 +912,7 @@ int EQ2Emu_lua_GiveLoot(lua_State* state) {
 			entity->AddLootCoins(coins);
 		safe_delete(items);
 	}
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -880,6 +922,7 @@ int EQ2Emu_lua_HasPendingLootItem(lua_State* state) {
 	Spawn* entity = lua_interface->GetSpawn(state);
 	Spawn* player = lua_interface->GetSpawn(state, 2);
 	int32 item_id = lua_interface->GetInt32Value(state, 3);
+	lua_interface->ResetFunctionStack(state);
 	if (entity && entity->IsEntity() && player && player->IsPlayer() && item_id > 0) {
 		lua_interface->SetBooleanValue(state, ((Player*)player)->HasPendingLootItem(entity->GetID(), item_id));
 		return 1;
@@ -892,6 +935,7 @@ int EQ2Emu_lua_HasPendingLoot(lua_State* state) {
 		return 0;
 	Spawn* entity = lua_interface->GetSpawn(state);
 	Spawn* player = lua_interface->GetSpawn(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (entity && player && player->IsPlayer()) {
 		lua_interface->SetBooleanValue(state, ((Player*)player)->HasPendingLootItems(entity->GetID()));
 		return 1;
@@ -905,6 +949,7 @@ int EQ2Emu_lua_CreateConversation(lua_State* state) {
 
 	vector<ConversationOption>* conversation = lua_interface->GetConversation(state);
 	safe_delete(conversation);
+	lua_interface->ResetFunctionStack(state);
 
 	conversation = new vector<ConversationOption>();
 	lua_interface->SetConversationValue(state, conversation);
@@ -922,6 +967,7 @@ int EQ2Emu_lua_AddConversationOption(lua_State* state) {
 		if (conv_option.option.length() > 0)
 			conversation->push_back(conv_option);
 	}
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -930,6 +976,7 @@ int EQ2Emu_lua_CloseConversation(lua_State* state) {
 		return 0;
 	Spawn* npc = lua_interface->GetSpawn(state);
 	Spawn* player = lua_interface->GetSpawn(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (npc && player && player->IsPlayer() && player->GetZone()) {
 		Client* client = player->GetZone()->GetClientBySpawn(player);
 		if (client) {
@@ -945,6 +992,7 @@ int EQ2Emu_lua_CloseItemConversation(lua_State* state) {
 		return 0;
 	Item* item = lua_interface->GetItem(state);
 	Spawn* player = lua_interface->GetSpawn(state, 2);
+	lua_interface->ResetFunctionStack(state);
 	if (item && player && player->IsPlayer() && player->GetZone()) {
 		Client* client = player->GetZone()->GetClientBySpawn(player);
 		if (client) {
@@ -971,6 +1019,8 @@ int EQ2Emu_lua_StartDialogConversation(lua_State* state) {
 	string mp3 = lua_interface->GetStringValue(state, 6);
 	int32 key1 = lua_interface->GetInt32Value(state, 7);
 	int32 key2 = lua_interface->GetInt32Value(state, 8);
+
+	lua_interface->ResetFunctionStack(state);
 	if (conversation && text.length() > 0 && (spawn || item) && player && player->IsPlayer()) {
 		Client* client = player->GetZone()->GetClientBySpawn(player);
 		if (client) {
@@ -1030,6 +1080,7 @@ int EQ2Emu_lua_StartConversation(lua_State* state) {
 	string mp3 = lua_interface->GetStringValue(state, 5);
 	int32 key1 = lua_interface->GetInt32Value(state, 6);
 	int32 key2 = lua_interface->GetInt32Value(state, 7);
+	lua_interface->ResetFunctionStack(state);
 	if (conversation && conversation->size() > 0 && text.length() > 0 && source && player && player->IsPlayer()) {
 		Client* client = source->GetZone()->GetClientBySpawn(player);
 		if (mp3.length() > 0)
@@ -1064,6 +1115,7 @@ int EQ2Emu_lua_SetLocationProximityFunction(lua_State* state) {
 	float max_variation = lua_interface->GetFloatValue(state, 5);
 	string in_range_function = lua_interface->GetStringValue(state, 6);
 	string leaving_range_function = lua_interface->GetStringValue(state, 7);
+	lua_interface->ResetFunctionStack(state);
 	if (zone && in_range_function.length() > 0)
 		zone->AddLocationProximity(x, y, z, max_variation, in_range_function, leaving_range_function);
 	return 0;
@@ -1077,6 +1129,7 @@ int EQ2Emu_lua_SetLootCoin(lua_State* state) {
 		int32 val = lua_interface->GetInt32Value(state, 2);
 		((Entity*)spawn)->SetLootCoins(val);
 	}
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -1084,6 +1137,7 @@ int EQ2Emu_lua_GetLootCoin(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn && spawn->IsEntity()) {
 		lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetLootCoins());
 		return 1;
@@ -1116,6 +1170,7 @@ int EQ2Emu_lua_IsPlayer(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		lua_interface->SetBooleanValue(state, spawn->IsPlayer());
 		return 1;
@@ -1127,6 +1182,7 @@ int EQ2Emu_lua_GetCharacterID(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn && spawn->IsPlayer()) {
 		lua_interface->SetInt32Value(state, ((Player*)spawn)->GetCharacterID());
 		return 1;
@@ -1181,6 +1237,7 @@ int EQ2Emu_lua_ClearRunningLocations(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
 		spawn->ClearRunningLocations();
 	}
@@ -1413,6 +1470,7 @@ int EQ2Emu_lua_AddItem(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	int32 item_id = lua_interface->GetInt32Value(state, 2);
 	int16 quantity = lua_interface->GetInt32Value(state, 3);
+	lua_interface->ResetFunctionStack(state);
 
 	// default of 1 quantity to add
 	if (quantity == 0)
@@ -1437,6 +1495,7 @@ int EQ2Emu_lua_SummonItem(lua_State* state) {
 	int32 item_id = lua_interface->GetInt32Value(state, 2);
 	bool send_messages = (lua_interface->GetInt8Value(state, 3) == 1);
 	string location = lua_interface->GetStringValue(state, 4);
+	lua_interface->ResetFunctionStack(state);
 
 
 	if (spawn && spawn->IsPlayer()) {
@@ -1465,6 +1524,7 @@ int EQ2Emu_lua_RemoveItem(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	int32 item_id = lua_interface->GetInt32Value(state, 2);
 	int16 quantity = lua_interface->GetInt16Value(state, 3);
+	lua_interface->ResetFunctionStack(state);
 
 	// default of 1 to remove
 	if (quantity == 0)
@@ -1492,6 +1552,7 @@ int EQ2Emu_lua_HasItem(lua_State* state) {
 	Spawn* player = lua_interface->GetSpawn(state);
 	int32 item_id = lua_interface->GetInt32Value(state, 2);
 	bool include_bank = lua_interface->GetInt8Value(state, 3);
+	lua_interface->ResetFunctionStack(state);
 	if (player && player->IsPlayer()) {
 		bool hasItem = false;
 		hasItem = ((Player*)player)->item_list.HasItem(item_id, include_bank);
@@ -1540,6 +1601,7 @@ int EQ2Emu_lua_Spawn(lua_State* state) {
 			if (scriptActive) {
 				zone->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN);
 			}
+			lua_interface->ResetFunctionStack(state);
 			lua_interface->SetSpawnValue(state, spawn);
 			return 1;
 		}
@@ -1552,6 +1614,7 @@ int EQ2Emu_lua_Spawn(lua_State* state) {
 			output = output.append("\t").append("Missing spawn_id.");
 		lua_interface->LogError("%s: Error in EQ2Emu_lua_Spawn - %s", lua_interface->GetScriptName(state), output.c_str());
 	}
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -1559,6 +1622,7 @@ int EQ2Emu_lua_GetZoneName(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	ZoneServer* zone = lua_interface->GetZone(state);
+	lua_interface->ResetFunctionStack(state);
 	if (zone) {
 		lua_interface->SetStringValue(state, zone->GetZoneName());
 		return 1;
@@ -1570,6 +1634,7 @@ int EQ2Emu_lua_GetZoneID(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	ZoneServer* zone = lua_interface->GetZone(state);
+	lua_interface->ResetFunctionStack(state);
 	if (zone) {
 		lua_interface->SetInt32Value(state, zone->GetZoneID());
 		return 1;
@@ -1595,6 +1660,7 @@ int EQ2Emu_lua_GetZone(lua_State* state) {
 				zone = spawn->GetZone();
 		}
 	}
+	lua_interface->ResetFunctionStack(state);
 	if (zone) {
 		lua_interface->SetZoneValue(state, zone);
 		return 1;
@@ -1625,6 +1691,7 @@ int EQ2Emu_lua_AddHate(lua_State* state) {
 		else if (npc && npc->IsNPC() && npc->GetZone())
 			((NPC*)npc)->AddHate((Entity*)entity, amount);
 	}
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -1666,6 +1733,7 @@ int EQ2Emu_lua_Zone(lua_State* state) {
 	}
 	else
 		lua_interface->LogError("%s: Error in EQ2Emu_lua_Zone: invalid zone or spawn input.", lua_interface->GetScriptName(state));
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -1676,6 +1744,7 @@ int EQ2Emu_lua_AddSpawnAccess(lua_State* state) {
 	Spawn* spawn2 = lua_interface->GetSpawn(state, 2);
 	if (spawn && spawn2)
 		spawn->AddAllowAccessSpawn(spawn2);
+	lua_interface->ResetFunctionStack(state);
 	return 0;
 }
 
@@ -1687,6 +1756,7 @@ int EQ2Emu_lua_CastSpell(lua_State* state) {
 	int8 spell_tier = lua_interface->GetInt8Value(state, 3);
 	Spawn* caster = lua_interface->GetSpawn(state, 4);
 	int16 custom_cast_time = lua_interface->GetInt16Value(state, 5);
+	lua_interface->ResetFunctionStack(state);
 
 	if (!target) {
 		lua_interface->LogError("%s: LUA CastSpell command error: target is not a valid spawn", lua_interface->GetScriptName(state));
@@ -1753,6 +1823,7 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
 		}
 		i++;
 	}
+	lua_interface->ResetFunctionStack(state);
 	if (caster && caster->IsEntity()) {
 		bool race_match = false;
 		bool success = false;
@@ -10282,14 +10353,31 @@ int EQ2Emu_lua_Evac(lua_State* state) {
 		target->SetZ(z);
 		target->SetHeading(h);
 
-		target->SetSpawnOrigX(target->GetX());
-		target->SetSpawnOrigY(target->GetY());
-		target->SetSpawnOrigZ(target->GetZ());
-		target->SetSpawnOrigHeading(target->GetHeading());
+		target->SetSpawnOrigX(x);
+		target->SetSpawnOrigY(y);
+		target->SetSpawnOrigZ(z);
+		target->SetSpawnOrigHeading(h);
 
 		if (target->IsPlayer()) {
 			Client* client = target->GetZone()->GetClientBySpawn(target);
 			if (client) {
+				client->GetCurrentZone()->ClearHate(client->GetPlayer());
+
+				client->SetReloadingZone(true);
+				target->SetX(x);
+				target->SetY(y);
+				target->SetZ(z);
+				target->SetHeading(h);
+
+				target->SetSpawnOrigX(x);
+				target->SetSpawnOrigY(y);
+				target->SetSpawnOrigZ(z);
+				target->SetSpawnOrigHeading(h);
+
+				target->SetAppearancePosition(x,y,z);
+				
+				client->SetZoningCoords(x,y,z,h);
+
 				PacketStruct* packet = configReader.getStruct("WS_TeleportWithinZone", client->GetVersion());
 				if (packet)
 				{
@@ -10299,6 +10387,10 @@ int EQ2Emu_lua_Evac(lua_State* state) {
 					client->QueuePacket(packet->serialize());
 					safe_delete(packet);
 				}
+
+					client->GetCurrentZone()->RemoveSpawn(target, false, false, true, true);
+							
+					client->GetPlayer()->SetSpawnSentState(target, SpawnState::SPAWN_STATE_SENT);
 			}
 		}
 	}
@@ -10316,32 +10408,39 @@ int EQ2Emu_lua_Evac(lua_State* state) {
 			if (!target2)
 				continue;
 
-			target2->SetX(x);
-			target2->SetY(y);
-			target2->SetZ(z);
-			target2->SetHeading(h);
-
-			target2->SetSpawnOrigX(target2->GetX());
-			target2->SetSpawnOrigY(target2->GetY());
-			target2->SetSpawnOrigZ(target2->GetZ());
-			target2->SetSpawnOrigHeading(target2->GetHeading());
-
 			if (target2->IsPlayer()) {
 				Client* client = target2->GetZone()->GetClientBySpawn(target2);
 				if (client) {
+					client->GetCurrentZone()->ClearHate(client->GetPlayer());
+					
+					client->SetReloadingZone(true);
+					target2->SetX(x);
+					target2->SetY(y);
+					target2->SetZ(z);
+					target2->SetHeading(h);
+
+					target2->SetSpawnOrigX(x);
+					target2->SetSpawnOrigY(y);
+					target2->SetSpawnOrigZ(z);
+					target2->SetSpawnOrigHeading(h);
+
+					target2->SetAppearancePosition(x,y,z);
+					
+					client->SetZoningCoords(x,y,z,h);
+					
 					PacketStruct* packet = configReader.getStruct("WS_TeleportWithinZone", client->GetVersion());
 					if (packet)
 					{
-						client->SetReloadingZone(true);
 						packet->setDataByName("x", x);
 						packet->setDataByName("y", y);
 						packet->setDataByName("z", z);
 						client->QueuePacket(packet->serialize());
 						safe_delete(packet);
 					}
+
+					client->GetCurrentZone()->RemoveSpawn(target2, false, false, true, true);
 					
-					client->GetCurrentZone()->ClearHate(client->GetPlayer());
-					client->GetCurrentZone()->RemoveSpawn(client->GetPlayer(), false, false, false, false);
+					client->GetPlayer()->SetSpawnSentState(target2, SpawnState::SPAWN_STATE_SENT);
 				}
 			}
 		}

+ 71 - 2
EQ2/source/WorldServer/Player.cpp

@@ -3491,6 +3491,9 @@ bool Player::HasActiveSpellEffect(Spell* spell, Spawn* target){
 
 void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version)
 {
+	if(GetClient() && GetClient()->IsReloadingZone())
+		return;
+
 	LogWrite(PLAYER__DEBUG, 7, "Player", "Enter: %s", __FUNCTION__); // trace
 
 	// XML structs may be to slow to use in this portion of the code as a single
@@ -3811,6 +3814,10 @@ bool Player::SetSpawnSentState(Spawn* spawn, SpawnState state) {
 	if(index > 0 && (state == SpawnState::SPAWN_STATE_SENDING)) {
 		LogWrite(PLAYER__WARNING, 0, "Player", "Spawn ALREADY INDEXED for Player %s (%u).  Spawn %s (index %u) attempted to state %u.", 
 			GetName(), GetCharacterID(), spawn->GetName(), index, state);
+			if(GetClient()->IsReloadingZone()) {
+				spawn_packet_sent.insert(make_pair(spawn->GetID(), state));
+				val = false;
+			}
 		// we don't do anything this spawn is already populated by the player 
 	}
 	else {
@@ -3864,11 +3871,15 @@ void Player::CheckSpawnStateQueue() {
 					break;
 				}
 				case SpawnState::SPAWN_STATE_REMOVING: {
+					if(itr->first == GetID() && GetClient()->IsReloadingZone()) {
+						itr->second->spawn_state_timer.Disable();
+						continue;
+					}
+					
 					if(itr->second->index_id) {
 						PacketStruct* packet = packet = configReader.getStruct("WS_DestroyGhostCmd", GetClient()->GetVersion());
 						packet->setDataByName("spawn_index", itr->second->index_id);
 						packet->setDataByName("delete", 1);	
-						
 						GetClient()->QueuePacket(packet->serialize());
 						safe_delete(packet);
 					}
@@ -3884,6 +3895,11 @@ void Player::CheckSpawnStateQueue() {
 					safe_delete(sr);
 					break;
 				}
+				default: {
+					// reset
+					itr->second->spawn_state_timer.Disable();
+					break;
+				}
 			}
 		}
 		else
@@ -6677,8 +6693,21 @@ void Player::RemoveTargetInvisHistory(int32 targetID)
 
 bool Player::SetSpawnMap(Spawn* spawn)
 {
-	if(!client->GetPlayer()->SetSpawnSentState(spawn, SpawnState::SPAWN_STATE_SENDING))
+	if(!client->GetPlayer()->SetSpawnSentState(spawn, SpawnState::SPAWN_STATE_SENDING)) {
+		/*index_mutex.writelock(__FUNCTION__, __LINE__);
+		spawn_id += 1;
+		if (spawn_index == 255)
+			spawn_index += 1; //just so we dont have to worry about overloading
+
+		map<int32, Spawn*>::iterator itr = player_spawn_id_map.find(player_spawn_reverse_id_map[this]);
+		if(itr != player_spawn_id_map.end()) {
+			player_spawn_id_map.erase(itr);
+		}
+		player_spawn_id_map[spawn_id] = spawn;
+		player_spawn_reverse_id_map[this] = spawn_id;
+		index_mutex.releasewritelock(__FUNCTION__, __LINE__);*/
 		return false;
+	}
 
 	index_mutex.writelock(__FUNCTION__, __LINE__);
 	spawn_id += 1;
@@ -7008,4 +7037,44 @@ bool Player::SerializeItemPackets(EquipmentItemList* equipList, vector<EQ2Packet
 		LogWrite(PLAYER__ERROR, 0, "Player", "failed to add item to item_list");
 	}
 	return false;
+}
+
+void Player::AddGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, int16 visual_tag) {
+	if(MatchGMVisualFilter(filter_type, filter_value, filter_search_str) > 0)
+		return;
+
+	vis_mutex.writelock(__FUNCTION__, __LINE__);
+	GMTagFilter filter;
+	filter.filter_type = filter_type;
+	filter.filter_value = filter_value;
+	memset(filter.filter_search_criteria, 0, sizeof(filter.filter_search_criteria));
+	if(filter_search_str)
+		memcpy(&filter.filter_search_criteria, filter_search_str, strnlen(filter_search_str, sizeof(filter.filter_search_criteria)));
+
+	filter.visual_tag = visual_tag;
+	gm_visual_filters.push_back(filter);
+	vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
+}
+
+int16 Player::MatchGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, bool in_vismutex_lock) {
+	if(!in_vismutex_lock)
+		vis_mutex.readlock(__FUNCTION__, __LINE__);
+	int16 tag_id = 0;
+	vector<GMTagFilter>::iterator itr = gm_visual_filters.begin();
+	for(;itr != gm_visual_filters.end();itr++) {
+		if(itr->filter_type == filter_type && itr->filter_value == filter_value) {
+			if(filter_search_str && !strcasecmp(filter_search_str, itr->filter_search_criteria)) {
+				tag_id = itr->visual_tag;
+				break;
+			}
+		}
+	}
+	if(!in_vismutex_lock)
+		vis_mutex.releasereadlock(__FUNCTION__, __LINE__);
+	return tag_id;
+}
+void Player::ClearGMVisualFilters() {
+	vis_mutex.writelock(__FUNCTION__, __LINE__);
+	gm_visual_filters.clear();
+	vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
 }

+ 21 - 1
EQ2/source/WorldServer/Player.h

@@ -167,6 +167,20 @@ struct SpellBookEntry{
 	bool visible;
 };
 
+struct GMTagFilter {
+	int32	filter_type;
+	int32	filter_value;
+	char	filter_search_criteria[256];
+	int16	visual_tag;
+};
+
+enum GMTagFilterType {
+	GMFILTERTYPE_NONE=0,
+	GMFILTERTYPE_FACTION=1,
+	GMFILTERTYPE_SPAWNGROUP=2,
+	GMFILTERTYPE_RACE=3,
+	GMFILTERTYPE_GROUNDSPAWN=4
+};
 enum SpawnState{
 	SPAWN_STATE_NONE=0,
 	SPAWN_STATE_SENDING=1,
@@ -1009,7 +1023,12 @@ public:
 	}
 	
 	bool SerializeItemPackets(EquipmentItemList* equipList, vector<EQ2Packet*>* packets, Item* item, int16 version, Item* to_item = 0);
+
+	void AddGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, int16 visual_tag);
+	int16 MatchGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, bool in_vismutex_lock = false);
+	void ClearGMVisualFilters();
 	Mutex MPlayerQuests;
+	float   pos_packet_speed;
 private:
 	bool reset_mentorship;
 	bool range_attack;
@@ -1053,7 +1072,6 @@ private:
 	map<string, int8>	friend_list;
 	map<string, int8>	ignore_list;
 	bool                pending_deletion;
-	float               pos_packet_speed;
 	PlayerControlFlags  control_flags;
 
 	map<int32, bool>	target_invis_history;
@@ -1139,6 +1157,8 @@ private:
 
 	bool all_spells_locked;
 	Timer lift_cooldown;
+
+	vector<GMTagFilter> gm_visual_filters;
 };
 #pragma pack()
 #endif

+ 36 - 0
EQ2/source/WorldServer/Spawn.cpp

@@ -32,11 +32,13 @@
 #include "LuaInterface.h"
 #include "Bots/Bot.h"
 #include "Zone/raycast_mesh.h"
+#include "RaceTypes/RaceTypes.h"
 
 extern ConfigReader configReader;
 extern RuleManager rule_manager;
 extern World world;
 extern ZoneList zone_list;
+extern MasterRaceTypeList race_types_list;
 
 Spawn::Spawn(){ 
 	loot_coins = 0;
@@ -252,6 +254,20 @@ void Spawn::InitializeVisPacketData(Player* player, PacketStruct* vis_packet) {
 	if (IsPlayer())
 		appearance.pos.grid_id = 0xFFFFFFFF;
 
+	int8 tag_icon = 0;
+
+	int32 tmp_id = 0;
+	if(faction_id && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_FACTION, faction_id, "", true)) > 0);
+	else if(IsGroundSpawn() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_GROUNDSPAWN, 1, "", true)) > 0);
+	else if((this->GetSpawnGroupID() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, 1, "", true)) > 0) ||
+			(!this->GetSpawnGroupID() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, 0, "", true)) > 0));
+	else if((this->GetRace() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, GetRace(), "", true)) > 0));
+	else if(((tmp_id = race_types_list.GetRaceType(GetModelType()) > 0) && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, tmp_id, "", true)) > 0));
+	else if(((tmp_id = race_types_list.GetRaceBaseType(GetModelType()) > 0) && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, tmp_id, "", true)) > 0));
+	else if(IsEntity() && (tag_icon = ((Entity*)this)->GetInfoStruct()->get_tag1()) > 0);
+	
+	vis_packet->setDataByName("tag1", tag_icon);
+
 	if (IsPlayer())
 		vis_packet->setDataByName("player", 1);
 	if (version <= 546) {
@@ -4281,4 +4297,24 @@ vector<Spawn*> Spawn::GetPassengersOnRail() {
 	}
 	m_RailMutex.unlock();
 	return tmp_list;
+}
+
+void Spawn::SetAppearancePosition(float x, float y, float z) {
+	appearance.pos.X = x;
+	appearance.pos.Y = y;
+	appearance.pos.Z = z;
+	appearance.pos.X2 = appearance.pos.X;
+	appearance.pos.Y2 = appearance.pos.Y;
+	appearance.pos.Z2 = appearance.pos.Z;
+	appearance.pos.X3 = appearance.pos.X;
+	appearance.pos.Y3 = appearance.pos.Y;
+	appearance.pos.Z3 = appearance.pos.Z;
+		
+	SetSpeedX(0);
+	SetSpeedY(0);
+	SetSpeedZ(0);
+	if(IsPlayer()) {
+		((Player*)this)->SetSideSpeed(0);
+		((Player*)this)->pos_packet_speed = 0;
+	}
 }

+ 2 - 0
EQ2/source/WorldServer/Spawn.h

@@ -1250,6 +1250,8 @@ public:
 	void RemoveRailPassenger(int32 char_id);
 	vector<Spawn*> GetPassengersOnRail();
 
+	void SetAppearancePosition(float x, float y, float z);
+	
 	void SetOmittedByDBFlag(bool val) { is_omitted_by_db_flag = val; }
 	bool IsOmittedByDBFlag() { return is_omitted_by_db_flag; }
 

+ 69 - 32
EQ2/source/WorldServer/World.cpp

@@ -542,49 +542,58 @@ void ZoneList::Remove(ZoneServer* zone) {
 	zlist.remove(zone);
 	MZoneList.releasewritelock(__FUNCTION__, __LINE__);
 }
-ZoneServer* ZoneList::Get(const char* zone_name, bool loadZone) {
+ZoneServer* ZoneList::Get(const char* zone_name, bool loadZone, bool skip_existing_zones) {
 	list<ZoneServer*>::iterator zone_iter;
 	ZoneServer* tmp = 0;
 	ZoneServer* ret = 0;
-	MZoneList.readlock(__FUNCTION__, __LINE__);
-	for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){
-		tmp = *zone_iter;
-		if (!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && strlen(zone_name) == strlen(tmp->GetZoneName()) && 
-			strncasecmp(tmp->GetZoneName(), zone_name, strlen(zone_name))==0){
-			if(tmp->NumPlayers() < 30 || tmp->IsCityZone())
-				ret = tmp;
-			break;
+
+	if(!skip_existing_zones) {
+		MZoneList.readlock(__FUNCTION__, __LINE__);
+		for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){
+			tmp = *zone_iter;
+			if (!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && strlen(zone_name) == strlen(tmp->GetZoneName()) && 
+				strncasecmp(tmp->GetZoneName(), zone_name, strlen(zone_name))==0){
+				if(tmp->NumPlayers() < 30 || tmp->IsCityZone())
+					ret = tmp;
+				break;
+			}
 		}
-	}
 
-	MZoneList.releasereadlock(__FUNCTION__, __LINE__);
+		MZoneList.releasereadlock(__FUNCTION__, __LINE__);
+	}
 
 	if(!ret )
 	{
 		if ( loadZone )
 		{
-			ret = new ZoneServer(zone_name);
+			ret = new ZoneServer(zone_name, true);
 			database.LoadZoneInfo(ret);
 			ret->Init();
 		}
 	}
+	else if(ret && loadZone) {
+		ret->IncrementIncomingClients();
+	}
 	return ret;
 }
 
-ZoneServer* ZoneList::Get(int32 id, bool loadZone) {
+ZoneServer* ZoneList::Get(int32 id, bool loadZone, bool skip_existing_zones) {
 	list<ZoneServer*>::iterator zone_iter;
 	ZoneServer* tmp = 0;
 	ZoneServer* ret = 0;
-	MZoneList.readlock(__FUNCTION__, __LINE__);
-	for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){
-		tmp = *zone_iter;
-		if(!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && tmp->GetZoneID() == id){
-			if(tmp->NumPlayers() < 30 || tmp->IsCityZone())
-				ret = tmp;
-			break;
+	if(!skip_existing_zones) {
+		MZoneList.readlock(__FUNCTION__, __LINE__);
+		for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){
+			tmp = *zone_iter;
+			if(!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && tmp->GetZoneID() == id){
+				if(tmp->NumPlayers() < 30 || tmp->IsCityZone())
+					ret = tmp;
+				break;
+			}
 		}
+		MZoneList.releasereadlock(__FUNCTION__, __LINE__);
 	}
-	MZoneList.releasereadlock(__FUNCTION__, __LINE__);
+
 	if(ret)
 		tmp = ret;
 	else if (loadZone) {
@@ -618,22 +627,25 @@ void ZoneList::SendZoneList(Client* client) {
 	MZoneList.releasereadlock(__FUNCTION__, __LINE__);
 }
 
-ZoneServer* ZoneList::GetByInstanceID(int32 id, int32 zone_id) {
+ZoneServer* ZoneList::GetByInstanceID(int32 id, int32 zone_id, bool skip_existing_zones) {
 	list<ZoneServer*>::iterator zone_iter;
 	ZoneServer* tmp = 0;
 	ZoneServer* ret = 0;
-	MZoneList.readlock(__FUNCTION__, __LINE__);
-	if ( id > 0 )
-	{
-		for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){
-			tmp = *zone_iter;
-			if(tmp->GetInstanceID() == id){
-					ret = tmp;
-					break;
+	if(!skip_existing_zones) {
+		MZoneList.readlock(__FUNCTION__, __LINE__);
+		if ( id > 0 )
+		{
+			for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){
+				tmp = *zone_iter;
+				if(tmp->GetInstanceID() == id){
+						ret = tmp;
+						break;
+				}
 			}
 		}
+		MZoneList.releasereadlock(__FUNCTION__, __LINE__);
 	}
-	MZoneList.releasereadlock(__FUNCTION__, __LINE__);
+
 	if(ret)
 		tmp = ret;
 	else if ( zone_id > 0 ){
@@ -705,6 +717,31 @@ bool ZoneList::ClientConnected(int32 account_id){
 	return ret;
 }
 
+void ZoneList::RemoveClientZoneReference(ZoneServer* zone){
+	map<string, Client*>::iterator itr;
+	MClientList.lock();
+	for(itr=client_map.begin(); itr != client_map.end(); itr++){
+		if(itr->second) {
+			if(itr->second->GetCurrentZone() == zone) {
+				itr->second->SetCurrentZone(nullptr);
+			}
+			if(itr->second->GetZoningDestination() == zone) {
+				itr->second->SetZoningDestination(nullptr);
+			}
+		}
+	}
+	MClientList.unlock();
+	
+	list<ZoneServer*>::iterator zone_iter;
+	ZoneServer* tmp = 0;
+	MZoneList.readlock(__FUNCTION__, __LINE__);
+	for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){
+		tmp = *zone_iter;
+		if(tmp)
+			tmp->RemoveClientsFromZone(zone);
+	}
+	MZoneList.releasereadlock(__FUNCTION__, __LINE__);
+}
 void ZoneList::ReloadClientQuests(){
 	list<ZoneServer*>::iterator zone_iter;
 	ZoneServer* tmp = 0;
@@ -2135,7 +2172,7 @@ int32 World::LoadItemBlueStats() {
 	return count;
 }
 
-sint64 World::newValue = 27;
+sint64 World::newValue = 0;
 
 sint16 World::GetItemStatAOMValue(sint16 subtype) {
 	sint16 tmp_subtype = subtype;

+ 4 - 3
EQ2/source/WorldServer/World.h

@@ -405,9 +405,9 @@ class ZoneList {
 	
 	void Add(ZoneServer* zone);
 	void Remove(ZoneServer* zone);
-	ZoneServer*	Get(int32 id, bool loadZone = true);
-	ZoneServer* Get(const char* zone_name, bool loadZone=true);
-	ZoneServer* GetByInstanceID(int32 id, int32 zone_id=0);
+	ZoneServer*	Get(int32 id, bool loadZone = true, bool skip_existing_zones = false);
+	ZoneServer* Get(const char* zone_name, bool loadZone=true, bool skip_existing_zones = false);
+	ZoneServer* GetByInstanceID(int32 id, int32 zone_id=0, bool skip_existing_zones = false);
 
 	/// <summary>Get the instance for the given zone id with the lowest population</summary>
 	/// <param name='zone_id'>The id of the zone to look up</param>
@@ -476,6 +476,7 @@ class ZoneList {
 		MClientList.unlock();
 	}
 	bool ClientConnected(int32 account_id);
+	void RemoveClientZoneReference(ZoneServer* zone);
 	void ReloadClientQuests();
 	bool DepopFinished();
 	void Depop();

+ 73 - 12
EQ2/source/WorldServer/client.cpp

@@ -150,6 +150,11 @@ Client::Client(EQStream* ieqs) : pos_update(125), quest_pos_timer(2000), lua_deb
 	connected = false;
 	camp_timer = 0;
 	client_zoning = false;
+	zoning_id = 0;
+	zoning_x = 0;
+	zoning_y = 0;
+	zoning_z = 0;
+	zoning_instance_id = 0;
 	player_pos_changed = false;
 	++numclients;
 	if (world.GetServerStatisticValue(STAT_SERVER_MOST_CONNECTIONS) < numclients)
@@ -210,6 +215,7 @@ Client::Client(EQStream* ieqs) : pos_update(125), quest_pos_timer(2000), lua_deb
 	hasSentTempPlacementSpawn = false;
 	spawn_removal_timer.Start();
 	disable_save = false;
+	SetZoningDestination(nullptr);
 }
 
 Client::~Client() {
@@ -694,11 +700,6 @@ void Client::HandlePlayerRevive(int32 point_id)
 
 void Client::SendCharInfo() {
 	EQ2Packet* app;
-
-	if(IsReloadingZone()){
-		SetReloadingZone(false);
-	}
-
 	
 	player->SetEquippedItemAppearances();
 
@@ -740,7 +741,15 @@ void Client::SendCharInfo() {
 	ClientPacketFunctions::SendLoginCommandMessages(this);
 
 	GetCurrentZone()->AddSpawn(player);
-	
+	if(IsReloadingZone() && (zoning_x || zoning_y || zoning_z)) {
+			GetPlayer()->SetX(zoning_x);
+			GetPlayer()->SetY(zoning_y);
+			GetPlayer()->SetZ(zoning_z);
+			GetPlayer()->SetHeading(zoning_h);
+
+			EQ2Packet* packet = GetPlayer()->Move(zoning_x, zoning_y, zoning_z, GetVersion(), zoning_h);		
+			QueuePacket(packet);
+	}
 	//SendCollectionList();
 	Guild* guild = player->GetGuild();
 	if (guild)
@@ -805,6 +814,9 @@ void Client::SendCharInfo() {
 	if (zone_script && lua_interface)
 		lua_interface->RunZoneScript(zone_script, "player_entry", GetCurrentZone(), GetPlayer());
 	this->client_zoning = false;
+	this->zoning_id = 0;
+	this->zoning_instance_id = 0;
+	SetZoningDestination(nullptr);
 	if (player->GetHP() < player->GetTotalHP() || player->GetPower() < player->GetTotalPower())
 		GetCurrentZone()->AddDamagedSpawn(player);
 
@@ -1503,7 +1515,38 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		}
 		else
 		{
+			if(zoning_destination)
+				SetCurrentZone(zoning_destination);
 			LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ReadyToZoneMsg", opcode, opcode);
+			bool succeed_override_zone = true;
+			if(!GetCurrentZone()) {
+				LogWrite(WORLD__ERROR, 0, "World", "OP_ReadyToZone: Player %s attempting to zone and zone is not there!  Emergency boot!", player->GetName());
+				if(zoning_instance_id) {
+					ZoneServer* zone = zone_list.GetByInstanceID(zoning_instance_id, zoning_id, true);
+					if(!zone) {
+						LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Emergency boot failed for %s, unable to get zoneserver instance id %u zone id %u.", player->GetName(), zoning_instance_id, zoning_id);
+						succeed_override_zone = false;
+					}
+					else {
+					SetCurrentZone(zone);
+					LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Unique instance has been created for %s through emergency boot!", player->GetName());
+
+					}
+				}
+				else if(zoning_id) {
+					
+					ZoneServer* zone = zone_list.Get(zoning_id, true, true);
+					if(!zone) {
+						LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Emergency boot failed for %s, unable to get zoneserver zone id %u.", player->GetName(), zoning_id);
+						succeed_override_zone = false;
+					}
+					else {
+					SetCurrentZone(zone);
+					LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Unique zone has been created for %s through emergency boot!", player->GetName());
+					}
+				}
+			}
+			
 			if (client_zoning)
 				LogWrite(WORLD__INFO, 0, "World", "OP_ReadyToZone: Player %s zoning to %s", player->GetName(), GetCurrentZone()->GetZoneName());
 			else
@@ -1648,12 +1691,21 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		if (packet) {
 			packet->LoadPacketData(app->pBuffer, app->size);
 			EQ2_16BitString str = packet->getType_EQ2_16BitString_ByName("signal");
-			if (str.size > 0) {
-				if (strcmp(str.data.c_str(), "sys_client_avatar_ready") == 0) {
+			if (strcmp(str.data.c_str(), "sys_client_avatar_ready") == 0) {
 					LogWrite(CCLIENT__DEBUG, 0, "Client", "Client '%s' (%u) is ready for spawn updates.", GetPlayer()->GetName(), GetPlayer()->GetCharacterID());
+					SetReloadingZone(false);
+					if(GetPlayer()->IsDeletedSpawn()) {
+						GetPlayer()->SetDeletedSpawn(false);
+					}
+					ResetZoningCoords();
 					SetReadyForUpdates();
 					GetPlayer()->SendSpawnChanges(true);
 					ProcessStateCommands();
+					GetPlayer()->changed = true;
+					GetPlayer()->info_changed = true;
+					GetPlayer()->vis_changed = true;
+					player_pos_changed = true;
+					GetPlayer()->AddChangedZoneSpawn();
 				}
 				else {
 					LogWrite(CCLIENT__WARNING, 0, "Client", "Player %s reported SysClient/SignalMsg state %s.", GetPlayer()->GetName(), str.data.c_str());
@@ -1663,7 +1715,6 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 				{
 					lua_interface->RunZoneScript(zone_script, "signal_changed", player->GetZone(), player, 0, str.data.c_str());
 				}
-			}
 		}
 		break;
 	}
@@ -3011,7 +3062,7 @@ bool Client::Process(bool zone_process) {
 		delete app;
 	}
 
-	if (GetCurrentZone() && GetCurrentZone()->GetSpawnByID(GetPlayer()->GetID()) && should_load_spells) {
+	if (GetCurrentZone() && !GetCurrentZone()->IsLoading() && GetCurrentZone()->GetSpawnByID(GetPlayer()->GetID()) && should_load_spells) {
 		player->ApplyPassiveSpells();
 		//database.LoadCharacterActiveSpells(player);
 		player->UnlockAllSpells(true);
@@ -4044,8 +4095,11 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords, bool is_spell) {
 		LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone Request Denied! No 'new_zone' value");
 		return;
 	}
-
+	
 	client_zoning = true;
+	zoning_id = new_zone->GetZoneID();
+	zoning_instance_id = new_zone->GetInstanceID();
+	
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting player Resurrecting to 'true'", __FUNCTION__);
 	player->SetResurrecting(true);
 
@@ -4063,8 +4117,9 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords, bool is_spell) {
 	GetCurrentZone()->RemoveSpawn(player, false, true, true, true, !is_spell);
 
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting zone to '%s'...", __FUNCTION__, new_zone->GetZoneName());
+	SetZoningDestination(new_zone);
 	SetCurrentZone(new_zone);
-
+	
 	if (player->GetGroupMemberInfo())
 	{
 		LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Player in group, updating group info...", __FUNCTION__);
@@ -4091,6 +4146,12 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords, bool is_spell) {
 		player->SetY(GetCurrentZone()->GetSafeY());
 		player->SetZ(GetCurrentZone()->GetSafeZ());
 		player->SetHeading(GetCurrentZone()->GetSafeHeading());
+			
+		SetZoningCoords(GetCurrentZone()->GetSafeX(), GetCurrentZone()->GetSafeY(), 
+						GetCurrentZone()->GetSafeZ(), GetCurrentZone()->GetSafeHeading());
+	}
+	else {
+		ResetZoningCoords();
 	}
 
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Saving Player info...", __FUNCTION__);

+ 23 - 0
EQ2/source/WorldServer/client.h

@@ -219,6 +219,10 @@ public:
 		current_zone = zone;
 		player->SetZone(zone, GetVersion());
 	}
+	void	SetZoningDestination(ZoneServer* zone) {
+		zoning_destination = zone;
+	}
+	ZoneServer* GetZoningDestination() { return zoning_destination; }
 	Player*	GetPlayer(){ return player; }
 	EQStream*	getConnection(){ return eqs; }
 
@@ -524,6 +528,18 @@ public:
 
 	void	DisableSave() { disable_save = true; }
 	bool	IsSaveDisabled() { return disable_save; }
+	void	ResetZoningCoords() { 
+		zoning_x = 0;
+		zoning_y = 0;
+		zoning_z = 0;
+		zoning_h = 0;
+	}
+	void	SetZoningCoords(float x, float y, float z, float h) { 
+		zoning_x = x;
+		zoning_y = y;
+		zoning_z = z;
+		zoning_h = h;
+	}
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -587,6 +603,13 @@ private:
 	bool	seencharsel;
 	bool	connected_to_zone;
 	bool	client_zoning;
+	int32	zoning_id;
+	int32	zoning_instance_id;
+	ZoneServer* zoning_destination;
+	float	zoning_x;
+	float	zoning_y;
+	float	zoning_z;
+	float	zoning_h;
 	bool	firstlogin;
 	bool	new_client_login;
 	Timer	pos_update;

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

@@ -119,7 +119,13 @@ extern MasterSkillList master_skill_list;
 int32 MinInstanceID = 1000;
 
 // JA: Moved most default values to Rules and risky initializers to ZoneServer::Init() - 2012.12.07
-ZoneServer::ZoneServer(const char* name) {
+ZoneServer::ZoneServer(const char* name, bool incoming_clients) {
+	if(incoming_clients)
+		IncrementIncomingClients();
+	else
+		incoming_clients = 0;
+	MIncomingClients.SetName("ZoneServer::MIncomingClients");
+
 	depop_zone = false;
 	repop_zone = false;
 	respawns_allowed = true;
@@ -162,6 +168,8 @@ ZoneServer::ZoneServer(const char* name) {
 	watchdogTimestamp = Timer::GetCurrentTime2();
 
 	MPendingSpawnRemoval.SetName("ZoneServer::MPendingSpawnRemoval");
+
+	lifetime_client_count = 0;
 }
 
 ZoneServer::~ZoneServer() {
@@ -191,7 +199,6 @@ ZoneServer::~ZoneServer() {
 
 	MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__);
 	MMasterZoneLock->unlock();
-	safe_delete(MMasterZoneLock);
 	world.UpdateServerStatistic(STAT_SERVER_NUM_ACTIVE_ZONES, -1);
 
 	// If lockout, public, tradeskill, or quest instance delete from db when zone shuts down
@@ -214,6 +221,21 @@ ZoneServer::~ZoneServer() {
 	--numzones;
 	UpdateWindowTitle(0);
 	zone_list.Remove(this);
+	zone_list.RemoveClientZoneReference(this);
+	safe_delete(MMasterZoneLock);
+}
+
+void ZoneServer::IncrementIncomingClients() { 
+	MIncomingClients.writelock(__FUNCTION__, __LINE__);
+	incoming_clients++;
+	MIncomingClients.releasewritelock(__FUNCTION__, __LINE__);
+}
+
+void ZoneServer::DecrementIncomingClients() { 
+	MIncomingClients.writelock(__FUNCTION__, __LINE__);
+	if(incoming_clients)
+		incoming_clients--;
+	MIncomingClients.releasewritelock(__FUNCTION__, __LINE__);
 }
 
 void ZoneServer::Init()
@@ -1122,7 +1144,10 @@ void ZoneServer::CheckSendSpawnToClient(Client* client, bool initial_login) {
 		for (itr = closest_spawns.begin(); itr != closest_spawns.end(); ) {
 			for (spawn_iter2 = itr->second->begin(); spawn_iter2 != itr->second->end(); spawn_iter2++) {
 				spawn = *spawn_iter2;
-				SendSpawn(spawn, client);
+
+				if(!client->IsReloadingZone() || (client->IsReloadingZone() && spawn != client->GetPlayer()))
+					SendSpawn(spawn, client);
+				
 				if (client->ShouldTarget() && client->GetCombineSpawn() == spawn)
 					client->TargetSpawn(spawn);
 			}
@@ -1391,8 +1416,15 @@ bool ZoneServer::Process()
 			}
 		}
 
-		if(shutdownTimer.Enabled() && shutdownTimer.Check())
-			zoneShuttingDown = true;
+		if(shutdownTimer.Enabled() && shutdownTimer.Check() && connected_clients.size(true) == 0) {
+			//if(lifetime_client_count)
+				zoneShuttingDown = true;
+			/*else { // allow up to 120 seconds then timeout
+				LogWrite(ZONE__WARNING, 0, "Zone", "No clients have connected to zone '%s' and the shutdown timer has counted down -- will delay shutdown for 120 seconds.", GetZoneName());
+				shutdownTimer.Start(120000, true);
+				lifetime_client_count = 1;
+			}*/
+		}
 
 		if (reloading_spellprocess){
 			MMasterZoneLock->unlock();
@@ -3054,6 +3086,8 @@ void ZoneServer::AddSpawn(Spawn* spawn) {
 
 void ZoneServer::AddClient(Client* client){
 	MClientList.writelock(__FUNCTION__, __LINE__);
+	lifetime_client_count++;
+	DecrementIncomingClients();
 	clients.push_back(client);
 	MClientList.releasewritelock(__FUNCTION__, __LINE__);
 
@@ -3139,7 +3173,7 @@ void ZoneServer::RemoveClient(Client* client)
 		database.ToggleCharacterOnline(client, 0);
 		
 		client->GetPlayer()->DeleteSpellEffects(true);
-		
+
 		RemoveSpawn(client->GetPlayer(), false, true, true, true, true);
 		connected_clients.Remove(client, true, DisconnectClientTimer); // changed from a hardcoded 30000 (30 sec) to the DisconnectClientTimer rule
 	}
@@ -3163,15 +3197,25 @@ void ZoneServer::ClientProcess()
 {
 	if(connected_clients.size(true) == 0)
 	{
-		if(!IsCityZone() && !AlwaysLoaded() && !shutdownTimer.Enabled())
+		MIncomingClients.readlock(__FUNCTION__, __LINE__);
+		bool shutdownDelayCheck = shutdownDelayTimer.Check();
+		if((!IsCityZone() && !AlwaysLoaded() && !shutdownTimer.Enabled()) || shutdownDelayCheck)
 		{
-			LogWrite(ZONE__INFO, 0, "Zone", "Starting zone shutdown timers...");
-			shutdownTimer.Start();
+			if(incoming_clients && !shutdownDelayTimer.Enabled()) {
+				LogWrite(ZONE__INFO, 0, "Zone", "Incoming clients (%u) expected for %s, delaying shutdown timer...", incoming_clients, GetZoneName());
+				shutdownDelayTimer.Start(120000, true);
+			}
+			else if(!incoming_clients || shutdownDelayCheck) {
+				LogWrite(ZONE__INFO, 0, "Zone", "Starting zone shutdown timer for %s...", GetZoneName());
+				shutdownTimer.Start();
+			}
 		}
+		MIncomingClients.releasereadlock(__FUNCTION__, __LINE__);
 		return;
 	}
 
-	shutdownTimer.Disable();	
+	shutdownTimer.Disable();
+	shutdownDelayTimer.Disable();	
 	Client* client = 0;		
 	MutexList<Client*>::iterator iterator = connected_clients.begin();
 
@@ -8113,4 +8157,19 @@ void ZoneServer::UpdateClientSpawnMap(Player* player, Client* client)
 {
 	// client may be null when passed
 	client_spawn_map.Put(player, client);
+}
+
+void ZoneServer::RemoveClientsFromZone(ZoneServer* zone) {
+	vector<Client*>::iterator itr;
+	MClientList.readlock(__FUNCTION__, __LINE__);
+	for (itr = clients.begin(); itr != clients.end(); itr++) {
+		Client* client = *itr;
+		if(client->GetCurrentZone() == zone) {
+			client->SetCurrentZone(nullptr);
+		}
+		if(client->GetZoningDestination() == zone) {
+			client->SetZoningDestination(nullptr);
+		}
+	}
+	MClientList.releasereadlock(__FUNCTION__, __LINE__);
 }

+ 12 - 2
EQ2/source/WorldServer/zoneserver.h

@@ -260,9 +260,11 @@ struct ZoneInfoSlideStruct {
 // need to attempt to clean this up and add xml comments, remove unused code, find a logical way to sort the functions maybe by get/set/process/add etc...
 class ZoneServer {
 public:
-	ZoneServer(const char* file);
+	ZoneServer(const char* file, bool incoming_clients=false);
     ~ZoneServer();
 	
+	void		IncrementIncomingClients();
+	void		DecrementIncomingClients();
 	void		Init();
 	bool		Process();
 	bool		SpawnProcess();
@@ -675,9 +677,12 @@ public:
 	void	QueueDefaultCommand(int32 spawn_id, std::string command, float distance);
 	void	ProcessQueuedStateCommands();
 	void	UpdateClientSpawnMap(Player* player, Client* client);
+	void	RemoveClientsFromZone(ZoneServer* zone);
 
 	void	WorldTimeUpdateTrigger() { sync_game_time_timer.Trigger(); }
 	void	StopSpawnScriptTimer(Spawn* spawn, std::string functionName);
+
+	Client*	RemoveZoneServerFromClient(ZoneServer* zone);
 private:
 #ifndef WIN32
 	pthread_t ZoneThread;
@@ -756,7 +761,7 @@ private:
 	void	InitWeather();																						// never used outside zone server
 	///<summary>Dismiss all pets in the zone, useful when the spell process needs to be reloaded</summary>
 	void DismissAllPets();																						// never used outside zone server
-
+	
 	/* Mutex Lists */
 	MutexList<int32> changed_spawns;										// int32 = spawn id
 	vector<Client*> clients;
@@ -809,6 +814,7 @@ private:
 	Mutex	MSpawnLocationList;
 	Mutex	MSpawnDeleteList;
 	Mutex	MClientList;
+	Mutex	MIncomingClients;
 	
 	/* Maps */
 	map<int32, int32>							dead_spawns;
@@ -850,6 +856,7 @@ private:
 	Timer	weatherTimer;
 	Timer	widget_timer;
 	Timer	queue_updates;
+	Timer	shutdownDelayTimer;
 	
 	/* Enums */
 	Instance_Type InstanceType;
@@ -1093,6 +1100,9 @@ public:
 	void ReloadSpawns();
 
 	void SendStateCommand(Spawn* spawn, int32 state);
+
+	int32 lifetime_client_count;
+	int32 incoming_clients;
 };
 
 #endif