Browse Source

Spawn indexes in Player class now protected by mutex

Fix #180
image 1 year ago
parent
commit
2ebc48c157

+ 1 - 1
EQ2/source/WorldServer/HeroicOp/HeroicOpPackets.cpp

@@ -40,7 +40,7 @@ void ClientPacketFunctions::SendHeroicOPUpdate(Client* client, HeroicOP* ho) {
 	PacketStruct* packet = configReader.getStruct("WS_HeroicOpportunity", client->GetVersion());
 	Spell* spell = 0;
 	if (packet) {
-		packet->setDataByName("id", client->GetPlayer()->player_spawn_reverse_id_map[client->GetPlayer()]);
+		packet->setDataByName("id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
 		if (ho->GetWheel()) {
 			spell = master_spell_list.GetSpell(ho->GetWheel()->spell_id, 1);
 			if (!spell) {

+ 62 - 18
EQ2/source/WorldServer/Player.cpp

@@ -114,6 +114,7 @@ Player::Player(){
 	pos_mutex.SetName("Player::pos_mutex");
 	vis_mutex.SetName("Player::vis_mutex");
 	info_mutex.SetName("Player::info_mutex");
+	index_mutex.SetName("Player::index_mutex");
 	m_playerSpawnQuestsRequired.SetName("Player::player_spawn_quests_required");
 	m_playerSpawnHistoryRequired.SetName("Player::player_spawn_history_required");
 	gm_vision = false;
@@ -165,11 +166,13 @@ Player::~Player(){
 	DeleteMail();
 	world.RemoveLottoPlayer(GetCharacterID());
 	safe_delete(info);
+	index_mutex.writelock(__FUNCTION__, __LINE__);
 	player_spawn_index_map.clear();
 	player_spawn_map.clear();
 	player_spawn_reverse_id_map.clear();
 	player_removed_spawns.clear();
 	player_spawn_id_map.clear();
+	index_mutex.releasewritelock(__FUNCTION__, __LINE__);
 
 	info_mutex.writelock(__FUNCTION__, __LINE__);
 	spawn_info_packet_list.clear();
@@ -2932,11 +2935,13 @@ SpellEffects* Player::GetSpellEffects() {
 }
 
 void Player::ClearEverything(){
+	index_mutex.writelock(__FUNCTION__, __LINE__);
 	player_removed_spawns.clear();
 	player_spawn_map.clear();
 	player_spawn_index_map.clear();
 	player_spawn_id_map.clear();
 	player_spawn_reverse_id_map.clear();
+	index_mutex.releasewritelock(__FUNCTION__, __LINE__);
 	map<int32, vector<int32>*>::iterator itr;
 	m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__);
 	for (itr = player_spawn_quests_required.begin(); itr != player_spawn_quests_required.end(); itr++){
@@ -3803,42 +3808,76 @@ void Player::CalculateLocation(){
 }
 
 Spawn* Player::GetSpawnByIndex(int16 index){
+	Spawn* spawn = 0;
+
+	index_mutex.readlock(__FUNCTION__, __LINE__);
 	if(player_spawn_map.count(index) > 0)
-		return player_spawn_map[index];
-	else
-		return 0;
+		spawn = player_spawn_map[index];
+	index_mutex.releasereadlock(__FUNCTION__, __LINE__);
+
+	return spawn;
 }
 
-int16 Player::GetIndexForSpawn(Spawn* spawn){
+int16 Player::GetIndexForSpawn(Spawn* spawn) {
+	int16 val = 0;
+
+	index_mutex.readlock(__FUNCTION__, __LINE__);
 	if(player_spawn_index_map.count(spawn) > 0)
-		return player_spawn_index_map[spawn];
-	else
-		return 0;
+		val = player_spawn_index_map[spawn];
+	index_mutex.releasereadlock(__FUNCTION__, __LINE__);
+
+	return val;
 }
 
 bool Player::WasSpawnRemoved(Spawn* spawn){
+	bool wasRemoved = false;
+
+	index_mutex.readlock(__FUNCTION__, __LINE__);
 	if(player_removed_spawns.count(spawn) > 0)
-		return true;
-	else
-		return false;
+		wasRemoved = true;
+	index_mutex.releasereadlock(__FUNCTION__, __LINE__);
+
+	return wasRemoved;
 }
 
 void Player::RemoveSpawn(Spawn* spawn)
 {
 	LogWrite(PLAYER__DEBUG, 3, "Player", "Remove Spawn '%s' (%u)", spawn->GetName(), spawn->GetID());
+
+	info_mutex.writelock(__FUNCTION__, __LINE__);
+	pos_mutex.writelock(__FUNCTION__, __LINE__);
+	vis_mutex.writelock(__FUNCTION__, __LINE__);
+
+	index_mutex.writelock(__FUNCTION__, __LINE__);
+
 	player_removed_spawns[spawn] = 1;
 
+	if (player_spawn_index_map[spawn] && player_spawn_map.count(player_spawn_index_map[spawn]) > 0)
+		player_spawn_map.erase(player_spawn_index_map[spawn]);
+
+	if (player_spawn_index_map.count(spawn) > 0)
+		player_spawn_index_map.erase(spawn);
+
+	if (player_spawn_id_map.count(spawn->GetID()) && player_spawn_id_map[spawn->GetID()] == spawn)
+		player_spawn_id_map.erase(spawn->GetID());
+
+	if (player_spawn_reverse_id_map.count(spawn))
+		player_spawn_reverse_id_map.erase(spawn);
+
 	int32 id = spawn->GetID();
-	info_mutex.writelock(__FUNCTION__, __LINE__);
-	spawn_info_packet_list.erase(id);
-	info_mutex.releasewritelock(__FUNCTION__, __LINE__);
+	if (spawn_info_packet_list.count(id))
+		spawn_info_packet_list.erase(id);
 
-	pos_mutex.writelock(__FUNCTION__, __LINE__);
-	spawn_pos_packet_list.erase(id);
-	pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
+	if (spawn_pos_packet_list.count(id))
+		spawn_pos_packet_list.erase(id);
 
-	vis_mutex.writelock(__FUNCTION__, __LINE__);
-	spawn_vis_packet_list.erase(id);
+	if (spawn_vis_packet_list.count(id))
+		spawn_vis_packet_list.erase(id);
+
+	index_mutex.releasewritelock(__FUNCTION__, __LINE__);
+
+	info_mutex.releasewritelock(__FUNCTION__, __LINE__);
+	pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
 	vis_mutex.releasewritelock(__FUNCTION__, __LINE__);
 }
 
@@ -4524,8 +4563,10 @@ bool Player::CanReceiveQuest(int32 quest_id){
 }
 
 void Player::ClearRemovedSpawn(Spawn* spawn){
+	index_mutex.writelock(__FUNCTION__, __LINE__);
 	if(player_removed_spawns.count(spawn) > 0)
 		player_removed_spawns.erase(spawn);
+	index_mutex.releasewritelock(__FUNCTION__, __LINE__);
 }
 
 bool Player::ShouldSendSpawn(Spawn* spawn){
@@ -4896,10 +4937,13 @@ void Player::ResetSavedSpawns(){
 	spawn_pos_packet_list.clear();
 	pos_mutex.releasewritelock(__FUNCTION__, __LINE__);
 
+	index_mutex.writelock(__FUNCTION__, __LINE__);
 	player_spawn_reverse_id_map.clear();
 	player_spawn_id_map.clear();
 	player_spawn_map.clear();
 	player_spawn_index_map.clear();
+	index_mutex.releasewritelock(__FUNCTION__, __LINE__);
+
 	m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__);
 	player_spawn_quests_required.clear();
 	m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__);

+ 69 - 15
EQ2/source/WorldServer/Player.h

@@ -569,22 +569,74 @@ public:
 	void	ClearRemovedSpawn(Spawn* spawn);
 	bool	ShouldSendSpawn(Spawn* spawn);
 
-	map<int16, Spawn*>* GetPlayerSpawnMap(){
-		return &player_spawn_map;
-	}
-	map<Spawn*, int16>* GetPlayerSpawnIndexMap(){
-		return &player_spawn_index_map;
-	}
 	Spawn* GetSpawnWithPlayerID(int32 id){
+		Spawn* spawn = 0;
+
+		index_mutex.readlock(__FUNCTION__, __LINE__);
 		if (player_spawn_id_map.count(id) > 0)
-			return player_spawn_id_map[id];
-		return 0;
+			spawn = player_spawn_id_map[id];
+		index_mutex.releasereadlock(__FUNCTION__, __LINE__);
+		return spawn;
 	}
 	int32 GetIDWithPlayerSpawn(Spawn* spawn){
+		int32 id = 0;
+
+		index_mutex.readlock(__FUNCTION__, __LINE__);
 		if (player_spawn_reverse_id_map.count(spawn) > 0)
-			return player_spawn_reverse_id_map[spawn];
-		return 0;
+			id = player_spawn_reverse_id_map[spawn];
+		index_mutex.releasereadlock(__FUNCTION__, __LINE__);
+		return id;
+	}
+
+	void SetSpawnMap(Spawn* spawn)
+	{
+		index_mutex.writelock(__FUNCTION__, __LINE__);
+		spawn_id += 1;
+		int32 tmp_id = spawn_id;
+		player_spawn_id_map[tmp_id] = spawn;
+
+		if(player_spawn_reverse_id_map.count(spawn))
+			player_spawn_reverse_id_map.erase(spawn);
+
+		player_spawn_reverse_id_map.insert(make_pair(spawn,tmp_id));
+		index_mutex.releasewritelock(__FUNCTION__, __LINE__);
 	}
+
+	void SetSpawnMapIndex(Spawn* spawn, int16 index)
+	{
+		index_mutex.writelock(__FUNCTION__, __LINE__);
+		if (player_spawn_map.count(index))
+			player_spawn_map[index] = spawn;
+		else
+			player_spawn_map[index] = spawn;
+		index_mutex.releasewritelock(__FUNCTION__, __LINE__);
+	}
+
+	int16 SetSpawnMapAndIndex(Spawn* spawn)
+	{
+		int16 new_index = 0;
+		index_mutex.writelock(__FUNCTION__, __LINE__);
+		spawn_index += 1;
+		if (spawn_index == 255)
+			spawn_index += 1; //just so we dont have to worry about overloading
+
+		new_index = spawn_index;
+
+		if (player_spawn_index_map.count(spawn))
+			player_spawn_index_map.erase(spawn);
+
+		player_spawn_index_map.insert(make_pair(spawn,new_index));
+
+		if (player_spawn_map.count(new_index))
+			player_spawn_map[new_index] = spawn;
+		else
+			player_spawn_map.insert(make_pair(new_index, spawn));
+
+		index_mutex.releasewritelock(__FUNCTION__, __LINE__);
+
+		return new_index;
+	}
+
 	PacketStruct*	GetQuestJournalPacket(bool all_quests, int16 version, int32 crc, int32 current_quest_id);
 	void			RemoveQuest(int32 id, bool delete_quest);
 	vector<Quest*>* CheckQuestsChatUpdate(Spawn* spawn);
@@ -602,10 +654,6 @@ public:
 	void AddHistoryRequiredSpawn(Spawn* spawn, int32 event_id);
 	int16				spawn_index;
 	int32				spawn_id;
-	map<Spawn*, int16>	player_spawn_index_map;
-	map<int16, Spawn*>	player_spawn_map;
-	map<int32, Spawn*>	player_spawn_id_map;
-	map<Spawn*, int32>	player_spawn_reverse_id_map;
 	map<int32, vector<int32>*>  player_spawn_quests_required;
 	map<int32, vector<int32>*>   player_spawn_history_required;
 	Mutex				m_playerSpawnQuestsRequired;
@@ -817,6 +865,7 @@ public:
 	Mutex info_mutex;
 	Mutex pos_mutex;
 	Mutex vis_mutex;
+	Mutex index_mutex;
 
 	void SetTempMount(int32 id) { tmp_mount_model = id; }
 	int32 GetTempMount() { return tmp_mount_model; }
@@ -864,7 +913,6 @@ private:
 	map<Spawn*, bool>	current_quest_flagged;
 	PlayerFaction		factions;
 	map<int32, Quest*>	completed_quests;
-	map<Spawn*, int8>	player_removed_spawns;
 	bool				charsheet_changed;
 	map<int32, string>	spawn_vis_packet_list;
 	map<int32, string>	spawn_info_packet_list;
@@ -965,6 +1013,12 @@ private:
 	EQ2_Color tmp_mount_saddle_color;
 
 	bool gm_vision;
+
+	map<Spawn*, int16>	player_spawn_index_map;
+	map<int16, Spawn*>	player_spawn_map;
+	map<int32, Spawn*>	player_spawn_id_map;
+	map<Spawn*, int32>	player_spawn_reverse_id_map;
+	map<Spawn*, int8>	player_removed_spawns;
 };
 #pragma pack()
 #endif

+ 22 - 20
EQ2/source/WorldServer/Spawn.cpp

@@ -198,7 +198,9 @@ void Spawn::InitializeHeaderPacketData(Player* player, PacketStruct* header, int
 		vector<Spawn*>::iterator itr;
 		int i = 0;
 		for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++, i++){
-			header->setArrayDataByName("group_spawn_id", player->GetIDWithPlayerSpawn((*itr)), i);
+			int32 idx = 0;
+			idx = player->GetIDWithPlayerSpawn((*itr));
+			header->setArrayDataByName("group_spawn_id", idx, i);
 		}
 		MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__);
 	}
@@ -383,29 +385,21 @@ EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, i
 		((Player*)((NPC*)this)->GetOwner())->GetInfoStruct()->pet_id = player->spawn_id;
 		player->SetCharSheetChanged(true);
 	}
+	m_Update.writelock(__FUNCTION__, __LINE__);
 
-	int16 index;
-	if (player->player_spawn_index_map.count(this) > 0) {
-		index = player->player_spawn_index_map[this];
-		player->player_spawn_map[index] = this;
+	int16 index = 0;
+	if ((index = player->GetIndexForSpawn(this)) > 0) {
+		player->SetSpawnMapIndex(this, index);
 	}
 	else {
-		player->spawn_index++;
-		if (player->spawn_index == 255)
-			player->spawn_index++; //just so we dont have to worry about overloading
-		index = player->spawn_index;
-		player->player_spawn_index_map[this] = index;
-		player->player_spawn_map[index] = this;
+		index = player->SetSpawnMapAndIndex(this);
 	}
 
 	// Jabantiz - [Bug] Client Crash on Revive
-	if (player->player_spawn_reverse_id_map.count(this) == 0) {
-		int32 spawn_id = ++player->spawn_id;
-		player->player_spawn_id_map[spawn_id] = this;
-		player->player_spawn_reverse_id_map[this] = spawn_id;
+	if (player->GetIDWithPlayerSpawn(this) == 0) {
+		player->SetSpawnMap(this);
 	}
 
-	m_Update.writelock(__FUNCTION__, __LINE__);
 	PacketStruct* header = player->GetSpawnHeaderStruct();
 	header->ResetData();
 	InitializeHeaderPacketData(player, header, index);
@@ -562,6 +556,7 @@ EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, i
 	}
 	if (offset > 0)
 		DumpPacket(part2, part2_size);
+
 	uchar tmp[1100];
 	bool reverse = (version > 283);
 	part2_size = Pack(tmp, part2, part2_size, 1100, version, reverse);
@@ -631,7 +626,7 @@ EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, i
 }
 
 uchar* Spawn::spawn_info_changes(Player* player, int16 version){
-	int16 index = player->player_spawn_index_map[this];
+	int16 index = player->GetIndexForSpawn(this);
 
 	PacketStruct* packet = player->GetSpawnInfoStruct();
 
@@ -688,7 +683,7 @@ uchar* Spawn::spawn_info_changes(Player* player, int16 version){
 
 uchar* Spawn::spawn_vis_changes(Player* player, int16 version){
 	PacketStruct* vis_struct = player->GetSpawnVisStruct();
-	int16 index = player->player_spawn_index_map[this];
+	int16 index = player->GetIndexForSpawn(this);
 
 	player->vis_mutex.writelock(__FUNCTION__, __LINE__);
 	uchar* orig_packet = player->GetSpawnVisPacketForXOR(id);
@@ -995,7 +990,7 @@ EQ2Packet* Spawn::spawn_update_packet(Player* player, int16 version, bool overri
 }
 
 uchar* Spawn::spawn_info_changes_ex(Player* player, int16 version) {
-	int16 index = player->player_spawn_index_map[this];
+	int16 index = player->GetIndexForSpawn(this);
 
 	PacketStruct* packet = player->GetSpawnInfoStruct();
 
@@ -1059,7 +1054,7 @@ uchar* Spawn::spawn_info_changes_ex(Player* player, int16 version) {
 
 uchar* Spawn::spawn_vis_changes_ex(Player* player, int16 version) {
 	PacketStruct* vis_struct = player->GetSpawnVisStruct();
-	int16 index = player->player_spawn_index_map[this];
+	int16 index = player->GetIndexForSpawn(this);
 
 	player->vis_mutex.writelock(__FUNCTION__, __LINE__);
 
@@ -3744,4 +3739,11 @@ bool Spawn::SetPermissionToEntityCommand(EntityCommand* command, Player* player,
 	}
 
 	return false;
+}
+
+void Spawn::RemoveSpawnFromPlayer(Player* player)
+{
+	m_Update.writelock(__FUNCTION__, __LINE__);
+	player->RemoveSpawn(this); // sets it as removed
+	m_Update.releasewritelock(__FUNCTION__, __LINE__);
 }

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

@@ -298,6 +298,8 @@ public:
 	void RemovePrimaryEntityCommand(const char* command);
 	bool SetPermissionToEntityCommand(EntityCommand* command, Player* player, bool permissionValue);
 
+	void RemoveSpawnFromPlayer(Player* player);
+
 	void AddSecondaryEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual){
 		secondary_command_list.push_back(CreateEntityCommand(name, distance, command, error_text, cast_time, spell_visual));
 	}

+ 1 - 1
EQ2/source/WorldServer/Tradeskills/Tradeskills.cpp

@@ -165,7 +165,7 @@ void TradeskillMgr::Process() {
 
 			PacketStruct* packet = configReader.getStruct("WS_UpdateCreateItem", client->GetVersion());
 			if (packet) {
-				packet->setDataByName("spawn_id", client->GetPlayer()->player_spawn_reverse_id_map[tradeskill->table]);
+				packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(tradeskill->table));
 				packet->setDataByName("effect", effect);
 				packet->setDataByName("total_durability", tradeskill->currentDurability);
 				packet->setDataByName("total_progress", tradeskill->currentProgress);

+ 50 - 27
EQ2/source/WorldServer/zoneserver.cpp

@@ -1055,9 +1055,7 @@ void ZoneServer::CheckSpawnRange(Spawn* spawn){
 }
 
 void ZoneServer::PrepareSpawnID(Player* player, Spawn* spawn){
-	player->spawn_id++;
-	player->player_spawn_id_map[player->spawn_id] = spawn;
-	player->player_spawn_reverse_id_map[spawn] = player->spawn_id;
+	player->SetSpawnMap(spawn);
 }
 
 void ZoneServer::CheckSendSpawnToClient(Client* client, bool initial_login) {
@@ -1071,24 +1069,24 @@ void ZoneServer::CheckSendSpawnToClient(Client* client, bool initial_login) {
 
 	Spawn* spawn = 0;
 	map<float, vector<Spawn*>* > closest_spawns;
-	if(spawn_range_map.count(client) > 0) {
-		if(initial_login || client->IsConnected()) {
+	if (spawn_range_map.count(client) > 0) {
+		if (initial_login || client->IsConnected()) {
 			MutexMap<int32, float >::iterator spawn_iter = spawn_range_map.Get(client)->begin();
-			while(spawn_iter.Next()) {
+			while (spawn_iter.Next()) {
 				spawn = GetSpawnByID(spawn_iter->first, true);
-				if(spawn && spawn->GetPrivateQuestSpawn()) {
-					if(!spawn->IsPrivateSpawn())
+				if (spawn && spawn->GetPrivateQuestSpawn()) {
+					if (!spawn->IsPrivateSpawn())
 						spawn->AddAllowAccessSpawn(spawn);
-					if(spawn->MeetsSpawnAccessRequirements(client->GetPlayer())) {
-						if(spawn->IsPrivateSpawn() && !spawn->AllowedAccess(client->GetPlayer()))
+					if (spawn->MeetsSpawnAccessRequirements(client->GetPlayer())) {
+						if (spawn->IsPrivateSpawn() && !spawn->AllowedAccess(client->GetPlayer()))
 							spawn->AddAllowAccessSpawn(client->GetPlayer());
 					}
-					else if(spawn->AllowedAccess(client->GetPlayer()))
+					else if (spawn->AllowedAccess(client->GetPlayer()))
 						spawn->RemoveSpawnAccess(client->GetPlayer());
 				}
-				if(spawn && spawn != client->GetPlayer() && client->GetPlayer()->ShouldSendSpawn(spawn)) {
-					if((!initial_login && spawn_iter->second <= SEND_SPAWN_DISTANCE) || (initial_login && (spawn_iter->second <= (SEND_SPAWN_DISTANCE/2) || spawn->IsWidget()))) {
-						if(closest_spawns.count(spawn_iter->second) == 0)
+				if (spawn && spawn != client->GetPlayer() && client->GetPlayer()->ShouldSendSpawn(spawn)) {
+					if ((!initial_login && spawn_iter->second <= SEND_SPAWN_DISTANCE) || (initial_login && (spawn_iter->second <= (SEND_SPAWN_DISTANCE / 2) || spawn->IsWidget()))) {
+						if (closest_spawns.count(spawn_iter->second) == 0)
 							closest_spawns[spawn_iter->second] = new vector<Spawn*>();
 						closest_spawns[spawn_iter->second]->push_back(spawn);
 						PrepareSpawnID(client->GetPlayer(), spawn);
@@ -1098,15 +1096,19 @@ void ZoneServer::CheckSendSpawnToClient(Client* client, bool initial_login) {
 		}
 		vector<Spawn*>::iterator spawn_iter2;
 		map<float, vector<Spawn*>* >::iterator itr;
-		for(itr = closest_spawns.begin(); itr != closest_spawns.end(); itr++) {
-			for(spawn_iter2 = itr->second->begin(); spawn_iter2 != itr->second->end(); spawn_iter2++) {
+		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;
 				client->GetPlayer()->ClearRemovedSpawn(spawn);
 				SendSpawn(spawn, client);
-				if(client->ShouldTarget() && client->GetCombineSpawn() == spawn)
+				if (client->ShouldTarget() && client->GetCombineSpawn() == spawn)
 					client->TargetSpawn(spawn);
 			}
-			delete itr->second;
+			vector<Spawn*>* vect = itr->second;
+			map<float, vector<Spawn*>* >::iterator tmpitr = itr;
+			itr++;
+			closest_spawns.erase(tmpitr);
+			safe_delete(vect);
 		}
 	}
 
@@ -1215,11 +1217,23 @@ void ZoneServer::DelayedSpawnRemoval(bool force_delete_all) {
 
 void ZoneServer::AddPendingDelete(Spawn* spawn) {
 	MSpawnDeleteList.writelock(__FUNCTION__, __LINE__);
-	if(spawn_delete_list.count(spawn) == 0)
+	if (spawn_delete_list.count(spawn) == 0)
 		spawn_delete_list.insert(make_pair(spawn, Timer::GetCurrentTime2() + spawn_delete_timer)); //give other threads up to 30 seconds to stop using this spawn reference
 	MSpawnDeleteList.releasewritelock(__FUNCTION__, __LINE__);
 }
 
+void ZoneServer::RemovePendingDelete(Spawn* spawn) {
+	if (!spawn)
+		return;
+
+	MSpawnDeleteList.writelock(__FUNCTION__, __LINE__);
+	if (spawn_delete_list.count(spawn) > 0)
+	{
+		spawn_delete_list.erase(spawn);
+	}
+	MSpawnDeleteList.releasewritelock(__FUNCTION__, __LINE__);
+}
+
 void ZoneServer::DeleteSpawns(bool delete_all) {
 	MSpawnDeleteList.writelock(__FUNCTION__, __LINE__);
 	if(spawn_delete_list.size() > 0){
@@ -2144,7 +2158,6 @@ Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respa
 			if (!spawn)
 			{
 				LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn to zone");
-				safe_delete(spawn);
 				continue;
 			}
 
@@ -3672,12 +3685,10 @@ bool ZoneServer::SendRemoveSpawn(Client* client, Spawn* spawn, PacketStruct* pac
 	LogWrite(ZONE__DEBUG, 7, "Zone", "%s: Processing SendRemoveSpawn for spawn index %u (%s)...cur_id: %i, wasremoved:: %i", client->GetPlayer()->GetName(), index, spawn->GetName(), cur_id, wasRemoved);
 	if(packet && index > 0 && !wasRemoved)
 	{
+		packet->ResetData();
+
 		packet->setDataByName("spawn_index", index);
-		client->GetPlayer()->GetPlayerSpawnMap()->erase(index);
-		client->GetPlayer()->GetPlayerSpawnIndexMap()->erase(spawn);
-		client->GetPlayer()->player_spawn_id_map.erase(cur_id);
-		client->GetPlayer()->player_spawn_reverse_id_map.erase(spawn);
-		client->GetPlayer()->RemoveSpawn(spawn); // sets it as removed
+		spawn->RemoveSpawnFromPlayer(client->GetPlayer());
 
 		if(delete_spawn)
 			packet->setDataByName("delete", 1);	
@@ -3921,6 +3932,8 @@ void ZoneServer::RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spa
 
 	safe_delete(packet);
 
+	bool alreadyDeletedSpawn = false;
+
 	if(respawn && !spawn->IsPlayer() && spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0)
 	{
 		LogWrite(ZONE__DEBUG, 3, "Zone", "Handle NPC Respawn for '%s'.", spawn->GetName());
@@ -3942,12 +3955,14 @@ void ZoneServer::RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spa
 				spawn->GetRespawnTime(),spawn->GetZone()->GetInstanceID());
 			}
 			safe_delete(spawn);
+			alreadyDeletedSpawn = true;
 		}
 		else
 		{
 			int32 spawnLocationID = spawn->GetSpawnLocationID();
 			int32 spawnRespawnTime = spawn->GetRespawnTime();
 			safe_delete(spawn);
+			alreadyDeletedSpawn = true;
 			respawn_timers.Put(spawnLocationID, Timer::GetCurrentTime2() + spawnRespawnTime * 1000);
 		}
 	}
@@ -3955,7 +3970,7 @@ void ZoneServer::RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spa
 	// Do we really need the mutex locks and check to dead_spawns as we remove it from dead spawns at the start of this function
 	if (lock && !respawn)
 		MDeadSpawns.readlock(__FUNCTION__, __LINE__);
-	if(!respawn && delete_spawn && dead_spawns.count(spawn->GetID()) == 0)
+	if(!alreadyDeletedSpawn && !respawn && delete_spawn && dead_spawns.count(spawn->GetID()) == 0)
 		AddPendingDelete(spawn);
 	if (lock && !respawn)
 		MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__);
@@ -6277,12 +6292,18 @@ void ZoneServer::HidePrivateSpawn(Spawn* spawn) {
 	Client* client = 0;	
 	Player* player = 0;
 	PacketStruct* packet = 0;
+	int32 packet_version = 0;
 	MutexList<Client*>::iterator itr = connected_clients.begin();
 	while (itr->Next()) {
 		client = itr->value;
 		player = client->GetPlayer();
 		if (player->WasSentSpawn(spawn->GetID()) && !player->WasSpawnRemoved(spawn)) {
-			packet = configReader.getStruct("WS_DestroyGhostCmd", client->GetVersion());
+			if (!packet || packet_version != client->GetVersion()) {
+				safe_delete(packet);
+				packet_version = client->GetVersion();
+				packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version);
+			}
+
 			SendRemoveSpawn(client, spawn, packet);
 			if(spawn_range_map.count(client) > 0)
 				spawn_range_map.Get(client)->erase(spawn->GetID());
@@ -6291,6 +6312,8 @@ void ZoneServer::HidePrivateSpawn(Spawn* spawn) {
 				player->SetTarget(0);
 		}
 	}
+
+	safe_delete(packet);
 }
 
 SpawnLocation* ZoneServer::GetSpawnLocation(int32 id) {

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

@@ -711,6 +711,7 @@ private:
 	void	ReloadTransporters();																				// never used outside zone server
 	void	DeleteSpawns(bool delete_all);																		// never used outside zone server
 	void	AddPendingDelete(Spawn* spawn);																		// never used outside zone server
+	void	RemovePendingDelete(Spawn* spawn);																		// never used outside zone server
 	void	ClearDeadSpawns();																					// never used outside zone server
 	void	RemoveChangedSpawn(Spawn* spawn);																	// never used outside zone server
 	void	ProcessDrowning();																					// never used outside zone server