diff --git a/Source/Framework/Constants/SmartAIConst.cs b/Source/Framework/Constants/SmartAIConst.cs index db9ab9be1..30970bd4c 100644 --- a/Source/Framework/Constants/SmartAIConst.cs +++ b/Source/Framework/Constants/SmartAIConst.cs @@ -227,6 +227,7 @@ namespace Framework.Constants OnSpellFailed = 84, // SpellID, CooldownMin, CooldownMax OnSpellStart = 85, // SpellID, CooldownMin, CooldownMax OnDespawn = 86, // NONE + SendEventTrigger = 87, // NONE End } diff --git a/Source/Game/AI/SmartScripts/SmartAIManager.cs b/Source/Game/AI/SmartScripts/SmartAIManager.cs index 16dac528e..dfc59197a 100644 --- a/Source/Game/AI/SmartScripts/SmartAIManager.cs +++ b/Source/Game/AI/SmartScripts/SmartAIManager.cs @@ -97,6 +97,15 @@ namespace Game.AI } break; } + case SmartScriptType.Event: + { + if (!Global.ObjectMgr.IsValidEvent((uint)temp.EntryOrGuid)) + { + Log.outError(LogFilter.Sql, $"SmartAIMgr::LoadSmartAIFromDB: Event id ({temp.EntryOrGuid}) does not exist, skipped loading."); + continue; + } + break; + } case SmartScriptType.Quest: { if (Global.ObjectMgr.GetQuestTemplate((uint)temp.EntryOrGuid) == null) @@ -360,6 +369,7 @@ namespace Game.AI case SmartEvents.SceneTrigger: case SmartEvents.SceneCancel: case SmartEvents.SceneComplete: + case SmartEvents.SendEventTrigger: return true; default: return false; @@ -589,6 +599,7 @@ namespace Game.AI SmartEvents.OnSpellFailed => Marshal.SizeOf(typeof(SmartEvent.SpellCast)), SmartEvents.OnSpellStart => Marshal.SizeOf(typeof(SmartEvent.SpellCast)), SmartEvents.OnDespawn => 0, + SmartEvents.SendEventTrigger => 0, _ => Marshal.SizeOf(typeof(SmartEvent.Raw)), }; @@ -1256,6 +1267,7 @@ namespace Game.AI case SmartEvents.SceneCancel: case SmartEvents.SceneComplete: case SmartEvents.SceneTrigger: + case SmartEvents.SendEventTrigger: break; //Unused @@ -2403,6 +2415,7 @@ namespace Game.AI SmartEvents.OnSpellFailed => SmartScriptTypeMaskId.Creature, SmartEvents.OnSpellStart => SmartScriptTypeMaskId.Creature, SmartEvents.OnDespawn => SmartScriptTypeMaskId.Creature, + SmartEvents.SendEventTrigger => SmartScriptTypeMaskId.Event, _ => 0, }; diff --git a/Source/Game/AI/SmartScripts/SmartScript.cs b/Source/Game/AI/SmartScripts/SmartScript.cs index 9a715cb16..df4c3c063 100644 --- a/Source/Game/AI/SmartScripts/SmartScript.cs +++ b/Source/Game/AI/SmartScripts/SmartScript.cs @@ -39,6 +39,7 @@ namespace Game.AI AreaTrigger _areaTrigger; SceneTemplate _sceneTemplate; Quest _quest; + uint _eventId; SmartScriptType _scriptType; uint _eventPhase; @@ -3203,6 +3204,7 @@ namespace Game.AI case SmartEvents.FollowCompleted: case SmartEvents.OnSpellclick: case SmartEvents.OnDespawn: + case SmartEvents.SendEventTrigger: ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; case SmartEvents.GossipHello: @@ -3876,7 +3878,7 @@ namespace Game.AI e.RunOnce = false; } - void FillScript(List e, WorldObject obj, AreaTriggerRecord at, SceneTemplate scene, Quest quest) + void FillScript(List e, WorldObject obj, AreaTriggerRecord at, SceneTemplate scene, Quest quest, uint eventId = 0) { if (e.Empty()) { @@ -3888,6 +3890,8 @@ namespace Game.AI Log.outDebug(LogFilter.ScriptsAi, $"SmartScript: EventMap for SceneId {scene.SceneId} is empty but is using SmartScript."); if (quest != null) Log.outDebug(LogFilter.ScriptsAi, $"SmartScript: EventMap for Quest {quest.Id} is empty but is using SmartScript."); + if (eventId != 0) + Log.outDebug(LogFilter.ScriptsAi, $"SmartScript: EventMap for Event {eventId} is empty but is using SmartScript."); return; } foreach (var holder in e) @@ -3934,43 +3938,48 @@ namespace Game.AI void GetScript() { List e; - if (_me != null) + // We must use script type to avoid ambiguities + switch (_scriptType) { - e = Global.SmartAIMgr.GetScript(-((int)_me.GetSpawnId()), _scriptType); - if (e.Empty()) - e = Global.SmartAIMgr.GetScript((int)_me.GetEntry(), _scriptType); - FillScript(e, _me, null, null, null); - } - else if (_go != null) - { - e = Global.SmartAIMgr.GetScript(-((int)_go.GetSpawnId()), _scriptType); - if (e.Empty()) - e = Global.SmartAIMgr.GetScript((int)_go.GetEntry(), _scriptType); - FillScript(e, _go, null, null, null); - } - else if (_trigger != null) - { - e = Global.SmartAIMgr.GetScript((int)_trigger.Id, _scriptType); - FillScript(e, null, _trigger, null, null); - } - else if (_areaTrigger != null) - { - e = Global.SmartAIMgr.GetScript((int)_areaTrigger.GetEntry(), _scriptType); - FillScript(e, _areaTrigger, null, null, null); - } - else if (_sceneTemplate != null) - { - e = Global.SmartAIMgr.GetScript((int)_sceneTemplate.SceneId, _scriptType); - FillScript(e, null, null, _sceneTemplate, null); - } - else if (_quest != null) - { - e = Global.SmartAIMgr.GetScript((int)_quest.Id, _scriptType); - FillScript(e, null, null, null, _quest); + case SmartScriptType.Creature: + e = Global.SmartAIMgr.GetScript(-(int)_me.GetSpawnId(), _scriptType); + if (e.Empty()) + e = Global.SmartAIMgr.GetScript((int)_me.GetEntry(), _scriptType); + FillScript(e, _me, null, null, null, 0); + break; + case SmartScriptType.GameObject: + e = Global.SmartAIMgr.GetScript(-(int)_go.GetSpawnId(), _scriptType); + if (e.Empty()) + e = Global.SmartAIMgr.GetScript((int)_go.GetEntry(), _scriptType); + FillScript(e, _go, null, null, null, 0); + break; + case SmartScriptType.AreaTriggerEntity: + case SmartScriptType.AreaTriggerEntityServerside: + e = Global.SmartAIMgr.GetScript((int)_areaTrigger.GetEntry(), _scriptType); + FillScript(e, _areaTrigger, null, null, null, 0); + break; + case SmartScriptType.AreaTrigger: + e = Global.SmartAIMgr.GetScript((int)_trigger.Id, _scriptType); + FillScript(e, null, _trigger, null, null, 0); + break; + case SmartScriptType.Scene: + e = Global.SmartAIMgr.GetScript((int)_sceneTemplate.SceneId, _scriptType); + FillScript(e, null, null, _sceneTemplate, null, 0); + break; + case SmartScriptType.Quest: + e = Global.SmartAIMgr.GetScript((int)_quest.Id, _scriptType); + FillScript(e, null, null, null, _quest, 0); + break; + case SmartScriptType.Event: + e = Global.SmartAIMgr.GetScript((int)_eventId, _scriptType); + FillScript(e, null, null, null, null, _eventId); + break; + default: + break; } } - public void OnInitialize(WorldObject obj, AreaTriggerRecord at = null, SceneTemplate scene = null, Quest qst = null) + public void OnInitialize(WorldObject obj, AreaTriggerRecord at = null, SceneTemplate scene = null, Quest qst = null, uint eventId = 0) { if (at != null) { @@ -4014,6 +4023,32 @@ namespace Game.AI Log.outDebug(LogFilter.ScriptsAi, $"SmartScript::OnInitialize: source is Quest with id {qst.Id}, triggered by player {_player.GetGUID()}"); } + else if (eventId != 0) + { + _scriptType = SmartScriptType.Event; + _eventId = eventId; + + if (obj.IsPlayer()) + { + _player = obj.ToPlayer(); + Log.outDebug(LogFilter.ScriptsAi, $"SmartScript::OnInitialize: source is Event {_eventId}, triggered by player {_player.GetGUID()}"); + } + else if (obj.IsCreature()) + { + _me = obj.ToCreature(); + Log.outDebug(LogFilter.ScriptsAi, $"SmartScript::OnInitialize: source is Event {_eventId}, triggered by creature {_me.GetEntry()}"); + } + else if (obj.IsGameObject()) + { + _go = obj.ToGameObject(); + Log.outDebug(LogFilter.ScriptsAi, $"SmartScript::OnInitialize: source is Event {_eventId}, triggered by gameobject {_go.GetEntry()}"); + } + else + { + Log.outError(LogFilter.ScriptsAi, $"SmartScript::OnInitialize: source is Event {_eventId}, missing trigger WorldObject"); + return; + } + } else if (obj != null) // Handle object based scripts { switch (obj.GetTypeId()) @@ -4337,6 +4372,7 @@ namespace Game.AI _me = m; _go = null; _areaTrigger = null; + _player = null; } } @@ -4348,6 +4384,7 @@ namespace Game.AI _me = null; _go = o; _areaTrigger = null; + _player = null; } } } diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index b194da09e..d8c9c61e8 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -4026,7 +4026,7 @@ namespace Game.Entities } if (eventId != 0) - GameEvents.Trigger(eventId, _owner, null); + GameEvents.Trigger(eventId, _owner, _owner); if (_autoCycleBetweenStopFrames) { diff --git a/Source/Game/Events/GameEventSender.cs b/Source/Game/Events/GameEventSender.cs index adfe3281d..a73af07b9 100644 --- a/Source/Game/Events/GameEventSender.cs +++ b/Source/Game/Events/GameEventSender.cs @@ -23,7 +23,8 @@ namespace Game if (zoneScript != null) zoneScript.ProcessEvent(target, gameEventId, source); - Map map = refForMapAndZoneScript.GetMap(); + Global.ScriptMgr.OnEventTrigger(target, source, gameEventId); + GameObject goTarget = target?.ToGameObject(); if (goTarget != null) { @@ -36,6 +37,7 @@ namespace Game if (sourcePlayer != null) TriggerForPlayer(gameEventId, sourcePlayer); + Map map = refForMapAndZoneScript.GetMap(); TriggerForMap(gameEventId, map, source, target); } diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 842ea698a..30ccc3802 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -1105,6 +1105,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} areatrigger scripts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + void LoadScripts(ScriptsType type) { uint oldMSTime = Time.GetMSTime(); @@ -1425,6 +1426,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} script definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadSpellScripts() { LoadScripts(ScriptsType.Spell); @@ -1452,20 +1454,20 @@ namespace Game Log.outError(LogFilter.Sql, $"Table `spell_scripts` - spell {spellId} effect {spellEffIndex} is not SPELL_EFFECT_SCRIPT_EFFECT or SPELL_EFFECT_DUMMY"); } } - public void LoadEventScripts() + + void LoadEventSet() { - LoadScripts(ScriptsType.Event); + _eventStorage.Clear(); - List evt_scripts = new(); - // Load all possible script entries from gameobjects + // Load all possible event ids from gameobjects foreach (var go in _gameObjectTemplateStorage) { uint eventId = go.Value.GetEventScriptId(); if (eventId != 0) - evt_scripts.Add(eventId); + _eventStorage.Add(eventId); } - // Load all possible script entries from spells + // Load all possible event ids from spells foreach (SpellNameRecord spellNameEntry in CliDB.SpellNameStorage.Values) { SpellInfo spell = Global.SpellMgr.GetSpellInfo(spellNameEntry.Id, Difficulty.None); @@ -1475,11 +1477,12 @@ namespace Game { if (spellEffectInfo.IsEffect(SpellEffectName.SendEvent)) if (spellEffectInfo.MiscValue != 0) - evt_scripts.Add((uint)spellEffectInfo.MiscValue); + _eventStorage.Add((uint)spellEffectInfo.MiscValue); } } } + // Load all possible event ids from taxi path nodes foreach (var path_idx in CliDB.TaxiPathNodesByPath) { for (uint node_idx = 0; node_idx < path_idx.Value.Length; ++node_idx) @@ -1487,21 +1490,54 @@ namespace Game TaxiPathNodeRecord node = path_idx.Value[node_idx]; if (node.ArrivalEventID != 0) - evt_scripts.Add(node.ArrivalEventID); + _eventStorage.Add(node.ArrivalEventID); if (node.DepartureEventID != 0) - evt_scripts.Add(node.DepartureEventID); + _eventStorage.Add(node.DepartureEventID); } } + } + + public void LoadEventScripts() + { + // Set of valid events referenced in several sources + LoadEventSet(); + + // Deprecated + LoadScripts(ScriptsType.Event); // Then check if all scripts are in above list of possible script entries foreach (var script in sEventScripts) { - var id = evt_scripts.Find(p => p == script.Key); - if (id == 0) - Log.outError(LogFilter.Sql, "Table `event_scripts` has script (Id: {0}) not referring to any gameobject_template type 10 data2 field, type 3 data6 field, type 13 data 2 field or any spell effect {1}", - script.Key, SpellEffectName.SendEvent); + if (!IsValidEvent(script.Key)) + Log.outError(LogFilter.Sql, $"Table `event_scripts` has script (Id: {script.Key}) not referring to any gameobject_template (type 3 data6 field, type 7 data3 field, type 10 data2 field, type 13 data2 field, type 50 data7 field), any taxi path node or any spell effect {SpellEffectName.SendEvent}"); } + + uint oldMSTime = Time.GetMSTime(); + + _eventScriptStorage.Clear(); // Reload case + + SQLResult result = DB.World.Query("SELECT Id, ScriptName FROM event_script_names"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 event scripts. DB table `event_script_names` is empty."); + return; + } + + do + { + uint eventId = result.Read(0); + string scriptName = result.Read(1); + + if (!IsValidEvent(eventId)) + { + Log.outError(LogFilter.Sql, $"Event (ID: {eventId}) not referring to any gameobject_template (type 3 data6 field, type 7 data3 field, type 10 data2 field, type 13 data2 field, type 50 data7 field), any taxi path node or any spell effect {SpellEffectName.SendEvent}"); + continue; + } + _eventScriptStorage[eventId] = GetScriptId(scriptName); + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, $"Loaded {_eventScriptStorage.Count} event scripts in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } //Load WP Scripts @@ -1664,11 +1700,18 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Validated {0} scripts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } - + public bool IsValidEvent(uint eventId) + { + return _eventStorage.Contains(eventId); + } public List GetSpellScriptsBounds(uint spellId) { return spellScriptsStorage.LookupByKey(spellId); } + public uint GetEventScriptId(uint eventId) + { + return _eventScriptStorage.LookupByKey(eventId); + } public List GetAllDBScriptNames() { return _scriptNamesStorage.GetAllDBScriptNames(); @@ -10834,6 +10877,8 @@ namespace Game public Dictionary> sEventScripts = new(); public Dictionary> sWaypointScripts = new(); Dictionary areaTriggerScriptStorage = new(); + List _eventStorage = new(); + Dictionary _eventScriptStorage = new(); //Maps public Dictionary gameTeleStorage = new(); diff --git a/Source/Game/Scripting/CoreScripts.cs b/Source/Game/Scripting/CoreScripts.cs index ea29b96ba..f7cea23d4 100644 --- a/Source/Game/Scripting/CoreScripts.cs +++ b/Source/Game/Scripting/CoreScripts.cs @@ -425,7 +425,7 @@ namespace Game.Scripting public virtual BattleField GetBattlefield(Map map) { return null; } } - + public class BattlegroundScript : ScriptObject { public BattlegroundScript(string name) : base(name) @@ -572,7 +572,7 @@ namespace Game.Scripting // Called when an achievement is completed. public virtual void OnCompleted(Player player, AchievementRecord achievement) { } } - + public class AchievementCriteriaScript : ScriptObject { public AchievementCriteriaScript(string name) : base(name) @@ -842,4 +842,17 @@ namespace Game.Scripting // Called when worldstate changes value, map is optional public virtual void OnValueChange(int worldStateId, int oldValue, int newValue, Map map) { } } + + public class EventScript : ScriptObject + { + public EventScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + // Called when a game event is triggered + public virtual void OnTrigger(WorldObject obj, WorldObject invoker, uint eventId) { } + } } diff --git a/Source/Game/Scripting/ScriptManager.cs b/Source/Game/Scripting/ScriptManager.cs index 2234b7813..6a0e58342 100644 --- a/Source/Game/Scripting/ScriptManager.cs +++ b/Source/Game/Scripting/ScriptManager.cs @@ -1110,7 +1110,15 @@ namespace Game.Scripting RunScript(script => script.OnValueChange(worldStateTemplate.Id, oldValue, newValue, map), worldStateTemplate.ScriptId); } - + + // EventScript + public void OnEventTrigger(WorldObject obj, WorldObject invoker, uint eventId) + { + Cypher.Assert(invoker != null); + + RunScript(script => script.OnTrigger(obj, invoker, eventId), Global.ObjectMgr.GetEventScriptId(eventId)); + } + public void ForEach(Action a) where T : ScriptObject { var reg = GetScriptRegistry(); diff --git a/Source/Game/Spells/SpellManager.cs b/Source/Game/Spells/SpellManager.cs index 51307fe96..e301fa682 100644 --- a/Source/Game/Spells/SpellManager.cs +++ b/Source/Game/Spells/SpellManager.cs @@ -1874,30 +1874,30 @@ namespace Game.Entities foreach (var spellEntry in mSpellInfoMap.Values) { if (spellEntry.Difficulty != Difficulty.None) + continue; + + foreach (var spellEffectInfo in spellEntry.GetEffects()) { - foreach (var spellEffectInfo in spellEntry.GetEffects()) + if (spellEffectInfo.Effect == SpellEffectName.Summon || spellEffectInfo.Effect == SpellEffectName.SummonPet) { - if (spellEffectInfo.Effect == SpellEffectName.Summon || spellEffectInfo.Effect == SpellEffectName.SummonPet) + int creature_id = spellEffectInfo.MiscValue; + CreatureTemplate cInfo = Global.ObjectMgr.GetCreatureTemplate((uint)creature_id); + if (cInfo == null) + continue; + + // get default pet spells from creature_template + uint petSpellsId = cInfo.Entry; + if (mPetDefaultSpellsMap.LookupByKey(cInfo.Entry) != null) + continue; + + PetDefaultSpellsEntry petDefSpells = new(); + for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j) + petDefSpells.spellid[j] = cInfo.Spells[j]; + + if (LoadPetDefaultSpells_helper(cInfo, petDefSpells)) { - int creature_id = spellEffectInfo.MiscValue; - CreatureTemplate cInfo = Global.ObjectMgr.GetCreatureTemplate((uint)creature_id); - if (cInfo == null) - continue; - - // get default pet spells from creature_template - uint petSpellsId = cInfo.Entry; - if (mPetDefaultSpellsMap.LookupByKey(cInfo.Entry) != null) - continue; - - PetDefaultSpellsEntry petDefSpells = new(); - for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j) - petDefSpells.spellid[j] = cInfo.Spells[j]; - - if (LoadPetDefaultSpells_helper(cInfo, petDefSpells)) - { - mPetDefaultSpellsMap[petSpellsId] = petDefSpells; - ++countCreature; - } + mPetDefaultSpellsMap[petSpellsId] = petDefSpells; + ++countCreature; } } } diff --git a/Source/Scripts/Smart/SmartAI.cs b/Source/Scripts/Smart/SmartAI.cs index 6d740dad1..fb3b68b96 100644 --- a/Source/Scripts/Smart/SmartAI.cs +++ b/Source/Scripts/Smart/SmartAI.cs @@ -115,4 +115,19 @@ namespace Scripts.Smart } } } + + [Script] + class SmartEventTrigger : EventScript + { + public SmartEventTrigger() : base("SmartEventTrigger") { } + + void OnTrigger(WorldObject obj, WorldObject invoker, uint eventId) + { + Log.outDebug(LogFilter.ScriptsAi, $"Event {eventId} is using SmartEventTrigger script"); + SmartScript script = new(); + // Set invoker as BaseObject if there isn't target for GameEvents::Trigger + script.OnInitialize(obj?? invoker, null, null, null, eventId); + script.ProcessEventsFor(SmartEvents.SendEventTrigger, invoker.ToUnit(), 0, 0, false, null, invoker.ToGameObject()); + } + } }