Ver Fonte

Procyon Update #2 - Linkdead fix, stability fixes, AE encounter NPC->player spell cast support

Fix #364 - Linkdead caused instability to world server and also the process of reconnecting a formerly connected player was not setup correctly (need to clear xor packets and other variables, visual states, player states)
- Fixed a crash issue with toggling client offline in database
- Encounter spells NPC->Player now properly handled
Image há 3 anos atrás
pai
commit
bc5e07681d

+ 15 - 0
EQ2/source/WorldServer/Items/Items.cpp

@@ -3747,6 +3747,14 @@ int8 PlayerItemList::FindFreeBankSlot() {
 	return ret;
 }
 
+void PlayerItemList::ResetPackets() {
+	safe_delete_array(orig_packet);
+	safe_delete_array(xor_packet);
+	orig_packet = 0;
+	xor_packet = 0;
+	packet_count = 0;
+}
+
 EquipmentItemList::EquipmentItemList(){
 	orig_packet = 0;
 	xor_packet = 0;
@@ -3771,6 +3779,13 @@ EquipmentItemList::~EquipmentItemList(){
 	safe_delete_array(xor_packet);
 }
 
+void EquipmentItemList::ResetPackets() {
+	safe_delete_array(orig_packet);
+	safe_delete_array(xor_packet);
+	orig_packet = 0;
+	xor_packet = 0;
+}
+
 bool EquipmentItemList::AddItem(int8 slot, Item* item){
 	if(item){
 		MEquipmentItems.lock();

+ 4 - 0
EQ2/source/WorldServer/Items/Items.h

@@ -1085,6 +1085,8 @@ public:
 	void RemoveOverflowItem(Item* item);
 
 	vector<Item*>* GetOverflowItemList();
+	
+	void	ResetPackets();
 
 private:
 	void AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow = false);
@@ -1102,6 +1104,8 @@ public:
 
 	vector<Item*>* GetAllEquippedItems();
 
+	void	ResetPackets();
+
 	bool	HasItem(int32 id);
 	int8	GetNumberOfItems();
 	Item*	GetItemFromUniqueID(int32 item_id);

+ 46 - 1
EQ2/source/WorldServer/Player.cpp

@@ -3005,6 +3005,7 @@ EQ2Packet* Player::GetSpellBookUpdatePacket(int16 version) {
 		int32 total_bytes = packet2->GetTotalPacketSize();
 		safe_delete(packet2);
 		packet->setArrayLengthByName("spell_count", count);
+		
 		if (count > 0) {
 			if (count > spell_count) {
 				uchar* tmp = 0;
@@ -5325,17 +5326,57 @@ void Player::ResetSavedSpawns(){
 	index_mutex.writelock(__FUNCTION__, __LINE__);
 	player_spawn_reverse_id_map.clear();
 	player_spawn_id_map.clear();
+	player_spawn_id_map[1] = this;
+	player_spawn_reverse_id_map[this] = 1;
 	index_mutex.releasewritelock(__FUNCTION__, __LINE__);
 
 	m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__);
 	player_spawn_quests_required.clear();
 	m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__);
-	info->RemoveOldPackets();
+	if(info)
+		info->RemoveOldPackets();
+	
 	safe_delete_array(movement_packet);
 	safe_delete_array(old_movement_packet);
 }
 
 void Player::SetReturningFromLD(bool val){
+	if(val && val != returning_from_ld)
+	{
+		if(GetPlayerItemList())
+			GetPlayerItemList()->ResetPackets();
+		
+		GetEquipmentList()->ResetPackets();
+		GetAppearanceEquipmentList()->ResetPackets();
+		skill_list.ResetPackets();
+		safe_delete_array(spell_orig_packet);
+		safe_delete_array(spell_xor_packet);
+		spell_orig_packet=0;
+		spell_xor_packet=0;
+		spell_count = 0;
+
+		reset_character_flag(CF_IS_SITTING);
+		if (GetActivityStatus() & ACTIVITY_STATUS_CAMPING)
+			SetActivityStatus(GetActivityStatus() - ACTIVITY_STATUS_CAMPING);
+
+		if (GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD)
+			SetActivityStatus(GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD);
+		
+		SetTempVisualState(0);
+
+		safe_delete_array(spawn_tmp_info_xor_packet);
+		safe_delete_array(spawn_tmp_vis_xor_packet);
+		safe_delete_array(spawn_tmp_pos_xor_packet);
+		spawn_tmp_info_xor_packet = 0;
+		spawn_tmp_vis_xor_packet = 0;
+		spawn_tmp_pos_xor_packet = 0;
+		pos_xor_size = 0;
+		info_xor_size = 0;
+		vis_xor_size = 0;
+		
+		spawn_index = 0;
+	}
+	
 	returning_from_ld = val;
 }
 
@@ -6202,6 +6243,10 @@ void PlayerInfo::RemoveOldPackets(){
 	safe_delete_array(orig_packet);
 	safe_delete_array(pet_changes);
 	safe_delete_array(pet_orig_packet);
+	changes = 0;
+	orig_packet = 0;
+	pet_changes = 0;
+	pet_orig_packet = 0;
 }
 
 PlayerControlFlags::PlayerControlFlags(){

+ 9 - 0
EQ2/source/WorldServer/Skills.cpp

@@ -488,6 +488,15 @@ void PlayerSkillList::AddSkillBonus(int32 spell_id, int32 skill_id, float value)
 	}
 }
 
+void PlayerSkillList::ResetPackets() {
+	safe_delete_array(orig_packet);
+	safe_delete_array(xor_packet);
+	orig_packet_size = 0;
+	orig_packet = 0;
+	xor_packet = 0;
+	packet_count = 0;
+}
+
 SkillBonus* PlayerSkillList::GetSkillBonus(int32 spell_id) {
 	SkillBonus *ret = 0;
 	

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

@@ -159,6 +159,7 @@ public:
 	map<int32, Skill*>* GetAllSkills();
 	bool HasSkillUpdates();
 
+	void ResetPackets();
 private:
 	volatile bool has_updates;
 	Mutex MSkillUpdates;

+ 20 - 10
EQ2/source/WorldServer/SpellProcess.cpp

@@ -1338,7 +1338,8 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 
 		if (lua_spell->targets.size() == 0 && spell->GetSpellData()->max_aoe_targets == 0) 
 		{
-			LogWrite(SPELL__ERROR, 0, "Spell", "SpellProcess::ProcessSpell Unable to find any spell targets for spell '%s'.", spell->GetName());
+			LogWrite(SPELL__ERROR, 0, "Spell", "SpellProcess::ProcessSpell Unable to find any spell targets for spell '%s', spell type: %u, target type %u.", 
+										spell->GetName(), spell->GetSpellData()->spell_type, spell->GetSpellData()->target_type);
 			lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell);
 			DeleteSpell(lua_spell);
 			return;
@@ -2092,6 +2093,11 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 						luaspell->initial_target = caster->GetID();
 						luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0;
 					}
+					else if(target && target->IsPlayer())
+					{
+						if(!GetPlayerGroupTargets((Player*)target, caster, luaspell, true, false))
+							AddSelfAndPet(luaspell, target);
+					}
 				}
 				else // default self cast for group/raid AE
 				{
@@ -2395,7 +2401,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 		LogWrite(SPELL__WARNING, 0, "Spell", "Warning in %s: Size of targets array is %u", __FUNCTION__, luaspell->targets.size());
 }
 
-void SpellProcess::GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell* luaspell, bool bypassSpellChecks, bool bypassRangeChecks)
+bool SpellProcess::GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell* luaspell, bool bypassSpellChecks, bool bypassRangeChecks)
 {
 	if (bypassSpellChecks || luaspell->spell->GetSpellData()->group_spell > 0 || luaspell->spell->GetSpellData()->icon_backdrop == 312)
 	{
@@ -2421,9 +2427,13 @@ void SpellProcess::GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell
 					}
 				}
 				group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
+
+				return true;
 			}
 		}
 	}
+
+	return false;
 }
 
 void SpellProcess::GetSpellTargetsTrueAOE(LuaSpell* luaspell) {
@@ -2819,17 +2829,17 @@ void SpellProcess::AddActiveSpell(LuaSpell* spell)
 		active_spells.Add(spell);
 }
 
-void SpellProcess::AddSelfAndPet(LuaSpell* spell, Spawn* caster, bool onlyPet)
+void SpellProcess::AddSelfAndPet(LuaSpell* spell, Spawn* self, bool onlyPet)
 {
 	if(!onlyPet)
-		spell->targets.push_back(caster->GetID());
+		spell->targets.push_back(self->GetID());
 	
-	if(caster->IsEntity() && ((Entity*)caster)->HasPet() && ((Entity*)caster)->GetPet())
-		spell->targets.push_back(((Entity*)caster)->GetPet()->GetID());
-	if(caster->IsEntity() && ((Entity*)caster)->HasPet() && ((Entity*)caster)->GetCharmedPet())
-		spell->targets.push_back(((Entity*)caster)->GetCharmedPet()->GetID());
-	if(!onlyPet && caster->IsEntity() && ((Entity*)caster)->IsPet() && ((Entity*)caster)->GetOwner())
-		spell->targets.push_back(((Entity*)caster)->GetOwner()->GetID());
+	if(self->IsEntity() && ((Entity*)self)->HasPet() && ((Entity*)self)->GetPet())
+		spell->targets.push_back(((Entity*)self)->GetPet()->GetID());
+	if(self->IsEntity() && ((Entity*)self)->HasPet() && ((Entity*)self)->GetCharmedPet())
+		spell->targets.push_back(((Entity*)self)->GetCharmedPet()->GetID());
+	if(!onlyPet && self->IsEntity() && ((Entity*)self)->IsPet() && ((Entity*)self)->GetOwner())
+		spell->targets.push_back(((Entity*)self)->GetOwner()->GetID());
 }
 
 void SpellProcess::AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bool onlyPet)

+ 2 - 2
EQ2/source/WorldServer/SpellProcess.h

@@ -332,7 +332,7 @@ public:
 	/// <param name='luaspell'>LuaSpell to get the targets for</param>
 	static void GetSpellTargets(LuaSpell* luaspell);
 
-	static void GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell* luaspell, bool bypassSpellChecks=false, bool bypassRangeChecks=false);
+	static bool GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell* luaspell, bool bypassSpellChecks=false, bool bypassRangeChecks=false);
 
 	/// <summary>Gets targets for a true aoe spell (not an encounter ae) and adds them to the LuaSpell targets array</summary>
 	/// <param name='luaspell'>LuaSpell to get the targets for</param>
@@ -392,7 +392,7 @@ public:
 	std::string ApplyLuaFunction(LuaSpell* spell, bool first_cast, const char* function, SpellScriptTimer* timer, Spawn* altTarget = 0);
 
 	void AddActiveSpell(LuaSpell* spell);
-	static void AddSelfAndPet(LuaSpell* spell, Spawn* caster, bool onlyPet=false);
+	static void AddSelfAndPet(LuaSpell* spell, Spawn* self, bool onlyPet=false);
 	static void AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bool onlyPet=false);
 private:
 	Mutex MSpellProcess;

+ 51 - 29
EQ2/source/WorldServer/client.cpp

@@ -289,6 +289,9 @@ void Client::PopulateSkillMap() {
 }
 
 void Client::SendLoginInfo() {
+	if(GetPlayer()->IsReturningFromLD())
+		firstlogin = true;
+
 	if (firstlogin) {
 		LogWrite(WORLD__DEBUG, 0, "World", "Increment Server_Accepted_Connection + 1");
 		world.UpdateServerStatistic(STAT_SERVER_ACCEPTED_CONNECTION, 1);
@@ -301,29 +304,44 @@ void Client::SendLoginInfo() {
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "Toggle Character Online...");
 	database.ToggleCharacterOnline(this, 1);
 
-	LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Skills for player '%s'...", player->GetName());
-	int32 count = database.LoadCharacterSkills(GetCharacterID(), player);
+	int32 count = 0;
+
+	if(!GetPlayer()->IsReturningFromLD())
+	{
+		LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Skills for player '%s'...", player->GetName());
+		count = database.LoadCharacterSkills(GetCharacterID(), player);
 
-	LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Spells for player '%s'...", player->GetName());
-	count = database.LoadCharacterSpells(GetCharacterID(), player);
+		LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Spells for player '%s'...", player->GetName());
+		count = database.LoadCharacterSpells(GetCharacterID(), player);
+	}
+	else
+	{
+		LogWrite(CCLIENT__INFO, 0, "Client", "Player is returning from linkdead status (Player does not need reload) thus skipping database loading for '%s'...", player->GetName());
+	}
 	
 	// get the latest character starting skills / spells, may have been updated after character creation
 	world.SyncCharacterAbilities(this);
 
-	count = database.LoadCharacterTitles(GetCharacterID(), player);
-	if (count == 0) {
-		LogWrite(CCLIENT__DEBUG, 0, "Client", "No character titles found!");
-		LogWrite(CCLIENT__DEBUG, 0, "Client", "Initializing starting values - Titles");
-		database.UpdateStartingTitles(GetCharacterID(), player->GetAdventureClass(), player->GetRace(), player->GetGender());
+	if(!GetPlayer()->IsReturningFromLD())
+	{
+		count = database.LoadCharacterTitles(GetCharacterID(), player);
+		if (count == 0) {
+			LogWrite(CCLIENT__DEBUG, 0, "Client", "No character titles found!");
+			LogWrite(CCLIENT__DEBUG, 0, "Client", "Initializing starting values - Titles");
+			database.UpdateStartingTitles(GetCharacterID(), player->GetAdventureClass(), player->GetRace(), player->GetGender());
+		}
 	}
 
-	count = database.LoadCharacterLanguages(GetCharacterID(), player);
-	if (count == 0)
-		LogWrite(CCLIENT__DEBUG, 0, "Client", "No character languages loaded!");
+	if(!GetPlayer()->IsReturningFromLD())
+	{
+		count = database.LoadCharacterLanguages(GetCharacterID(), player);
+		if (count == 0)
+			LogWrite(CCLIENT__DEBUG, 0, "Client", "No character languages loaded!");
 
-	count = database.LoadPlayerRecipeBooks(GetCharacterID(), player);
-	if (count == 0)
-		LogWrite(CCLIENT__DEBUG, 0, "Client", "No character recipe books found!");
+		count = database.LoadPlayerRecipeBooks(GetCharacterID(), player);
+		if (count == 0)
+			LogWrite(CCLIENT__DEBUG, 0, "Client", "No character recipe books found!");
+	}
 
 	ClientPacketFunctions::SendLoginAccepted(this);
 
@@ -340,16 +358,19 @@ void Client::SendLoginInfo() {
 		zone_list.CheckFriendList(this);
 	}
 
-	database.LoadCharacterItemList(GetAccountID(), GetCharacterID(), player, GetVersion());
-	if (firstlogin && player->item_list.GetNumberOfItems() == 0 && player->GetEquipmentList()->GetNumberOfItems() == 0) //re-add starting items if missing
+	if(!GetPlayer()->IsReturningFromLD())
 	{
-		LogWrite(CCLIENT__WARNING, 0, "Client", "Player has no items - reloading starting items: '%s' (%u)", player->GetName(), GetCharacterID());
-		database.UpdateStartingItems(GetCharacterID(), player->GetAdventureClass(), player->GetRace());
 		database.LoadCharacterItemList(GetAccountID(), GetCharacterID(), player, GetVersion());
+		if (firstlogin && player->item_list.GetNumberOfItems() == 0 && player->GetEquipmentList()->GetNumberOfItems() == 0) //re-add starting items if missing
+		{
+			LogWrite(CCLIENT__WARNING, 0, "Client", "Player has no items - reloading starting items: '%s' (%u)", player->GetName(), GetCharacterID());
+			database.UpdateStartingItems(GetCharacterID(), player->GetAdventureClass(), player->GetRace());
+			database.LoadCharacterItemList(GetAccountID(), GetCharacterID(), player, GetVersion());
+		}
+		database.LoadPlayerFactions(this);
+		database.LoadCharacterQuests(this);
+		database.LoadPlayerMail(this);
 	}
-	database.LoadPlayerFactions(this);
-	database.LoadCharacterQuests(this);
-	database.LoadPlayerMail(this);
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
 	SendQuestJournal(true, 0, false);
 
@@ -714,7 +735,7 @@ void Client::SendCharInfo() {
 	if (guild)
 		guild->GuildMemberLogin(this, firstlogin);
 
-	app = player->item_list.serialize(GetPlayer(), GetVersion());
+	app = player->GetPlayerItemList()->serialize(GetPlayer(), GetVersion());
 	if (app) {
 		LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__);
 		//DumpPacket(app);
@@ -730,7 +751,7 @@ void Client::SendCharInfo() {
 		QueuePacket(app);
 	}
 
-	vector<Item*>* items = player->item_list.GetItemsFromBagID(-3); // bank items
+	vector<Item*>* items = player->GetPlayerItemList()->GetItemsFromBagID(-3); // bank items
 	if (items && items->size() > 0) {
 		for (int32 i = 0; i < items->size(); i++) {
 			EQ2Packet* outapp = items->at(i)->serialize(GetVersion(), false, GetPlayer());
@@ -745,7 +766,7 @@ void Client::SendCharInfo() {
 		QueuePacket(app);
 
 	safe_delete(items);
-	items = player->item_list.GetItemsFromBagID(-4); //shared bank items
+	items = player->GetPlayerItemList()->GetItemsFromBagID(-4); //shared bank items
 	if (items && items->size() > 0) {
 		for (int32 i = 0; i < items->size(); i++)
 			QueuePacket(items->at(i)->serialize(GetVersion(), false, GetPlayer()));
@@ -836,6 +857,7 @@ void Client::SendCharInfo() {
 	database.LoadCharacterSpellEffects(GetCharacterID(), this, DB_TYPE_SPELLEFFECTS);
 	GetPlayer()->SetSaveSpellEffects(false);
 	GetPlayer()->SetCharSheetChanged(true);
+	GetPlayer()->SetReturningFromLD(false);
 }
 
 void Client::SendZoneSpawns() {
@@ -9743,12 +9765,12 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code)
 					if (client->GetVersion() == version) {
 						Player* current_player = GetPlayer();
 						SetPlayer(client->GetPlayer());
+						GetPlayer()->SetClient(this);
+						GetPlayer()->SetReturningFromLD(true);
+						SetCurrentZone(GetPlayer()->GetZone());
+						GetPlayer()->GetZone()->UpdateClientSpawnMap(GetPlayer(), this);
 						client->SetPlayer(current_player);
-						client->GetPlayer()->SetPendingDeletion(true);
-
-						GetPlayer()->SetPendingDeletion(false);
 						GetPlayer()->ResetSavedSpawns();
-						GetPlayer()->SetReturningFromLD(true);
 					}
 					ZoneServer* tmpZone = client->GetCurrentZone();
 					tmpZone->RemoveClientImmediately(client);

+ 23 - 16
EQ2/source/WorldServer/zoneserver.cpp

@@ -2971,15 +2971,13 @@ void ZoneServer::AddSpawn(Spawn* spawn) {
 	spawn->info_changed = false;
 	spawn->vis_changed = false;
 	spawn->changed = false;
-	if(!spawn->IsPlayer() || (spawn->IsPlayer() && !((Player*)spawn)->IsReturningFromLD())) {
-		// Write locking the spawn list here will cause deadlocks, so instead add it to a temp list that the
-		// main spawn thread will put into the spawn_list when ever it has a chance.
-		MPendingSpawnListAdd.writelock(__FUNCTION__, __LINE__);
-		pending_spawn_list_add.push_back(spawn);
-		MPendingSpawnListAdd.releasewritelock(__FUNCTION__, __LINE__);
-	}
-	else
-		((Player*)spawn)->SetReturningFromLD(false);
+	
+	// Write locking the spawn list here will cause deadlocks, so instead add it to a temp list that the
+	// main spawn thread will put into the spawn_list when ever it has a chance.
+	MPendingSpawnListAdd.writelock(__FUNCTION__, __LINE__);
+	pending_spawn_list_add.push_back(spawn);
+	MPendingSpawnListAdd.releasewritelock(__FUNCTION__, __LINE__);
+	
 	spawn_range.Trigger();
 	spawn_check_add.Trigger();
 
@@ -3101,6 +3099,7 @@ void ZoneServer::RemoveClientImmediately(Client* client) {
 
 	if(client) 
 	{
+		database.ToggleCharacterOnline(client, 0);
 		loginserver.SendImmediateEquipmentUpdatesForChar(client->GetPlayer()->GetCharacterID());
 		if(connected_clients.count(client) > 0)
 		{
@@ -3115,6 +3114,8 @@ void ZoneServer::RemoveClientImmediately(Client* client) {
 			//clients.Remove(client);
 			LogWrite(ZONE__INFO, 0, "Zone", "Removing connection for client '%s'.", client->GetPlayer()->GetName());
 			connected_clients.Remove(client, true);
+			// client is deleted at this point
+			client = 0;
 		}
 		else
 		{
@@ -3125,9 +3126,6 @@ void ZoneServer::RemoveClientImmediately(Client* client) {
 			MClientList.releasewritelock(__FUNCTION__, __LINE__);
 			//clients.Remove(client, true);
 		}
-
-		LogWrite(MISC__TODO, 1, "TODO", "Put Player Online Status updates in a timer eventually\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
-		database.ToggleCharacterOnline(client, 0);
 	}
 }
 
@@ -3172,7 +3170,7 @@ void ZoneServer::ClientProcess()
 					}
 
 				}
-				client_spawn_map.Put(client->GetPlayer(), 0);
+				UpdateClientSpawnMap(client->GetPlayer(), 0);
 				client->Disconnect();
 				RemoveClient(client);
 			}
@@ -3191,7 +3189,7 @@ void ZoneServer::ClientProcess()
 							world.GetGroupManager()->GroupMessage(client->GetPlayer()->GetGroupMemberInfo()->group_id, "%s has gone Linkdead.", client->GetPlayer()->GetName());
 					}
 				}
-				client_spawn_map.Put(client->GetPlayer(), 0);
+				UpdateClientSpawnMap(client->GetPlayer(), 0);
 				client->Disconnect();
 				RemoveClient(client);
 			}
@@ -4919,7 +4917,7 @@ void ZoneServer::SendZoneSpawns(Client* client){
 	for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
 		Spawn* spawn = itr->second;
 		if (spawn) {
-			if(spawn == client->GetPlayer() && client->IsReloadingZone())
+			if(spawn == client->GetPlayer() && (client->IsReloadingZone() || client->GetPlayer()->IsReturningFromLD()))
 			{
 				client->GetPlayer()->SetSpawnMap(spawn);
 			}
@@ -5094,7 +5092,10 @@ vector<ZoneInfoSlideStruct*>* ZoneServer::GenerateTutorialSlides() {
 }
 
 EQ2Packet* ZoneServer::GetZoneInfoPacket(Client* client){
-	client_spawn_map.Put(client->GetPlayer(), client);
+	// this takes place when we get the LoginInfo for returning LD players
+	if(!client->GetPlayer()->IsReturningFromLD())
+		UpdateClientSpawnMap(client->GetPlayer(), client);
+	
 	PacketStruct* packet = configReader.getStruct("WS_ZoneInfo", client->GetVersion());
 	packet->setSmallStringByName("server1",net.GetWorldName());
 	packet->setSmallStringByName("server2",net.GetWorldName());
@@ -8091,4 +8092,10 @@ void ZoneServer::ProcessQueuedStateCommands() // in a client list lock only
 		lua_spawn_update_command.clear();
 	}
 	MLuaQueueStateCmd.unlock();
+}
+
+void ZoneServer::UpdateClientSpawnMap(Player* player, Client* client)
+{
+	// client may be null when passed
+	client_spawn_map.Put(player, client);
 }

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

@@ -678,6 +678,7 @@ public:
 	void	QueueStateCommandToClients(int32 spawn_id, int32 state);
 	void	QueueDefaultCommand(int32 spawn_id, std::string command, float distance);
 	void	ProcessQueuedStateCommands();
+	void	UpdateClientSpawnMap(Player* player, Client* client);
 private:
 #ifndef WIN32
 	pthread_t ZoneThread;