From e44861a6295f487b6de36151b1ee0265a2bc0969 Mon Sep 17 00:00:00 2001 From: Hondacrx Date: Mon, 19 May 2025 17:23:36 -0400 Subject: [PATCH] Core/AI: Implemented conversation ai Port From (https://github.com/TrinityCore/TrinityCore/commit/309ba22a15e5e0b4321b99f7157ccb18e0adc8dd) --- Source/Game/AI/AISelector.cs | 9 ++++ Source/Game/AI/CoreAI/ConversationAI.cs | 55 +++++++++++++++++++++ Source/Game/Entities/Conversation.cs | 29 +++++++++-- Source/Game/Entities/Player/Player.DB.cs | 36 +++++--------- Source/Game/Entities/Player/Player.Quest.cs | 37 ++++---------- Source/Game/Handlers/MiscHandler.cs | 6 +-- Source/Game/Scripting/CoreScripts.cs | 26 +++++----- Source/Game/Scripting/ScriptManager.cs | 42 +++++----------- Source/Scripts/BrokenIsles/ZoneMardum.cs | 36 +++++++------- Source/Scripts/World/Conversation.cs | 10 ++-- 10 files changed, 160 insertions(+), 126 deletions(-) create mode 100644 Source/Game/AI/CoreAI/ConversationAI.cs diff --git a/Source/Game/AI/AISelector.cs b/Source/Game/AI/AISelector.cs index d9d2d2c6a..747d51175 100644 --- a/Source/Game/AI/AISelector.cs +++ b/Source/Game/AI/AISelector.cs @@ -110,5 +110,14 @@ namespace Game.AI _ => new GameObjectAI(go), }; } + + public static ConversationAI SelectConversationAI(Conversation conversation) + { + ConversationAI ai = Global.ScriptMgr.GetConversationAI(conversation); + if ( ai != null) + return ai; + + return new NullConversationAI(conversation, Global.ObjectMgr.GetScriptId("NullConversationAI", false)); + } } } diff --git a/Source/Game/AI/CoreAI/ConversationAI.cs b/Source/Game/AI/CoreAI/ConversationAI.cs new file mode 100644 index 000000000..952e02c58 --- /dev/null +++ b/Source/Game/AI/CoreAI/ConversationAI.cs @@ -0,0 +1,55 @@ +// Copyright (c) CypherCore All rights reserved. +// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. + +using Game.Entities; +using Game.Spells; + +namespace Game.AI +{ + public class ConversationAI + { + uint _scriptId; + public Conversation conversation; + + public ConversationAI(Conversation c, uint scriptId = 0) + { + _scriptId = scriptId != 0 ? scriptId : c.GetScriptId(); + conversation = c; + + Cypher.Assert(_scriptId != 0, "A ConversationAI was initialized with an invalid scriptId!"); + } + + // Called when the Conversation has just been initialized, just before added to map + public virtual void OnInitialize() { } + + // Called when Conversation is created but not added to Map yet. + public virtual void OnCreate(Unit creator) { } + + // Called when Conversation is started + public virtual void OnStart() { } + + // Called when player sends CMSG_CONVERSATION_LINE_STARTED with valid conversation guid + public virtual void OnLineStarted(uint lineId, Player sender) { } + + // Called for each update tick + public virtual void OnUpdate(uint diff) { } + + // Called when the Conversation is removed + public virtual void OnRemove() { } + + // Pass parameters between AI + public virtual void DoAction(int param) { } + public virtual uint GetData(uint id = 0) { return 0; } + public virtual void SetData(uint id, uint value) { } + public virtual void SetGUID(ObjectGuid guid, int id = 0) { } + public virtual ObjectGuid GetGUID(int id = 0) { return ObjectGuid.Empty; } + + // Gets the id of the AI (script id) + public uint GetId() { return _scriptId; } + } + + class NullConversationAI : ConversationAI + { + public NullConversationAI(Conversation c, uint scriptId) : base(c, scriptId) { } + } +} diff --git a/Source/Game/Entities/Conversation.cs b/Source/Game/Entities/Conversation.cs index 700117fb3..4285cd3a5 100644 --- a/Source/Game/Entities/Conversation.cs +++ b/Source/Game/Entities/Conversation.cs @@ -2,6 +2,7 @@ // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. using Framework.Constants; +using Game.AI; using Game.DataStorage; using Game.Maps; using Game.Networking; @@ -43,6 +44,8 @@ namespace Game.Entities //- Remove the Conversation from the accessor and from all lists of objects in world if (IsInWorld) { + _ai.OnRemove(); + base.RemoveFromWorld(); GetMap().GetObjectsStore().Remove(GetGUID()); } @@ -50,7 +53,7 @@ namespace Game.Entities public override void Update(uint diff) { - Global.ScriptMgr.OnConversationUpdate(this, diff); + _ai.OnUpdate(diff); if (GetDuration() > TimeSpan.FromMilliseconds(diff)) { @@ -116,13 +119,13 @@ namespace Game.Entities SetEntry(conversationEntry); SetObjectScale(1.0f); + AI_Initialize(); + _textureKitId = conversationTemplate.TextureKitId; foreach (var actor in conversationTemplate.Actors) new ConversationActorFillVisitor(this, creator, map, actor).Invoke(actor); - Global.ScriptMgr.OnConversationCreate(this, creator); - List lines = new(); foreach (ConversationLineTemplate line in conversationTemplate.Lines) { @@ -165,7 +168,7 @@ namespace Game.Entities // conversations are despawned 5-20s after LastLineEndTime _duration += TimeSpan.FromSeconds(10); - Global.ScriptMgr.OnConversationCreate(this, creator); + _ai.OnCreate(creator); } public bool Start() @@ -193,7 +196,7 @@ namespace Game.Entities if (!GetMap().AddToMap(this)) return false; - Global.ScriptMgr.OnConversationStart(this); + _ai.OnStart(); return true; } @@ -284,6 +287,20 @@ namespace Game.Entities return actor.ToCreature(); } + void AI_Initialize() + { + AI_Destroy(); + _ai = AISelector.SelectConversationAI(this); + _ai.OnInitialize(); + } + + void AI_Destroy() + { + _ai = null; + } + + public ConversationAI GetAI() { return _ai; } + public uint GetScriptId() { return Global.ConversationDataStorage.GetConversationTemplate(GetEntry()).ScriptId; @@ -363,6 +380,8 @@ namespace Game.Entities Dictionary<(Locale locale, uint lineId), TimeSpan> _lineStartTimes = new(); TimeSpan[] _lastLineEndTimes = new TimeSpan[(int)Locale.Total]; + ConversationAI _ai; + class ValuesUpdateForPlayerWithMaskSender : IDoWork { Conversation Owner; diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index d91bb9ab2..f6b3092a8 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -632,7 +632,8 @@ namespace Game.Entities stmt.AddValue(5, homebind.GetPositionZ()); stmt.AddValue(6, homebind.GetOrientation()); DB.Characters.Execute(stmt); - }; + } + ; if (!ok && HasAtLoginFlag(AtLoginFlags.FirstLogin)) { @@ -869,11 +870,7 @@ namespace Game.Entities // Skip loading special quests - they are also added to rewarded quests but only once and remain there forever // instead add them separately from load daily/weekly/monthly/seasonal if (!quest.IsDailyOrWeekly() && !quest.IsMonthly() && !quest.IsSeasonal()) - { - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); - if (questBit != 0) - SetQuestCompletedBit(questBit, true); - } + SetQuestCompletedBit(quest_id, true); for (uint i = 0; i < quest.GetRewChoiceItemsCount(); ++i) GetSession().GetCollectionMgr().AddItemAppearance(quest.RewardChoiceItemId[i]); @@ -929,9 +926,7 @@ namespace Game.Entities continue; AddDynamicUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.DailyQuestsCompleted), quest_id); - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); - if (questBit != 0) - SetQuestCompletedBit(questBit, true); + SetQuestCompletedBit(quest_id, true); Log.outDebug(LogFilter.Player, "Daily quest ({0}) cooldown for player (GUID: {1})", quest_id, GetGUID().ToString()); } @@ -954,9 +949,7 @@ namespace Game.Entities continue; m_weeklyquests.Add(quest_id); - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); - if (questBit != 0) - SetQuestCompletedBit(questBit, true); + SetQuestCompletedBit(quest_id, true); Log.outDebug(LogFilter.Player, "Weekly quest {0} cooldown for player (GUID: {1})", quest_id, GetGUID().ToString()); } @@ -984,10 +977,7 @@ namespace Game.Entities m_seasonalquests[event_id] = new(); m_seasonalquests[event_id][quest_id] = completedTime; - - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); - if (questBit != 0) - SetQuestCompletedBit(questBit, true); + SetQuestCompletedBit(quest_id, true); Log.outDebug(LogFilter.Player, "Seasonal quest {0} cooldown for player (GUID: {1})", quest_id, GetGUID().ToString()); } @@ -996,12 +986,8 @@ namespace Game.Entities m_SeasonalQuestChanged = false; } - void _LoadMonthlyQuestStatus() + void _LoadMonthlyQuestStatus(SQLResult result) { - PreparedStatement stmt = CharacterDatabase.GetPreparedStatement(CharStatements.SEL_CHARACTER_QUESTSTATUS_MONTHLY); - stmt.AddValue(0, GetGUID().GetCounter()); - SQLResult result = DB.Characters.Query(stmt); - m_monthlyquests.Clear(); if (!result.IsEmpty()) @@ -1014,9 +1000,7 @@ namespace Game.Entities continue; m_monthlyquests.Add(quest_id); - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(quest_id); - if (questBit != 0) - SetQuestCompletedBit(questBit, true); + SetQuestCompletedBit(quest_id, true); Log.outDebug(LogFilter.Player, "Monthly quest {0} cooldown for player (GUID: {1})", quest_id, GetGUID().ToString()); } @@ -3447,6 +3431,7 @@ namespace Game.Entities _LoadDailyQuestStatus(holder.GetResult(PlayerLoginQueryLoad.DailyQuestStatus)); _LoadWeeklyQuestStatus(holder.GetResult(PlayerLoginQueryLoad.WeeklyQuestStatus)); _LoadSeasonalQuestStatus(holder.GetResult(PlayerLoginQueryLoad.SeasonalQuestStatus)); + _LoadMonthlyQuestStatus(holder.GetResult(PlayerLoginQueryLoad.MonthlyQuestStatus)); _LoadRandomBGStatus(holder.GetResult(PlayerLoginQueryLoad.RandomBg)); // after spell and quest load @@ -3705,7 +3690,8 @@ namespace Game.Entities stmt.AddValue(0, GetGUID().GetCounter()); characterTransaction.Append(stmt); - static float finiteAlways(float f) { return float.IsFinite(f) ? f : 0.0f; }; + static float finiteAlways(float f) { return float.IsFinite(f) ? f : 0.0f; } + ; if (create) { diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index 2c024f6ac..118d0661b 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -156,11 +156,7 @@ namespace Game.Entities public void DailyReset() { foreach (uint questId in m_activePlayerData.DailyQuestsCompleted) - { - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); - if (questBit != 0) - SetQuestCompletedBit(questBit, false); - } + SetQuestCompletedBit(questId, false); DailyQuestsReset dailyQuestsReset = new(); dailyQuestsReset.Count = m_activePlayerData.DailyQuestsCompleted.Size(); @@ -207,11 +203,7 @@ namespace Game.Entities return; foreach (uint questId in m_weeklyquests) - { - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); - if (questBit != 0) - SetQuestCompletedBit(questBit, false); - } + SetQuestCompletedBit(questId, false); for (ushort slot = 0; slot < SharedConst.MaxQuestLogSize; ++slot) { @@ -253,9 +245,7 @@ namespace Game.Entities { if (completedTime < eventStartTime) { - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); - if (questBit != 0) - SetQuestCompletedBit(questBit, false); + SetQuestCompletedBit(questId, false); eventList.Remove(questId); } @@ -271,11 +261,7 @@ namespace Game.Entities return; foreach (uint questId in m_monthlyquests) - { - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); - if (questBit != 0) - SetQuestCompletedBit(questBit, false); - } + SetQuestCompletedBit(questId, false); m_monthlyquests.Clear(); // DB data deleted in caller @@ -1203,9 +1189,7 @@ namespace Game.Entities // make full db save SaveToDB(false); - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); - if (questBit != 0) - SetQuestCompletedBit(questBit, true); + SetQuestCompletedBit(questId, true); if (quest.HasFlag(QuestFlags.Pvp)) { @@ -1256,9 +1240,7 @@ namespace Game.Entities m_RewardedQuests.Add(questId); m_RewardedQuestsSave[questId] = QuestSaveType.Default; - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); - if (questBit != 0) - SetQuestCompletedBit(questBit, true); + SetQuestCompletedBit(questId, true); } public void FailQuest(uint questId) @@ -1952,9 +1934,7 @@ namespace Game.Entities m_RewardedQuestsSave[questId] = QuestSaveType.ForceDelete; } - uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); - if (questBit != 0) - SetQuestCompletedBit(questBit, false); + SetQuestCompletedBit(questId, false); // Remove seasonal quest also Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); @@ -2429,8 +2409,9 @@ namespace Game.Entities return (m_activePlayerData.BitVectors.GetValue().Values[(int)PlayerDataFlag.CharacterQuestCompletedIndex].Values[fieldOffset] & flag) != 0; } - void SetQuestCompletedBit(uint questBit, bool completed) + void SetQuestCompletedBit(uint questId, bool completed) { + uint questBit = Global.DB2Mgr.GetQuestUniqueBitFlag(questId); if (questBit == 0) return; diff --git a/Source/Game/Handlers/MiscHandler.cs b/Source/Game/Handlers/MiscHandler.cs index d0df48068..a3b31e8b2 100644 --- a/Source/Game/Handlers/MiscHandler.cs +++ b/Source/Game/Handlers/MiscHandler.cs @@ -446,9 +446,9 @@ namespace Game [WorldPacketHandler(ClientOpcodes.ConversationLineStarted)] void HandleConversationLineStarted(ConversationLineStarted conversationLineStarted) { - Conversation convo = ObjectAccessor.GetConversation(_player, conversationLineStarted.ConversationGUID); - if (convo != null) - Global.ScriptMgr.OnConversationLineStarted(convo, conversationLineStarted.LineID, _player); + Conversation conversation = ObjectAccessor.GetConversation(_player, conversationLineStarted.ConversationGUID); + if (conversation != null) + conversation.GetAI().OnLineStarted(conversationLineStarted.LineID, _player); } [WorldPacketHandler(ClientOpcodes.RequestLatestSplashScreen)] diff --git a/Source/Game/Scripting/CoreScripts.cs b/Source/Game/Scripting/CoreScripts.cs index 8b02410bb..78d01ca3f 100644 --- a/Source/Game/Scripting/CoreScripts.cs +++ b/Source/Game/Scripting/CoreScripts.cs @@ -247,13 +247,24 @@ namespace Game.Scripting public virtual BattlegroundScript GetBattlegroundScript(BattlegroundMap map) { return null; } } + public class GenericConversationScript