diff --git a/Source/Game/Chat/Commands/QuestCommands.cs b/Source/Game/Chat/Commands/QuestCommands.cs index 977320376..24dd586fc 100644 --- a/Source/Game/Chat/Commands/QuestCommands.cs +++ b/Source/Game/Chat/Commands/QuestCommands.cs @@ -107,6 +107,7 @@ namespace Game.Chat player.RemoveActiveQuest(quest.Id, false); player.RemoveRewardedQuest(quest.Id); + player.DespawnPersonalSummonsForQuest(quest.Id); Global.ScriptMgr.OnQuestStatusChange(player, quest.Id); Global.ScriptMgr.OnQuestStatusChange(player, quest, oldStatus, QuestStatus.None); diff --git a/Source/Game/Entities/Creature/CreatureData.cs b/Source/Game/Entities/Creature/CreatureData.cs index b0357ede1..510462025 100644 --- a/Source/Game/Entities/Creature/CreatureData.cs +++ b/Source/Game/Entities/Creature/CreatureData.cs @@ -343,6 +343,7 @@ namespace Game.Entities public uint? CreatureIDVisibleToSummoner; public uint? GroundMountDisplayID; public uint? FlyingMountDisplayID; + public List DespawnOnQuestsRemoved; } public class CreatureAddon diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index 2c5844b4a..9fd1881f2 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -184,6 +184,7 @@ namespace Game.Entities SetQuestSlot(slot, 0); AbandonQuest(questId); RemoveActiveQuest(questId); + DespawnPersonalSummonsForQuest(questId); if (quest.LimitTime != 0) RemoveTimedQuest(questId); @@ -226,6 +227,7 @@ namespace Game.Entities SetQuestSlot(slot, 0); AbandonQuest(questId); RemoveActiveQuest(questId); + DespawnPersonalSummonsForQuest(questId); if (quest.LimitTime != 0) RemoveTimedQuest(questId); @@ -2190,6 +2192,23 @@ namespace Game.Entities UpdateObjectVisibility(); } + public void DespawnPersonalSummonsForQuest(uint questId) + { + List creatureList = GetCreatureListWithOptionsInGrid(100.0f, new FindCreatureOptions() { IgnorePhases = true, PrivateObjectOwnerGuid = GetGUID() }); // we might want to replace this with SummonList in Player at some point + + foreach (Creature creature in creatureList) + { + CreatureSummonedData summonedData = Global.ObjectMgr.GetCreatureSummonedData(creature.GetEntry()); + if (summonedData == null) + continue; + + if (summonedData.DespawnOnQuestsRemoved != null) + { + if (summonedData.DespawnOnQuestsRemoved.Contains(questId)) + creature.DespawnOrUnsummon(); + } + } + } public ushort GetReqKillOrCastCurrentCount(uint quest_id, int entry) { diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 8997aabc5..ac75cfc38 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -1761,8 +1761,6 @@ namespace Game // We load the creature models after loading but before checking LoadCreatureTemplateModels(); - LoadCreatureSummonedData(); - // Checking needs to be done after loading because of the difficulty self referencing foreach (var template in creatureTemplateStorage.Values) CheckCreatureTemplate(template); @@ -2018,12 +2016,13 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} creature template models in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } - void LoadCreatureSummonedData() + + public void LoadCreatureSummonedData() { uint oldMSTime = Time.GetMSTime(); - // 0 1 2 3 - SQLResult result = DB.World.Query("SELECT CreatureID, CreatureIDVisibleToSummoner, GroundMountDisplayID, FlyingMountDisplayID FROM creature_summoned_data"); + // 0 1 2 3 4 + SQLResult result = DB.World.Query("SELECT CreatureID, CreatureIDVisibleToSummoner, GroundMountDisplayID, FlyingMountDisplayID, DespawnOnQuestsRemoved FROM creature_summoned_data"); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature summoned data definitions. DB table `creature_summoned_data` is empty."); @@ -2074,6 +2073,28 @@ namespace Game } } + if (!result.IsNull(4)) + { + List questList = new(); + foreach (string questStr in result.Read(4).Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + if (!uint.TryParse(questStr, out uint questId)) + continue; + + Quest quest = GetQuestTemplate(questId); + if (quest == null) + { + Log.outError(LogFilter.Sql, $"Table `creature_summoned_data` references non-existing quest {questId} in DespawnOnQuestsRemoved for creature {creatureId}, skipping"); + continue; + } + + questList.Add(questId); + } + + if (!questList.Empty()) + summonedData.DespawnOnQuestsRemoved = questList; + } + } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, $"Loaded {creatureSummonedDataStorage.Count} creature summoned data definitions in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); diff --git a/Source/Game/Handlers/QuestHandler.cs b/Source/Game/Handlers/QuestHandler.cs index 4fd229f1a..0de78b89e 100644 --- a/Source/Game/Handlers/QuestHandler.cs +++ b/Source/Game/Handlers/QuestHandler.cs @@ -437,6 +437,7 @@ namespace Game GetPlayer().TakeQuestSourceItem(questId, true); // remove quest src item from player GetPlayer().AbandonQuest(questId); // remove all quest items player received before abandoning quest. Note, this does not remove normal drop items that happen to be quest requirements. GetPlayer().RemoveActiveQuest(questId); + GetPlayer().DespawnPersonalSummonsForQuest(questId); Log.outInfo(LogFilter.Network, "Player {0} abandoned quest {1}", GetPlayer().GetGUID().ToString(), questId); diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 51dbdf1b2..08cde6bc6 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -3746,6 +3746,7 @@ namespace Game.Spells player.RemoveActiveQuest(quest_id, false); player.RemoveRewardedQuest(quest_id); + player.DespawnPersonalSummonsForQuest(quest_id); Global.ScriptMgr.OnQuestStatusChange(player, quest_id); Global.ScriptMgr.OnQuestStatusChange(player, quest, oldStatus, QuestStatus.None); diff --git a/Source/Game/World/WorldManager.cs b/Source/Game/World/WorldManager.cs index 236d2fed7..ac4d5d346 100644 --- a/Source/Game/World/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -704,6 +704,9 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Game Event Data..."); // must be after loading pools fully Global.GameEventMgr.LoadFromDB(); + Log.outInfo(LogFilter.ServerLoading, "Loading creature summoned data..."); + Global.ObjectMgr.LoadCreatureSummonedData(); // must be after LoadCreatureTemplates() and LoadQuests() + Log.outInfo(LogFilter.ServerLoading, "Loading NPCSpellClick Data..."); // must be after LoadQuests Global.ObjectMgr.LoadNPCSpellClickSpells();