Browse Source

Fix #381 - NPC Spell Enhancements
- spawn_npc_spells is now globally loaded (World level) instead of per Zone.
- spawn_npc_spells now has a on_spawn_cast and on_aggro_cast column, these are as described. on_spawn_cast will be subset of buffing, but first casted. Same with on aggro, once on aggro is casted it will no longer cast unless using SetCastOnAggroComplete to reset.
- DB Changes:
alter table spawn_npc_spells add on_spawn_cast tinyint(3) unsigned default 0;
alter table spawn_npc_spells add on_aggro_cast tinyint(3) unsigned default 0;

- NPC AI code updated to support spawn group targets for buffs
- Fixed NPC's being unable to cast friendly spells on each other (heals should work and other spells now)
- Added /reload spells npc command to reload specifically npc spell lists, /reload spells also will include them
- Added LUA Function IsCastOnAggroComplete(NPC) / SetCastOnAggroComplete(NPC, true|false) - this will allow overriding the cast on aggro, if you want the NPC to repeatedly trigger sublists you can keep setting to false

Emagi 1 năm trước cách đây
mục cha
commit
f0e3800333

+ 1 - 1
EQ2/source/WorldServer/Bots/Bot.h

@@ -38,7 +38,7 @@ public:
 	void SetRecast(Spell* spell, int32 time);
 	bool ShouldMelee();
 
-	Spell* GetNextBuffSpell() { return GetBuffSpell(); }
+	Spell* GetNextBuffSpell(Spawn* target = 0) { return GetBuffSpell(); }
 	Spell* GetHealSpell();
 	Spell* GetRezSpell();
 

+ 20 - 9
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -1869,6 +1869,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload luasystem");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spawnscripts");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spells");
+		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spells npc");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload quests");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spawns");
 		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload groundspawns");
@@ -1907,15 +1908,25 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 		break;
 	}
 	case COMMAND_RELOAD_SPELLS: {
-		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Spells...");
-		world.SetReloadingSubsystem("Spells");
-		zone_list.DeleteSpellProcess();
-		master_spell_list.Reload();
-		if (lua_interface)
-			lua_interface->ReloadSpells();
-		zone_list.LoadSpellProcess();
-		world.RemoveReloadingSubSystem("Spells");
-		client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");		
+		if (sep && sep->arg[0] && strcmp(sep->arg[0], "npc") == 0) {
+			client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading NPC Spells Lists (Note: Must Reload Spawns/Repop to reset npc spells)...");
+			world.PurgeNPCSpells();
+			database.LoadNPCSpells();
+			client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
+		}
+		else {
+			client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Spells & NPC Spell Lists (Note: Must Reload Spawns/Repop to reset npc spells)...");
+			world.SetReloadingSubsystem("Spells");
+			zone_list.DeleteSpellProcess();
+			master_spell_list.Reload();
+			if (lua_interface)
+				lua_interface->ReloadSpells();
+			zone_list.LoadSpellProcess();
+			world.RemoveReloadingSubSystem("Spells");
+			world.PurgeNPCSpells();
+			database.LoadNPCSpells();
+			client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
+		}		
 		break;
 	}
 	case COMMAND_RELOAD_GROUNDSPAWNS: {

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

@@ -4856,7 +4856,7 @@ int EQ2Emu_lua_SetSpellList(lua_State* state) {
 		NPC* npc = (NPC*)spawn;
 		npc->SetPrimarySpellList(primary_list);
 		npc->SetSecondarySpellList(secondary_list);
-		npc->SetSpells(npc->GetZone()->GetNPCSpells(npc->GetPrimarySpellList(), npc->GetSecondarySpellList()));
+		npc->SetSpells(world.GetNPCSpells(npc->GetPrimarySpellList(), npc->GetSecondarySpellList()));
 	}
 	return 0;
 }
@@ -13145,5 +13145,53 @@ int EQ2Emu_lua_SetPlayerPOVGhost(lua_State* state) {
 	lua_interface->ResetFunctionStack(state);
 
 	lua_interface->SetBooleanValue(state, success_sight);
+	return 1;
+}
+
+
+int EQ2Emu_lua_SetCastOnAggroComplete(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	bool result = false;
+	
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	bool cast_completed = (lua_interface->GetInt8Value(state, 2) == 1);
+	lua_interface->ResetFunctionStack(state);
+
+	if (!spawn)
+		lua_interface->LogError("%s: LUA SetCastOnAggroComplete error: Could not find spawn.", lua_interface->GetScriptName(state));
+	else if (!spawn->IsNPC())
+		lua_interface->LogError("%s: LUA SetCastOnAggroComplete error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName());
+	else
+	{
+		((NPC*)spawn)->cast_on_aggro_completed = cast_completed;
+		result = true;
+	}
+
+	lua_interface->SetBooleanValue(state, result);
+
+	return 1;
+}
+
+int EQ2Emu_lua_IsCastOnAggroComplete(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	bool result = false;
+	
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
+
+	if (!spawn)
+		lua_interface->LogError("%s: LUA IsCastOnAggroComplete error: Could not find spawn.", lua_interface->GetScriptName(state));
+	else if (!spawn->IsNPC())
+		lua_interface->LogError("%s: LUA IsCastOnAggroComplete error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName());
+	else
+	{
+		if(((NPC*)spawn)->cast_on_aggro_completed)
+			result = true;
+	}
+
+	lua_interface->SetBooleanValue(state, result);
+
 	return 1;
 }

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

@@ -628,4 +628,6 @@ int EQ2Emu_lua_CreateWidgetRegion(lua_State* state);
 int EQ2Emu_lua_RemoveRegion(lua_State* state);
 
 int EQ2Emu_lua_SetPlayerPOVGhost(lua_State* state);
+int EQ2Emu_lua_SetCastOnAggroComplete(lua_State* state);
+int EQ2Emu_lua_IsCastOnAggroComplete(lua_State* state);
 #endif

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

@@ -1490,6 +1490,8 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "RemoveRegion", EQ2Emu_lua_RemoveRegion);
 	
 	lua_register(state, "SetPlayerPOVGhost", EQ2Emu_lua_SetPlayerPOVGhost);
+	
+	lua_register(state, "SetCastOnAggroComplete", EQ2Emu_lua_SetCastOnAggroComplete);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {

+ 109 - 9
EQ2/source/WorldServer/NPC.cpp

@@ -103,6 +103,7 @@ NPC::NPC(NPC* old_npc){
 		SetOmittedByDBFlag(old_npc->IsOmittedByDBFlag());
 		SetLootTier(old_npc->GetLootTier());
 		SetLootDropType(old_npc->GetLootDropType());
+		has_spells = old_npc->HasSpells();
 	}
 }
 
@@ -150,6 +151,8 @@ void NPC::Initialize(){
 	m_ShardCharID = 0;
 	m_ShardCreatedTimestamp = 0;
 	m_call_runback = false;
+	has_spells = false;
+	cast_on_aggro_completed = false;
 }
 
 EQ2Packet* NPC::serialize(Player* player, int16 version){
@@ -169,10 +172,44 @@ void NPC::SetSkills(map<string, Skill*>* in_skills){
 	skills = in_skills;
 }
 
-void NPC::SetSpells(vector<Spell*>* in_spells){
+void NPC::SetSpells(vector<NPCSpell*>* in_spells){
+	for(int i=0;i<CAST_TYPE::MAX_CAST_TYPES;i++) {
+		cast_on_spells[i].clear();
+	}
+	
+	if(spells) {
+		vector<NPCSpell*>::iterator itr;
+		for(itr = spells->begin(); itr != spells->end(); itr++){
+			safe_delete((*itr));
+		}
+	}
 	safe_delete(spells);
 
 	spells = in_spells;
+	
+	if(spells && spells->size() > 0) {
+		has_spells = true;
+		
+		vector<NPCSpell*>::iterator itr;
+		for(itr = spells->begin(); itr != spells->end();){
+			if((*itr)->cast_on_spawn) {
+				cast_on_spells[CAST_TYPE::CAST_ON_SPAWN].push_back((*itr));
+				itr = spells->erase(itr); // we don't keep on the master list
+				continue;
+			}
+			if((*itr)->cast_on_initial_aggro) {
+				cast_on_spells[CAST_TYPE::CAST_ON_AGGRO].push_back((*itr));
+				itr = spells->erase(itr); // we don't keep on the master list
+				continue;
+			}
+			
+			// didn't hit a continue case, iterate
+			itr++;
+		}
+	}
+	else {
+		has_spells = false;
+	}
 }
 
 void NPC::SetRunbackLocation(float x, float y, float z, int32 gridid, bool set_hp_runback){
@@ -781,7 +818,44 @@ int32 NPC::GetEquipmentListID(){
 	return equipment_list_id;
 }
 
-Spell* NPC::GetNextSpell(float distance){
+Spell* NPC::GetNextSpell(Spawn* target, float distance){
+	if(!cast_on_aggro_completed) {
+		Spell* ret = nullptr;
+		Spell* tmpSpell = nullptr;
+		vector<NPCSpell*>::iterator itr;
+		Spawn* tmpTarget = target;
+		for (itr = cast_on_spells[CAST_ON_AGGRO].begin(); itr != cast_on_spells[CAST_ON_AGGRO].end(); itr++) {
+			tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier);
+			
+			if(!tmpSpell)
+				continue;
+			if (tmpSpell->GetSpellData()->friendly_spell > 0) {
+				tmpTarget = (Spawn*)this;
+			}
+			if (tmpSpell->GetSpellData()) {
+				SpellEffects* effect = ((Entity*)tmpTarget)->GetSpellEffect(tmpSpell->GetSpellID());
+				if (!effect) {
+					ret = tmpSpell;
+							
+					if (tmpSpell->GetSpellData()->friendly_spell > 0) {
+						tmpTarget = target;
+					}
+					break;
+				}
+			}
+			if (tmpSpell->GetSpellData()->friendly_spell > 0) {
+				tmpTarget = target;
+			}
+		}
+		
+		if(ret) {
+			return ret;
+		}
+		else {
+			cast_on_aggro_completed = true;
+		}
+	}
+	
 	int8 val = rand()%100;
 	if(ai_strategy == AI_STRATEGY_OFFENSIVE){
 		if(val >= 20)//80% chance to cast offensive spell if Offensive AI
@@ -802,9 +876,9 @@ Spell* NPC::GetNextSpell(float distance, int8 type){
 		if(distance < 0)
 			distance = 0;
 		Spell* tmpSpell = 0;
-		vector<Spell*>::iterator itr;
+		vector<NPCSpell*>::iterator itr;
 		for(itr = spells->begin(); itr != spells->end(); itr++){
-			tmpSpell = *itr;
+			tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier);
 			if(!tmpSpell || (type == AI_STRATEGY_OFFENSIVE && tmpSpell->GetSpellData()->friendly_spell > 0))
 				continue;
 			if (tmpSpell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE)
@@ -823,15 +897,41 @@ Spell* NPC::GetNextSpell(float distance, int8 type){
 	return ret;
 }
 
-Spell* NPC::GetNextBuffSpell() {
+Spell* NPC::GetNextBuffSpell(Spawn* target) {
+	if(!target) {
+		target = (Spawn*)this;
+	}
+	
 	Spell* ret = 0;
+	
+	if(!target->IsEntity()) {
+		return ret;
+	}
+	
 	if (spells && GetZone()->GetSpellProcess()) {
 		Spell* tmpSpell = 0;
-		vector<Spell*>::iterator itr;
+		vector<NPCSpell*>::iterator itr;
+		for (itr = cast_on_spells[CAST_ON_SPAWN].begin(); itr != cast_on_spells[CAST_ON_SPAWN].end(); itr++) {
+			tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier);
+			if (tmpSpell && tmpSpell->GetSpellData()) {
+				SpellEffects* effect = ((Entity*)target)->GetSpellEffect(tmpSpell->GetSpellID());
+				if (effect) {
+					if (effect->tier < tmpSpell->GetSpellTier()) {
+						ret = tmpSpell;
+						break;
+					}
+				}
+				else {
+					ret = tmpSpell;
+					break;
+				}
+			}
+		}
+		
 		for (itr = spells->begin(); itr != spells->end(); itr++) {
-			tmpSpell = *itr;
+			tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier);
 			if (tmpSpell && tmpSpell->GetSpellData() && tmpSpell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) {
-				SpellEffects* effect = GetSpellEffect(tmpSpell->GetSpellID());
+				SpellEffects* effect = ((Entity*)target)->GetSpellEffect(tmpSpell->GetSpellID());
 				if (effect) {
 					if (effect->tier < tmpSpell->GetSpellTier()) {
 						ret = tmpSpell;
@@ -944,6 +1044,6 @@ void NPC::SetZone(ZoneServer* in_zone, int32 version) {
 	if (in_zone){
 		GetZone()->SetNPCEquipment(this);
 		SetSkills(GetZone()->GetNPCSkills(primary_skill_list, secondary_skill_list));
-		SetSpells(GetZone()->GetNPCSpells(primary_spell_list, secondary_spell_list));
+		SetSpells(world.GetNPCSpells(primary_spell_list, secondary_spell_list));
 	}
 }

+ 35 - 4
EQ2/source/WorldServer/NPC.h

@@ -69,8 +69,34 @@
 #define PET_TYPE_COSMETIC	4
 #define PET_TYPE_DUMBFIRE	5
 
+enum CAST_TYPE {
+	CAST_ON_SPAWN=0,
+	CAST_ON_AGGRO=1,
+	MAX_CAST_TYPES=2
+};
 class Brain;
 
+class NPCSpell {
+public:
+	NPCSpell() {
+		
+	}
+	
+	NPCSpell(NPCSpell* inherit) {
+			list_id = inherit->list_id;
+			spell_id = inherit->spell_id;
+			tier = inherit->tier;
+			cast_on_spawn = inherit->cast_on_spawn;
+			cast_on_initial_aggro = inherit->cast_on_initial_aggro;
+	}
+	
+	int32 	list_id;
+	int32 	spell_id;
+	int8 	tier;
+	bool	cast_on_spawn;
+	bool	cast_on_initial_aggro;
+};
+
 class NPC : public Entity {
 public:
 	NPC();
@@ -104,15 +130,15 @@ public:
 	int32	GetSecondarySkillList();
 	void	SetEquipmentListID(int32 id);
 	int32	GetEquipmentListID();
-	Spell*	GetNextSpell(float distance);
-	virtual Spell*	GetNextBuffSpell();
+	Spell*	GetNextSpell(Spawn* target, float distance);
+	virtual Spell*	GetNextBuffSpell(Spawn* target = 0);
 	void	SetAggroRadius(float radius, bool overrideBaseValue = false);
 	float	GetAggroRadius();
 	float	GetBaseAggroRadius() { return base_aggro_radius; }
 	void	SetCastPercentage(int8 percentage);
 	int8	GetCastPercentage();
 	void	SetSkills(map<string, Skill*>* in_skills);
-	void	SetSpells(vector<Spell*>* in_spells);
+	void	SetSpells(vector<NPCSpell*>* in_spells);
 	void	SetRunbackLocation(float x, float y, float z, int32 gridid, bool set_hp_runback = false);
 	MovementLocation* GetRunbackLocation();
 	float	GetRunbackDistance();
@@ -150,7 +176,10 @@ public:
 	sint64 GetShardCreatedTimestamp() { return m_ShardCreatedTimestamp; }
 	void SetShardCreatedTimestamp(sint64 timestamp) { m_ShardCreatedTimestamp = timestamp; }
 	
+	bool HasSpells() { return has_spells; }
+	
 	std::atomic<bool> m_call_runback;
+	std::atomic<bool> cast_on_aggro_completed;
 private:
 	MovementLocation* runback;
 	int8	cast_percentage;
@@ -158,7 +187,8 @@ private:
 	float	base_aggro_radius;
 	Spell*	GetNextSpell(float distance, int8 type);
 	map<string, Skill*>* skills;
-	vector<Spell*>* spells;
+	vector<NPCSpell*>* spells;
+	vector<NPCSpell*> cast_on_spells[CAST_TYPE::MAX_CAST_TYPES];
 	int32	primary_spell_list;
 	int32	secondary_spell_list;
 	int32	primary_skill_list;
@@ -179,6 +209,7 @@ private:
 	int32		m_ShardID;
 	int32		m_ShardCharID;
 	sint64		m_ShardCreatedTimestamp;
+	bool		has_spells;
 };
 #endif
 

+ 40 - 9
EQ2/source/WorldServer/NPC_AI.cpp

@@ -79,6 +79,7 @@ void Brain::Think() {
 			if (!m_body->EngagedInCombat()) {
 				m_body->ClearRunningLocations();
 				m_body->InCombat(true);
+				m_body->cast_on_aggro_completed = false;
 				m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AGGRO, target);
 			}
 
@@ -350,7 +351,7 @@ void Brain::MoveCloser(Spawn* target) {
 bool Brain::ProcessSpell(Entity* target, float distance) {
 	if(rand()%100 > m_body->GetCastPercentage() || m_body->IsStifled() || m_body->IsFeared())
 		return false;
-	Spell* spell = m_body->GetNextSpell(distance);
+	Spell* spell = m_body->GetNextSpell(target, distance);
 	if(spell){
 		Spawn* spell_target = 0;
 		if(spell->GetSpellData()->friendly_spell == 1){
@@ -369,7 +370,19 @@ bool Brain::ProcessSpell(Entity* target, float distance) {
 		}
 		else
 			spell_target = target;
-		m_body->GetZone()->ProcessSpell(spell, m_body, spell_target);
+		
+		BrainCastSpell(spell, spell_target, false);
+		return true;
+	}
+	return false;
+}
+
+bool Brain::BrainCastSpell(Spell* spell, Spawn* cast_on, bool calculate_run_loc) {
+	if (spell) {
+		if(calculate_run_loc) {
+			m_body->CalculateRunningLocation(true);
+		}
+		m_body->GetZone()->ProcessSpell(spell, m_body, cast_on);
 		m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
 		return true;
 	}
@@ -380,14 +393,32 @@ bool Brain::CheckBuffs() {
 	if (!m_body->GetZone()->GetSpellProcess() || m_body->EngagedInCombat() || m_body->IsCasting() || m_body->IsMezzedOrStunned() || !m_body->Alive() || m_body->IsStifled() || !HasRecovered())
 		return false;
 
-	Spell* spell = m_body->GetNextBuffSpell();
-	if (spell) {
-		m_body->CalculateRunningLocation(true);
-		m_body->GetZone()->ProcessSpell(spell, m_body, m_body);
-		m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
-		return true;
+	Spell* spell = m_body->GetNextBuffSpell(m_body);
+	
+	bool casted_on = false;
+	if(!(casted_on = BrainCastSpell(spell, m_body)) && m_body->IsNPC() && ((NPC*)m_body)->HasSpells()) {
+		Spawn* target = nullptr;
+	
+		vector<Spawn*>* group = m_body->GetSpawnGroup();
+		if(group && group->size() > 0){
+			vector<Spawn*>::iterator itr;
+			for(itr = group->begin(); itr != group->end(); itr++){
+				Spawn* spawn = (*itr);
+				if(spawn->IsEntity() && spawn != m_body) {
+					if(target) {
+						Spell* spell = m_body->GetNextBuffSpell(spawn);
+						SpellEffects* se = ((Entity*)spawn)->GetSpellEffect(spell->GetSpellData()->id);
+						if(!se && BrainCastSpell(spell, spawn)) {
+							casted_on = true;
+							break;
+						}
+					}
+				}
+			}
+		}
+		safe_delete(group);
 	}
-	return false;
+	return casted_on;
 }
 
 void Brain::ProcessMelee(Entity* target, float distance) {

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

@@ -76,6 +76,8 @@ public:
 
 	/* Combat related functions */
 	
+	bool BrainCastSpell(Spell* spell, Spawn* cast_on, bool calculate_run_loc = true);
+	
 	/// <summary></summary>
 	/// <param name=""></param>
 	/// <param name=""></param>

+ 3 - 0
EQ2/source/WorldServer/SpellProcess.cpp

@@ -1304,6 +1304,9 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 				else if (target->IsBot() && (caster->IsPlayer() || caster->IsBot())) {
 					// Needed so bots or player can cast friendly spells on bots
 				}
+				else if (caster->IsNPC()) {
+					// npcs can cast on other npcs
+				}
 				else
 				{
 					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND);

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

@@ -139,6 +139,8 @@ World::~World(){
 	map<std::string, MapRange*>::iterator itr4;
 	for (itr4 = maps.begin(); itr4 != maps.end(); itr4++)
 		safe_delete(itr4->second);
+	
+	PurgeNPCSpells();
 }
 
 void World::init(){
@@ -2765,3 +2767,71 @@ void World::CopyVoiceOver(VoiceOverStruct* struct1, VoiceOverStruct* struct2) {
 	struct1->is_garbled = struct2->is_garbled;
 	struct1->garble_link_id = struct2->garble_link_id;
 }
+
+void World::AddNPCSpell(int32 list_id, int32 spell_id, int8 tier, bool spawn_cast, bool aggro_cast){
+    std::unique_lock lock(MNPCSpells);
+	NPCSpell* npc_spell_struct = new NPCSpell;
+	npc_spell_struct->list_id = list_id;
+	npc_spell_struct->spell_id = spell_id;
+	npc_spell_struct->tier = tier;
+	npc_spell_struct->cast_on_spawn = spawn_cast;
+	npc_spell_struct->cast_on_initial_aggro = aggro_cast;
+	if(npc_spell_list.count(list_id) && npc_spell_list[list_id].count(spell_id)) {
+		map<int32, NPCSpell*>::iterator spell_itr = npc_spell_list[list_id].find(spell_id);
+		if(spell_itr != npc_spell_list[list_id].end()) {
+			safe_delete(spell_itr->second);
+			npc_spell_list[list_id].erase(spell_itr);
+		}
+	}
+	
+	npc_spell_list[list_id].insert(make_pair(spell_id, npc_spell_struct));
+}
+
+vector<NPCSpell*>* World::GetNPCSpells(int32 primary_list, int32 secondary_list){
+    std::shared_lock lock(MNPCSpells);
+	vector<NPCSpell*>* ret = 0;
+	if(npc_spell_list.count(primary_list) > 0){
+		ret = new vector<NPCSpell*>();
+		map<int32, NPCSpell*>::iterator itr;
+		Spell* tmpSpell = 0;
+		for(itr = npc_spell_list[primary_list].begin(); itr != npc_spell_list[primary_list].end(); itr++){
+			tmpSpell = master_spell_list.GetSpell(itr->first, itr->second->tier);
+			if(tmpSpell) {
+				NPCSpell* addedSpell = new NPCSpell(itr->second);
+				ret->push_back(addedSpell);
+			}
+		}
+	}
+	if(npc_spell_list.count(secondary_list) > 0){
+		if(!ret)
+			ret = new vector<NPCSpell*>();
+		map<int32, NPCSpell*>::iterator itr;
+		Spell* tmpSpell = 0;
+		for(itr = npc_spell_list[secondary_list].begin(); itr != npc_spell_list[secondary_list].end(); itr++){
+			tmpSpell = master_spell_list.GetSpell(itr->first, itr->second->tier);
+			if(tmpSpell) {
+				NPCSpell* addedSpell = new NPCSpell(itr->second);
+				ret->push_back(addedSpell);
+			}
+		}
+	}
+	if(ret && ret->size() == 0){
+		safe_delete(ret);
+		ret = 0;
+	}
+	return ret;
+}
+
+void World::PurgeNPCSpells() {
+    std::unique_lock lock(MNPCSpells);
+	map<int32, map<int32, NPCSpell*> >::iterator list_itr;
+	map<int32, NPCSpell*>::iterator spell_itr;
+	Spell* tmpSpell = 0;
+	for(list_itr = npc_spell_list.begin(); list_itr != npc_spell_list.end(); list_itr++) {
+		for(spell_itr = npc_spell_list[list_itr->first].begin(); spell_itr != npc_spell_list[list_itr->first].end(); spell_itr++){
+			safe_delete(spell_itr->second);
+		}
+	}
+	
+	npc_spell_list.clear();
+}

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

@@ -24,6 +24,8 @@
 #include <vector>
 #include <map>
 #include <list>
+#include <mutex>
+#include <shared_mutex>
 #include "SpawnLists.h"
 #include "zoneserver.h"
 #include "NPC.h"
@@ -659,6 +661,13 @@ public:
 	bool FindVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_ = nullptr, bool* find_garbled = nullptr, VoiceOverStruct* garble_struct_ = nullptr);
 	void AddVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_);
 	void CopyVoiceOver(VoiceOverStruct* struct1, VoiceOverStruct* struct2);
+	
+	/* NPC Spells */
+	void AddNPCSpell(int32 list_id, int32 spell_id, int8 tier, bool spawn_cast, bool aggro_cast);
+	vector<NPCSpell*>* GetNPCSpells(int32 primary_list, int32 secondary_list);
+	
+	void PurgeNPCSpells();
+	
 	Mutex MVoiceOvers;
 	
 	static sint64 newValue;
@@ -674,6 +683,8 @@ private:
 	Mutex MZoneScripts;
 	//Mutex MGroups;
 	
+	mutable std::shared_mutex MNPCSpells;
+	
 	map<int32, MerchantInfo*> merchant_info;
 	map<int32, vector<MerchantItemInfo> > merchant_inventory_items;
 	int32 vitality_frequency;
@@ -726,5 +737,7 @@ private:
 	std::map<std::string, MapRange*> maps;
 	Mutex				MWorldMaps;
 	Mutex				MWorldRegionMaps;
+	
+	map<int32, map<int32, NPCSpell*> > npc_spell_list;
 };
 #endif

+ 3 - 4
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -500,13 +500,13 @@ void WorldDatabase::LoadCommandList()
 	LoadSubCommandList();
 }
 
-int32 WorldDatabase::LoadNPCSpells(ZoneServer* zone){
+int32 WorldDatabase::LoadNPCSpells(){
 	Query query;
 	MYSQL_ROW row;
 	int32 count = 0;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spell_list_id, spell_id, spell_tier FROM spawn_npc_spells where spell_list_id > 0");
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spell_list_id, spell_id, spell_tier, on_spawn_cast, on_aggro_cast FROM spawn_npc_spells where spell_list_id > 0");
 	while(result && (row = mysql_fetch_row(result))){
-		zone->AddNPCSpell(atoul(row[0]), atoul(row[1]), atoi(row[2]));
+		world.AddNPCSpell(atoul(row[0]), atoul(row[1]), atoi(row[2]), atoul(row[3]), atoul(row[4]));
 		count++;
 
 		LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC Spell List: %u, spell id: %u, tier: %i", atoul(row[0]), atoul(row[1]), atoi(row[2]));
@@ -1092,7 +1092,6 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 		LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC: '%s' (%u)", npc->appearance.name, id);
 	}
 	LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC(s).", total);
-	LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Spell(s).", LoadNPCSpells(zone));
 	LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Skill(s).", LoadNPCSkills(zone));
 	LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Equipment Piece(s).", LoadNPCEquipment(zone));
 	LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Appearance(s).", LoadAppearances(zone));	

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

@@ -254,7 +254,7 @@ public:
 	void	LoadNPCs(ZoneServer* zone);
 	void	LoadSpiritShards(ZoneServer* zone);
 	int32	LoadAppearances(ZoneServer* zone, Client* client = 0);
-	int32	LoadNPCSpells(ZoneServer* zone);
+	int32	LoadNPCSpells();
 	int32	LoadNPCSkills(ZoneServer* zone);
 	int32	LoadNPCEquipment(ZoneServer* zone);
 	void	LoadObjects(ZoneServer* zone);

+ 3 - 0
EQ2/source/WorldServer/net.cpp

@@ -324,6 +324,9 @@ int main(int argc, char** argv) {
 	LogWrite(WORLD__INFO, 0, "World", "Loading Chest Trap Data...");
 	database.LoadChestTraps();
 
+	LogWrite(WORLD__INFO, 0, "World", "Loading NPC Spells...");
+	database.LoadNPCSpells();
+	
 	if (threadedLoad) {
 		LogWrite(WORLD__INFO, 0, "World", "Waiting for load threads to finish.");
 		while (!world.items_loaded || !world.spells_loaded /*|| !world.achievments_loaded*/)

+ 1 - 35
EQ2/source/WorldServer/zoneserver.cpp

@@ -437,7 +437,7 @@ void ZoneServer::LoadSpellProcess(){
 	for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
 		spawn = itr->second;
 		if(spawn && spawn->IsNPC())
-			((NPC*)spawn)->SetSpells(GetNPCSpells(((NPC*)spawn)->GetPrimarySpellList(), ((NPC*)spawn)->GetSecondarySpellList()));
+			((NPC*)spawn)->SetSpells(world.GetNPCSpells(((NPC*)spawn)->GetPrimarySpellList(), ((NPC*)spawn)->GetSecondarySpellList()));
 	}
 	MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
 }
@@ -7363,40 +7363,6 @@ void ZoneServer::ClearEntityCommands() {
 	}
 }
 
-void ZoneServer::AddNPCSpell(int32 list_id, int32 spell_id, int8 tier){
-	npc_spell_list[list_id][spell_id] = tier;
-}
-
-vector<Spell*>* ZoneServer::GetNPCSpells(int32 primary_list, int32 secondary_list){
-	vector<Spell*>* ret = 0;
-	if(npc_spell_list.count(primary_list) > 0){
-		ret = new vector<Spell*>();
-		map<int32, int8>::iterator itr;
-		Spell* tmpSpell = 0;
-		for(itr = npc_spell_list[primary_list].begin(); itr != npc_spell_list[primary_list].end(); itr++){
-			tmpSpell = master_spell_list.GetSpell(itr->first, itr->second);
-			if(tmpSpell)
-				ret->push_back(tmpSpell);
-		}
-	}
-	if(npc_spell_list.count(secondary_list) > 0){
-		if(!ret)
-			ret = new vector<Spell*>();
-		map<int32, int8>::iterator itr;
-		Spell* tmpSpell = 0;
-		for(itr = npc_spell_list[secondary_list].begin(); itr != npc_spell_list[secondary_list].end(); itr++){
-			tmpSpell = master_spell_list.GetSpell(itr->first, itr->second);
-			if(tmpSpell)
-				ret->push_back(tmpSpell);
-		}
-	}
-	if(ret && ret->size() == 0){
-		safe_delete(ret);
-		ret = 0;
-	}
-	return ret;
-}
-
 void ZoneServer::AddNPCSkill(int32 list_id, int32 skill_id, int16 value){
 	npc_skill_list[list_id][skill_id] = value;
 }

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

@@ -960,7 +960,6 @@ private:
 
 	bool reloading;
 	map<int32, vector<EntityCommand*>* > entity_command_list;
-	map<int32, map<int32, int8> > npc_spell_list;
 	map<int32, map<int32, int16> > npc_skill_list;
 	map<int32, vector<int32> > npc_equipment_list;
 	map<int32, NPC*> npc_list;
@@ -1017,10 +1016,6 @@ public:
 			return 0;
 	}
 
-	/* NPC Spells */
-	void AddNPCSpell(int32 list_id, int32 spell_id, int8 tier);
-	vector<Spell*>* GetNPCSpells(int32 primary_list, int32 secondary_list);
-
 	/* NPC Skills */
 	void AddNPCSkill(int32 list_id, int32 skill_id, int16 value);
 	map<string, Skill*>* GetNPCSkills(int32 primary_list, int32 secondary_list);