Browse Source

Address starting_zones limitation, fix harvesting / removespawn crashes

Fix #228
Fix #229

/waypoint now allows target if you are a GM (over 100 status)
Image 3 years ago
parent
commit
2f8d68244d

+ 8 - 5
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -1611,6 +1611,10 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				if (!client->ShowPathToTarget(atof(sep->arg[0]), atof(sep->arg[1]), atof(sep->arg[2]), 0))
 					client->Message(CHANNEL_COLOR_RED, "Invalid coordinates given");
 			}
+			else if ( client->GetAdminStatus() > 100 && cmdTarget ) {
+				if (!client->ShowPathToTarget(cmdTarget->GetX(), cmdTarget->GetY(), cmdTarget->GetZ(), 0))
+					client->Message(CHANNEL_COLOR_RED, "Invalid coordinates given");
+			}
 			else {
 				client->ClearWaypoint();
 				client->Message(CHANNEL_COLOR_YELLOW, "Usage: /waypoint x y z");
@@ -1917,7 +1921,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				if(dead && dead->IsPlayer() == false){
 					dead->SetHP(0);
 					if(sep && sep->arg[0] && sep->IsNumber(0) && atoi(sep->arg[0]) == 1)
-						client->GetCurrentZone()->RemoveSpawn(false, dead, true);
+						client->GetCurrentZone()->RemoveSpawn(dead, true);
 					else
 						client->GetPlayer()->KillSpawn(dead);
 				}else{
@@ -2858,7 +2862,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			query.RunQuery2(Q_INSERT, "delete from spawn_instance_data where spawn_id = %u and spawn_location_id = %u and pickup_item_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), spawn->GetPickupItemID());
 
 			if (database.RemoveSpawnFromSpawnLocation(spawn)) {
-				client->GetCurrentZone()->RemoveSpawn(false, spawn, true, true);
+				client->GetCurrentZone()->RemoveSpawn(spawn, true, true);
 			}
 
 			// we had a UI Window displayed, update the house items
@@ -2971,8 +2975,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					if (client->GetTempPlacementSpawn())
 					{
 						Spawn* tmp = client->GetTempPlacementSpawn();
-						client->GetCurrentZone()->RemoveSpawn(false, tmp);
-						delete tmp;
+						client->GetCurrentZone()->RemoveSpawn(tmp);
 						client->SetTempPlacementSpawn(nullptr);
 					}
 
@@ -3912,7 +3915,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			if(spawn && !spawn->IsPlayer()){
 				if(spawn->GetSpawnLocationID() > 0){
 					if(database.RemoveSpawnFromSpawnLocation(spawn)){
-						client->GetCurrentZone()->RemoveSpawn(false, spawn, true, false);
+						client->GetCurrentZone()->RemoveSpawn(spawn, true, false);
 						client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully removed spawn from zone");
 					}
 					else

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

@@ -1164,7 +1164,7 @@ void Entity::DismissPet(NPC* pet, bool from_death, bool spawnListLocked) {
 
 	// remove the spawn from the world
 	if (!from_death && pet->GetPetType() != PET_TYPE_CHARMED)
-		GetZone()->RemoveSpawn(spawnListLocked, pet);
+		GetZone()->RemoveSpawn(pet);
 }
 
 float Entity::CalculateBonusMod() {

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

@@ -29,6 +29,7 @@
 #include "classes.h"
 #include "Variables.h"
 #include "SpellProcess.h"
+#include "Rules/Rules.h"
 #include "../common/Log.h"
 #include <math.h>
 #include "HeroicOp/HeroicOp.h"
@@ -56,6 +57,7 @@ extern MasterSkillList master_skill_list;
 extern MasterHeroicOPList master_ho_list;
 extern MasterRaceTypeList race_types_list;
 extern MasterLanguagesList master_languages_list;
+extern RuleManager rule_manager;
 
 vector<string> ParseString(string strVal, char delim) {
 	stringstream ss(strVal);
@@ -3925,7 +3927,7 @@ int EQ2Emu_lua_Harvest(lua_State* state) {
 
 			((GroundSpawn*)node)->ProcessHarvest(client);
 			if (((GroundSpawn*)node)->GetNumberHarvests() == 0)
-				player->GetZone()->RemoveSpawn(true, node, true);
+				player->GetZone()->RemoveSpawn(node, true);
 		}
 	}
 	else if (player && player->IsPlayer()) {
@@ -11105,4 +11107,20 @@ int EQ2Emu_lua_IsInvulnerable(lua_State* state) {
 		return 1;
 	}
 	return 0;
+}
+
+int EQ2Emu_lua_GetRuleFlagInt32(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	string category = lua_interface->GetStringValue(state);
+	string name = lua_interface->GetStringValue(state, 2);
+	Rule *ret = 0;
+	if ((ret = rule_manager.GetGlobalRule(category.c_str(), name.c_str()))) {
+		
+		lua_interface->SetInt32Value(state, ret->GetInt32());
+		return 1;
+	}
+	
+	lua_interface->LogError("%s: LUA GetRuleFlag Unknown rule with category '%s' and type '%s'", lua_interface->GetScriptName(state), category.c_str(), name.c_str());
+	return 0;
 }

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

@@ -519,4 +519,6 @@ int EQ2Emu_lua_InLava(lua_State* state);
 int EQ2Emu_lua_DamageSpawn(lua_State* state);
 
 int EQ2Emu_lua_IsInvulnerable(lua_State* state);
+
+int EQ2Emu_lua_GetRuleFlagInt32(lua_State* state);
 #endif

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

@@ -1233,6 +1233,8 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	
 	lua_register(state, "DamageSpawn", EQ2Emu_lua_DamageSpawn);
 	lua_register(state, "IsInvulnerable", EQ2Emu_lua_IsInvulnerable);
+	
+	lua_register(state, "GetRuleFlagInt32", EQ2Emu_lua_GetRuleFlagInt32);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {

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

@@ -269,6 +269,11 @@ void RuleManager::Init()
 	RULE_INIT(R_World, SaveHeadshotImage, "1");						// default: true
 	RULE_INIT(R_World, SendPaperdollImagesToLogin, "1");			// default: true
 	RULE_INIT(R_World, TreasureChestDisabled, "0");					// default: false
+	RULE_INIT(R_World, StartingZoneRuleFlag, "0");					// default: 0 - match any options available, just based on version/other fields (will not force qc/outpost)
+																	// 1 - force split zones on alignment/deity despite client selection (queens colony/overlord outpost)
+																	// 2 - (isle of refuge)
+																	// 4 - send to 'new' starting zones, won't support old clients
+																	// 5+ - send to new and old starting zones as needed
 	//INSERT INTO `ruleset_details`(`id`, `ruleset_id`, `rule_category`, `rule_type`, `rule_value`, `description`) VALUES (NULL, '1', 'R_World', '', '', '')
 
 	/* ZONE */
@@ -388,6 +393,10 @@ Rule * RuleManager::GetGlobalRule(int32 category, int32 type) {
 	return global_rule_set.GetRule(category, type);
 }
 
+Rule * RuleManager::GetGlobalRule(const char* category, const char* type) {
+	return global_rule_set.GetRule(category, type);
+}
+
 bool RuleManager::SetZoneRuleSet(int32 zone_id, int32 rule_set_id) {
 	bool ret = true;
 	RuleSet *rule_set;

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

@@ -127,6 +127,7 @@ enum RuleType {
 	SaveHeadshotImage,
 	SendPaperdollImagesToLogin,
 	TreasureChestDisabled,
+	StartingZoneRuleFlag,
 
 	/* ZONE */
 	MinZoneLevelOverrideStatus,
@@ -247,6 +248,7 @@ public:
 
 	bool SetGlobalRuleSet(int32 rule_set_id);
 	Rule * GetGlobalRule(int32 category, int32 type);
+	Rule * GetGlobalRule(const char* category, const char* type);
 
 	bool SetZoneRuleSet(int32 zone_id, int32 rule_set_id);
 	Rule * GetZoneRule(int32 zone_id, int32 category, int32 type);

+ 79 - 13
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -2117,7 +2117,7 @@ int32 WorldDatabase::SaveCharacter(PacketStruct* create, int32 loginID){
 	int32 last_insert_id = query.GetLastInsertedID();
 	int32 char_id = last_insert_id;
 	UpdateStartingFactions(char_id, create->getType_int8_ByName("starting_zone"));
-	UpdateStartingZone(char_id, class_id, race_id, create->getType_int8_ByName("starting_zone"));
+	UpdateStartingZone(char_id, class_id, race_id, create);
 	UpdateStartingItems(char_id, class_id, race_id);
 	UpdateStartingSkills(char_id, class_id, race_id);
 	UpdateStartingSpells(char_id, class_id, race_id);
@@ -3264,7 +3264,7 @@ int32 WorldDatabase::SaveCombinedSpawnLocation(ZoneServer* zone, Spawn* in_spawn
 		}
 		for(itr=spawns->begin();itr!=spawns->end();itr++){
 			spawn = *itr;
-			zone->RemoveSpawn(false, spawn);
+			zone->RemoveSpawn(spawn);
 		}
 		safe_delete(spawns);
 	}
@@ -3442,21 +3442,37 @@ string WorldDatabase::GetStartingZoneName(int8 choice){
 	return zone_name;
 }
 
-void WorldDatabase::UpdateStartingZone(int32 char_id, int8 class_id, int8 race_id, int8 choice)
+void WorldDatabase::UpdateStartingZone(int32 char_id, int8 class_id, int8 race_id, PacketStruct* create)
 {
 	Query query;
 
-	LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default zone for race: %i, class: %i for char_id: %u (choice: %i)", race_id, class_id, char_id, choice);
+	int32 packetVersion = create->GetVersion();
+	int8 choice = create->getType_int8_ByName("starting_zone"); // 0 = far journey, 1 = isle of refuge
+	int8 deity = create->getType_int8_ByName("deity"); // aka 'alignment' for early DOF, 0 = evil, 1 = good
+	int32 startingZoneRuleFlag = rule_manager.GetGlobalRule(R_World, StartingZoneRuleFlag)->GetInt32();
+	
+	if((startingZoneRuleFlag == 1 || startingZoneRuleFlag == 2) && choice > 1)
+	{
+		LogWrite(PLAYER__INFO, 0, "Player", "Starting zone rule flag %u override choice %u to deity value of %u", startingZoneRuleFlag, choice, deity);
+		choice = deity; // inherit deity to know starting choice is 'good' or evil
+	}
+	
+	LogWrite(PLAYER__INFO, 0, "Player", "Adding default zone for race: %i, class: %i for char_id: %u (choice: %i), deity(alignment): %u, version: %u.", race_id, class_id, char_id, choice, deity, packetVersion);
 
 	// first, check to see if there is a starting_zones record for this race/class/choice combo (now using extended Archetype/BaseClass/Class combos
 	MYSQL_RES* result = 0;
 	
+	string whereRuleFlag("");
+	if(startingZoneRuleFlag > 0)
+		whereRuleFlag = string(" AND ruleflag & " + std::to_string(startingZoneRuleFlag));
+	
+	string syntaxSelect("SELECT z.name, sz.zone_id, z.safe_x, z.safe_y, z.safe_z, sz.x, sz.y, sz.z, sz.heading, sz.is_instance FROM");
 	if ( class_id == 0 )
-		result = query.RunQuery2(Q_SELECT, "SELECT `name` FROM starting_zones sz, zones z WHERE sz.zone_id = z.id AND class_id = 255 AND race_id IN (%i, 255) AND choice = %u",
-			race_id, choice);
+		result = query.RunQuery2(Q_SELECT, "%s starting_zones sz, zones z WHERE sz.zone_id = z.id AND class_id = 255 AND race_id IN (%i, 255) AND deity IN (%i, 255) AND choice = %u AND (min_version = 0 or min_version <= %u) AND (max_version = 0 or max_version >= %u)%s",
+			syntaxSelect.c_str(), race_id, deity, choice, packetVersion, packetVersion, whereRuleFlag.c_str());
 		else
-			result = query.RunQuery2(Q_SELECT, "SELECT `name` FROM starting_zones sz, zones z WHERE sz.zone_id = z.id AND class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) AND choice IN (%i, 255)",
-		classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id, choice);
+			result = query.RunQuery2(Q_SELECT, "%s starting_zones sz, zones z WHERE sz.zone_id = z.id AND class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) AND deity IN (%i, 255) AND choice IN (%i, 255) AND (min_version = 0 or min_version <= %u) AND (max_version = 0 or max_version >= %u)%s",
+		syntaxSelect.c_str(), classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id, deity, choice, packetVersion, packetVersion, whereRuleFlag.c_str());
 
 	// TODO: verify client version so clients do not crash trying to enter zones they do not own (paks)
 	if(result && mysql_num_rows(result) > 0)
@@ -3464,15 +3480,65 @@ void WorldDatabase::UpdateStartingZone(int32 char_id, int8 class_id, int8 race_i
 		string zone_name = "ERROR";
 		MYSQL_ROW row;
 
+		bool zoneSet = false;
+
+		float safeX = 0.0f, safeY = 0.0f, safeZ = 0.0f, x = 0.0f, y = 0.0f, z = 0.0f, heading = 0.0f;
+		int8 is_instance = 0;
+		int32 zone_id = 0;
+		int32 instance_id = 0;
+
 		if( result && (row = mysql_fetch_row(result)) )
-			zone_name = string(row[0]);
+		{
+			int8 i=0;
+
+			zoneSet = true;
+
+			zone_name = string(row[i++]);
+
+			zone_id = atoul(row[i++]);
 
+			safeX = atof(row[i++]);
+			safeY = atof(row[i++]);
+			safeZ = atof(row[i++]);
+
+			x = atof(row[i++]);
+			y = atof(row[i++]);
+			z = atof(row[i++]);
+
+			if ( x == -999999.0f && y == -999999.0f && z == -999999.0f)
+			{
+				x = safeX;
+				y = safeY;
+				z = safeZ;
+			}
+
+			heading = atof(row[i++]);
+
+			if(heading == -999999.0f )
+				heading = 0.0f;
+			
+			is_instance = atoul(row[i++]);
+		}
+
+		if(is_instance) // should only be true if we get a result
+		{
+			// this will force a pre-load
+			ZoneServer* instance_zone = zone_list.GetByInstanceID(0, zone_id);
+			if (instance_zone) {
+				instance_id = CreateNewInstance(zone_id);
+				AddCharacterInstance(char_id, instance_id, string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime());
+				// make sure we inherit the instance id setup in the AddCharacterInstance
+				instance_zone->SetupInstance(instance_id);
+			}
+		}
 		if (class_id == 0)
-			query.RunQuery2(Q_UPDATE, "UPDATE characters c, zones z, starting_zones sz SET c.current_zone_id = z.id, c.x = z.safe_x, c.y = z.safe_y, c.z = z.safe_z, c.starting_city = z.id WHERE z.id = sz.zone_id AND sz.class_id = 255 AND sz.race_id IN (%i, 255) AND sz.choice = %u AND c.id = %u",
+			query.RunQuery2(Q_UPDATE, "UPDATE characters c, zones z, starting_zones sz SET c.current_zone_id = z.id, c.x = %f, c.y = %f, c.z = %f, c.heading = %f, c.starting_city = z.id, c.instance_id = %u WHERE z.id = sz.zone_id AND sz.class_id = 255 AND sz.race_id IN (%i, 255) AND sz.choice = %u AND c.id = %u",
+				x, y, z, heading, instance_id, 
 				race_id, choice, char_id);
 		else
-			query.RunQuery2(Q_UPDATE, "UPDATE characters c, zones z, starting_zones sz SET c.current_zone_id = z.id, c.x = z.safe_x, c.y = z.safe_y, c.z = z.safe_z, c.starting_city = %i WHERE z.id = sz.zone_id AND sz.class_id IN (%i, %i, %i, 255) AND sz.race_id IN (%i, 255) AND sz.choice IN (%i, 255) AND c.id = %u", 
-				choice, classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id, choice, char_id);
+			query.RunQuery2(Q_UPDATE, "UPDATE characters c, zones z, starting_zones sz SET c.current_zone_id = z.id, c.x = %f, c.y = %f, c.z = %f, c.heading = %f, c.starting_city = %i, c.instance_id = %u WHERE z.id = sz.zone_id AND sz.class_id IN (%i, %i, %i, 255) AND sz.race_id IN (%i, 255) AND sz.choice IN (%i, 255) AND c.id = %u", 
+				x, y, z, heading, choice, instance_id, 
+				classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id, choice, char_id);
 
 		if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
 			LogWrite(PLAYER__ERROR, 0, "Player", "Error in UpdateStartingZone custom starting_zones, query: '%s': %s", query.GetQuery(), query.GetError());
@@ -3481,7 +3547,7 @@ void WorldDatabase::UpdateStartingZone(int32 char_id, int8 class_id, int8 race_i
 
 		if(query.GetAffectedRows() > 0)
 		{
-			LogWrite(PLAYER__DEBUG, 0, "Player", "Setting New Character Starting Zone to '%s' FROM starting_zones table.", zone_name.c_str());
+			LogWrite(PLAYER__INFO, 0, "Player", "Setting New Character Starting Zone to '%s' with location %f, %f, %f and heading %f FROM starting_zones table.", zone_name.c_str(), x, y, z, heading);
 			return;
 		}
 	}

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

@@ -203,7 +203,7 @@ public:
 	void	UpdateDataTableVersion(char* name, int32 version);
 	void	UpdateStartingFactions(int32 char_id, int8 choice);
 	string	GetStartingZoneName(int8 choice);
-	void	UpdateStartingZone(int32 char_id, int8 class_id, int8 race_id, int8 choice);
+	void	UpdateStartingZone(int32 char_id, int8 class_id, int8 race_id, PacketStruct* create);
 	void	UpdateStartingItems(int32 char_id, int8 class_id, int8 race_id, bool base_class = false);
 	void	UpdateStartingSkills(int32 char_id, int8 class_id, int8 race_id);
 	void	UpdateStartingSpells(int32 char_id, int8 class_id, int8 race_id);

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

@@ -621,7 +621,7 @@ void Client::HandlePlayerRevive(int32 point_id)
 		safe_delete(packet);
 	}
 
-	GetCurrentZone()->RemoveSpawn(false, player, false);
+	GetCurrentZone()->RemoveSpawn(player, false);
 
 	m_resurrect.writelock(__FUNCTION__, __LINE__);
 	if (current_rez.active)
@@ -1200,8 +1200,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		if (GetTempPlacementSpawn())
 		{
 			Spawn* tmp = GetTempPlacementSpawn();
-			GetCurrentZone()->RemoveSpawn(false, tmp);
-			delete tmp;
+			GetCurrentZone()->RemoveSpawn(tmp);
 			SetTempPlacementSpawn(nullptr);
 			SetPlacementUniqueItemID(0);
 			break; // break out early if we are tied to a temp spawn
@@ -3152,7 +3151,7 @@ void ClientList::Remove(Client* client, bool remove_data) {
 void Client::SetCurrentZone(int32 id) {
 	if (current_zone) {
 		//current_zone->GetCombat()->RemoveHate(player);
-		current_zone->RemoveSpawn(false, player, false);
+		current_zone->RemoveSpawn(player, false);
 	}
 	SetCurrentZone(zone_list.Get(id));
 
@@ -3161,7 +3160,7 @@ void Client::SetCurrentZone(int32 id) {
 void Client::SetCurrentZoneByInstanceID(int32 id, int32 zoneid) {
 	if (current_zone) {
 		//current_zone->GetCombat()->RemoveHate(player);
-		current_zone->RemoveSpawn(false, player, false);
+		current_zone->RemoveSpawn(player, false);
 	}
 	SetCurrentZone(zone_list.GetByInstanceID(id, zoneid));
 
@@ -3820,7 +3819,7 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords) {
 
 
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from current zone...", __FUNCTION__);
-	GetCurrentZone()->RemoveSpawn(false, player, false);
+	GetCurrentZone()->RemoveSpawn(player, false);
 
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting zone to '%s'...", __FUNCTION__, new_zone->GetZoneName());
 	SetCurrentZone(new_zone);
@@ -6016,7 +6015,7 @@ void Client::SaveCombineSpawns(const char* name) {
 	else if ((spawnLocationID = database.SaveCombinedSpawnLocation(GetCurrentZone(), combine_spawn, name)) > 0) {
 		Message(CHANNEL_COLOR_YELLOW, "Successfully combined %u spawns into spawn location: %u", count, spawnLocationID);
 		// we remove the spawn inside SaveCombinedSpawnLocation
-		//GetCurrentZone()->RemoveSpawn(false, combine_spawn);
+		//GetCurrentZone()->RemoveSpawn(combine_spawn);
 	}
 	else
 		SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn group, check console for details.");

+ 32 - 51
EQ2/source/WorldServer/zoneserver.cpp

@@ -271,17 +271,9 @@ void ZoneServer::Init()
 	world.LoadRegionMaps(zoneName);
 	
 	world.LoadMaps(zoneName);
-/*	if (Grid == nullptr) {
-		Grid = new SPGrid(string(GetZoneFile()), 0);
-	}
-	if (zonemap == nullptr) {
-		zonemap = Map::LoadMapFile(zoneName, Grid);
-	}*/
-
+	
 	pathing = IPathfinder::Load(zoneName);
 	movementMgr = new MobMovementManager();
-//	else
-	//	LogWrite(ZONE__ERROR, 0, "SPGrid", "ZoneServer::Init() Grid is not null in init, wtf!");
 
 	MMasterSpawnLock.SetName("ZoneServer::MMasterSpawnLock");
 	m_npc_faction_list.SetName("ZoneServer::npc_faction_list");
@@ -1461,6 +1453,9 @@ bool ZoneServer::SpawnProcess(){
 		bool aggroCheck = aggro_timer.Check();
 		vector<int32> pending_spawn_list_remove;
 
+		// Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them all
+		ProcessSpawnRemovals();
+		
 		map<int32, Spawn*>::iterator itr;
 		if (spawnRange || checkRemove)
 		{
@@ -1500,19 +1495,6 @@ bool ZoneServer::SpawnProcess(){
 			SendSpawnChanges();
 		}
 
-		// Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them all
-		MPendingSpawnRemoval.writelock(__FUNCTION__, __LINE__);
-		if (m_pendingSpawnRemove.size() > 0) {
-			MSpawnList.writelock(__FUNCTION__, __LINE__);
-			vector<int32>::iterator itr2;
-			for (itr2 = m_pendingSpawnRemove.begin(); itr2 != m_pendingSpawnRemove.end(); itr2++) 
-				spawn_list.erase(*itr2);
-
-			m_pendingSpawnRemove.clear();
-			MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
-		}
-		MPendingSpawnRemoval.releasewritelock(__FUNCTION__, __LINE__);
-
 		if (movement || aggroCheck)
 		{
 			MSpawnList.readlock(__FUNCTION__, __LINE__);
@@ -1558,6 +1540,10 @@ bool ZoneServer::SpawnProcess(){
 			pending_spawn_list_remove.clear();
 			MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
 		}
+		
+		// Double Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them before we replace with pending spawns
+		// and also potentially further down when we delete the Spawn* in DeleteSpawns(false)
+		ProcessSpawnRemovals();
 
 		// Check to see if there are spawns waiting to be added to the spawn list, if so add them all
 		if (pending_spawn_list_add.size() > 0) {
@@ -1627,7 +1613,7 @@ void ZoneServer::CheckDeadSpawnRemoval() {
 				{
 					dead_spawns.erase(spawn->GetID());
 					MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__);
-					RemoveSpawn(false, spawn, true, true, true);
+					RemoveSpawn(spawn, true, true, true);
 					MDeadSpawns.writelock(__FUNCTION__, __LINE__);
 				}
 			}
@@ -3001,7 +2987,7 @@ void ZoneServer::RemoveClient(Client* client)
 		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);
 		
-		RemoveSpawn(false, client->GetPlayer(), false);
+		RemoveSpawn(client->GetPlayer(), false);
 		connected_clients.Remove(client, true, DisconnectClientTimer); // changed from a hardcoded 30000 (30 sec) to the DisconnectClientTimer rule
 	}
 }
@@ -3787,12 +3773,12 @@ void ZoneServer::RemoveFromRangeMap(Client* client){
 }
 */
 
-void ZoneServer::RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spawn, bool respawn, bool lock) 
+void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool lock) 
 {
 	LogWrite(ZONE__DEBUG, 3, "Zone", "Processing RemoveSpawn function for %s (%i)...", spawn->GetName(),spawn->GetID());
 
 	spawn->RemoveSpawnProximities();
-	RemoveSpawnProximities(spawnListLocked, spawn);
+	RemoveSpawnProximities(spawn);
 
 	if (movementMgr != nullptr && spawn->IsEntity()) {
 		movementMgr->RemoveMob((Entity*)spawn);
@@ -3813,17 +3799,8 @@ void ZoneServer::RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spa
 	if (spawn_expire_timers.count(spawn->GetID()) > 0)
 		spawn_expire_timers.erase(spawn->GetID());
 	
-	// Clear the pointer in the spawn list, spawn thread will remove the key
-	if (!spawnListLocked)
-	{
-		MSpawnList.writelock(__FUNCTION__, __LINE__);
-		spawn_list.erase(spawn->GetID());
-		MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
-	}
-	else
-	{
-		AddPendingSpawnRemove(spawn->GetID());
-	}
+	// we will remove the spawn ptr and entry in the spawn_list later.. it is not safe right now (lua? client process? spawn process? etc? too many factors)
+	AddPendingSpawnRemove(spawn->GetID());
 
 	PacketStruct* packet = 0;
 	int16 packet_version = 0;
@@ -3854,9 +3831,6 @@ void ZoneServer::RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spa
 	MClientList.releasereadlock(__FUNCTION__, __LINE__);
 
 	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());
@@ -3877,15 +3851,11 @@ void ZoneServer::RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spa
 				database.CreateInstanceSpawnRemoved(spawn->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT, 
 				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);
 		}
 	}
@@ -3893,7 +3863,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(!alreadyDeletedSpawn && !respawn && delete_spawn && dead_spawns.count(spawn->GetID()) == 0)
+	if(delete_spawn && dead_spawns.count(spawn->GetID()) == 0)
 		AddPendingDelete(spawn);
 	if (lock && !respawn)
 		MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__);
@@ -7642,11 +7612,10 @@ void ZoneServer::AddSpawnProximities(Spawn* newSpawn) {
 	MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
 }
 
-void ZoneServer::RemoveSpawnProximities(bool spawnListLocked, Spawn* oldSpawn) {
+// we only call this inside a write lock
+void ZoneServer::RemoveSpawnProximities(Spawn* oldSpawn) {
 	Spawn* spawn = 0;
 	map<int32, Spawn*>::iterator itr;
-	if (!spawnListLocked)
-		MSpawnList.readlock(__FUNCTION__, __LINE__);
 	for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
 		spawn = itr->second;
 		if (spawn && spawn != oldSpawn) {
@@ -7658,9 +7627,6 @@ void ZoneServer::RemoveSpawnProximities(bool spawnListLocked, Spawn* oldSpawn) {
 			// don't need to remove oldSpawn proximities, we clear them all out
 		}
 	}
-
-	if (!spawnListLocked)
-		MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
 }
 
 void ZoneServer::SetSpawnScript(SpawnEntry* entry, Spawn* spawn)
@@ -7805,4 +7771,19 @@ void ZoneServer::AddPendingSpawnRemove(int32 id)
 		MPendingSpawnRemoval.writelock(__FUNCTION__, __LINE__);
 		m_pendingSpawnRemove.push_back(id);
 		MPendingSpawnRemoval.releasewritelock(__FUNCTION__, __LINE__);
+}
+
+void ZoneServer::ProcessSpawnRemovals()
+{
+	MPendingSpawnRemoval.writelock(__FUNCTION__, __LINE__);
+	if (m_pendingSpawnRemove.size() > 0) {
+		MSpawnList.writelock(__FUNCTION__, __LINE__);
+		vector<int32>::iterator itr2;
+		for (itr2 = m_pendingSpawnRemove.begin(); itr2 != m_pendingSpawnRemove.end(); itr2++) 
+			spawn_list.erase(*itr2);
+
+		m_pendingSpawnRemove.clear();
+		MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
+	}
+	MPendingSpawnRemoval.releasewritelock(__FUNCTION__, __LINE__);
 }

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

@@ -303,7 +303,7 @@ public:
 	
 	void	AddSpawnGroupChance(int32 group_id, float percent);
 	
-	void	RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spawn = true, bool respawn = true, bool lock = true);
+	void	RemoveSpawn(Spawn* spawn, bool delete_spawn = true, bool respawn = true, bool lock = true);
 	void	ProcessSpawnLocations();
 	void	SendQuestUpdates(Client* client, Spawn* spawn = 0);
 	
@@ -645,7 +645,7 @@ public:
 	void SetSpawnStructs(Client* client);
 
 	void AddSpawnProximities(Spawn* spawn);
-	void RemoveSpawnProximities(bool spawnListLocked, Spawn* spawn);
+	void RemoveSpawnProximities(Spawn* spawn);
 	void SetSpawnScript(SpawnEntry* entry, Spawn* spawn);
 	bool IsLoading() {
 		return LoadingData;
@@ -662,6 +662,7 @@ public:
 	void CancelThreads();
 
 	void AddPendingSpawnRemove(int32 id);
+	void ProcessSpawnRemovals();
 private:
 #ifndef WIN32
 	pthread_t ZoneThread;