// 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 Framework.Constants; using Framework.Database; using Game.DataStorage; using Game.Entities; using Game.Movement; using Game.Spells; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Game.AI { public class SmartAIManager : Singleton { MultiMap[] _eventMap = new MultiMap[(int)SmartScriptType.Max]; SmartAIManager() { for (byte i = 0; i < (int)SmartScriptType.Max; i++) _eventMap[i] = new MultiMap(); } public void LoadFromDB() { uint oldMSTime = Time.GetMSTime(); for (byte i = 0; i < (int)SmartScriptType.Max; i++) _eventMap[i].Clear(); //Drop Existing SmartAI List PreparedStatement stmt = WorldDatabase.GetPreparedStatement(WorldStatements.SEL_SMART_SCRIPTS); SQLResult result = DB.World.Query(stmt); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 SmartAI scripts. DB table `smartai_scripts` is empty."); return; } int count = 0; do { SmartScriptHolder temp = new(); temp.EntryOrGuid = result.Read(0); if (temp.EntryOrGuid == 0) { Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: invalid entryorguid (0), skipped loading."); continue; } SmartScriptType source_type = (SmartScriptType)result.Read(1); if (source_type >= SmartScriptType.Max) { Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: invalid source_type ({0}), skipped loading.", source_type); continue; } if (temp.EntryOrGuid >= 0) { switch (source_type) { case SmartScriptType.Creature: if (Global.ObjectMgr.GetCreatureTemplate((uint)temp.EntryOrGuid) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: Creature entry ({0}) does not exist, skipped loading.", temp.EntryOrGuid); continue; } break; case SmartScriptType.GameObject: { if (Global.ObjectMgr.GetGameObjectTemplate((uint)temp.EntryOrGuid) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: GameObject entry ({0}) does not exist, skipped loading.", temp.EntryOrGuid); continue; } break; } case SmartScriptType.AreaTrigger: { if (CliDB.AreaTableStorage.LookupByKey((uint)temp.EntryOrGuid) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: AreaTrigger entry ({0}) does not exist, skipped loading.", temp.EntryOrGuid); continue; } break; } case SmartScriptType.Scene: { if (Global.ObjectMgr.GetSceneTemplate((uint)temp.EntryOrGuid) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: Scene id ({0}) does not exist, skipped loading.", temp.EntryOrGuid); continue; } break; } case SmartScriptType.Event: { if (!Global.ObjectMgr.IsValidEvent((uint)temp.EntryOrGuid)) { Log.outError(LogFilter.Sql, $"SmartAIMgr::LoadFromDB: Event id ({temp.EntryOrGuid}) does not exist, skipped loading."); continue; } break; } case SmartScriptType.Quest: { if (Global.ObjectMgr.GetQuestTemplate((uint)temp.EntryOrGuid) == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: Quest id ({temp.EntryOrGuid}) does not exist, skipped loading."); continue; } break; } case SmartScriptType.TimedActionlist: break;//nothing to check, really case SmartScriptType.AreaTriggerEntity: { if (Global.AreaTriggerDataStorage.GetAreaTriggerTemplate(new AreaTriggerId((uint)temp.EntryOrGuid, false)) == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: AreaTrigger entry ({temp.EntryOrGuid} IsServerSide false) does not exist, skipped loading."); continue; } break; } case SmartScriptType.AreaTriggerEntityCustom: { if (Global.AreaTriggerDataStorage.GetAreaTriggerTemplate(new AreaTriggerId((uint)temp.EntryOrGuid, true)) == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: AreaTrigger entry ({temp.EntryOrGuid} IsCustom true) does not exist, skipped loading."); continue; } break; } default: Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: not yet implemented source_type {0}", source_type); continue; } } else { switch (source_type) { case SmartScriptType.Creature: { CreatureData creature = Global.ObjectMgr.GetCreatureData((ulong)-temp.EntryOrGuid); if (creature == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: Creature guid ({-temp.EntryOrGuid}) does not exist, skipped loading."); continue; } CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(creature.Id); if (creatureInfo == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: Creature entry ({creature.Id}) guid ({-temp.EntryOrGuid}) does not exist, skipped loading."); continue; } if (creatureInfo.AIName != "SmartAI") { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: Creature entry ({creature.Id}) guid ({-temp.EntryOrGuid}) is not using SmartAI, skipped loading."); continue; } break; } case SmartScriptType.GameObject: { GameObjectData gameObject = Global.ObjectMgr.GetGameObjectData((ulong)-temp.EntryOrGuid); if (gameObject == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: GameObject guid ({-temp.EntryOrGuid}) does not exist, skipped loading."); continue; } GameObjectTemplate gameObjectInfo = Global.ObjectMgr.GetGameObjectTemplate(gameObject.Id); if (gameObjectInfo == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: GameObject entry ({gameObject.Id}) guid ({-temp.EntryOrGuid}) does not exist, skipped loading."); continue; } if (gameObjectInfo.AIName != "SmartGameObjectAI") { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: GameObject entry ({gameObject.Id}) guid ({-temp.EntryOrGuid}) is not using SmartGameObjectAI, skipped loading."); continue; } break; } default: Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: GUID-specific scripting not yet implemented for source_type {source_type}"); continue; } } temp.SourceType = source_type; temp.EventId = result.Read(2); temp.Link = result.Read(3); bool invalidDifficulties = false; foreach (string token in result.Read(4).Split(' ', StringSplitOptions.RemoveEmptyEntries)) { if (!Enum.TryParse(token, out Difficulty difficultyId)) { invalidDifficulties = true; Log.outError(LogFilter.Sql, $"SmartAIMgr::LoadFromDB: Invalid difficulties for entryorguid ({temp.EntryOrGuid}) source_type ({temp.GetScriptType()}) id ({temp.EventId}), skipped loading."); break; } if (difficultyId != 0 && !CliDB.DifficultyStorage.ContainsKey(difficultyId)) { invalidDifficulties = true; Log.outError(LogFilter.Sql, $"SmartAIMgr::LoadFromDB: Invalid difficulty id ({difficultyId}) for entryorguid ({temp.EntryOrGuid}) source_type ({temp.GetScriptType()}) id ({temp.EventId}), skipped loading."); break; } temp.Difficulties.Add(difficultyId); } if (invalidDifficulties) continue; temp.Event.type = (SmartEvents)result.Read(5); temp.Event.event_phase_mask = result.Read(6); temp.Event.event_chance = result.Read(7); temp.Event.event_flags = (SmartEventFlags)result.Read(8); temp.Event.raw.param1 = result.Read(9); temp.Event.raw.param2 = result.Read(10); temp.Event.raw.param3 = result.Read(11); temp.Event.raw.param4 = result.Read(12); temp.Event.raw.param5 = result.Read(13); temp.Event.param_string = result.Read(14); temp.Action.type = (SmartActions)result.Read(15); temp.Action.raw.param1 = result.Read(16); temp.Action.raw.param2 = result.Read(17); temp.Action.raw.param3 = result.Read(18); temp.Action.raw.param4 = result.Read(19); temp.Action.raw.param5 = result.Read(20); temp.Action.raw.param6 = result.Read(21); temp.Action.raw.param7 = result.Read(22); temp.Action.param_string = result.Read(23); temp.Target.type = (SmartTargets)result.Read(24); temp.Target.raw.param1 = result.Read(25); temp.Target.raw.param2 = result.Read(26); temp.Target.raw.param3 = result.Read(27); temp.Target.raw.param4 = result.Read(28); temp.Target.param_string = result.Read(29); temp.Target.x = result.Read(30); temp.Target.y = result.Read(31); temp.Target.z = result.Read(32); temp.Target.o = result.Read(33); //check target if (!IsTargetValid(temp)) continue; // check all event and action params if (!IsEventValid(temp)) continue; // specific check for timed events switch (temp.Event.type) { case SmartEvents.Update: case SmartEvents.UpdateOoc: case SmartEvents.UpdateIc: case SmartEvents.HealthPct: case SmartEvents.ManaPct: case SmartEvents.Range: case SmartEvents.FriendlyHealthPCT: case SmartEvents.FriendlyMissingBuff: case SmartEvents.HasAura: case SmartEvents.TargetBuffed: if (temp.Event.minMaxRepeat.repeatMin == 0 && temp.Event.minMaxRepeat.repeatMax == 0 && !temp.Event.event_flags.HasAnyFlag(SmartEventFlags.NotRepeatable) && temp.SourceType != SmartScriptType.TimedActionlist) { temp.Event.event_flags |= SmartEventFlags.NotRepeatable; Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: Entry {temp.EntryOrGuid} SourceType {temp.GetScriptType()}, Event {temp.EventId}, Missing Repeat flag."); } break; case SmartEvents.VictimCasting: if (temp.Event.minMaxRepeat.min == 0 && temp.Event.minMaxRepeat.max == 0 && !temp.Event.event_flags.HasAnyFlag(SmartEventFlags.NotRepeatable) && temp.SourceType != SmartScriptType.TimedActionlist) { temp.Event.event_flags |= SmartEventFlags.NotRepeatable; Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: Entry {temp.EntryOrGuid} SourceType {temp.GetScriptType()}, Event {temp.EventId}, Missing Repeat flag."); } break; case SmartEvents.FriendlyIsCc: if (temp.Event.friendlyCC.repeatMin == 0 && temp.Event.friendlyCC.repeatMax == 0 && !temp.Event.event_flags.HasAnyFlag(SmartEventFlags.NotRepeatable) && temp.SourceType != SmartScriptType.TimedActionlist) { temp.Event.event_flags |= SmartEventFlags.NotRepeatable; Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadFromDB: Entry {temp.EntryOrGuid} SourceType {temp.GetScriptType()}, Event {temp.EventId}, Missing Repeat flag."); } break; default: break; } // creature entry / guid not found in storage, create empty event list for it and increase counters if (!_eventMap[(int)source_type].ContainsKey(temp.EntryOrGuid)) ++count; // store the new event _eventMap[(int)source_type].Add(temp.EntryOrGuid, temp); } while (result.NextRow()); // Post Loading Validation for (byte i = 0; i < (int)SmartScriptType.Max; ++i) { if (_eventMap[i] == null) continue; foreach (var key in _eventMap[i].Keys) { var list = _eventMap[i].LookupByKey(key); foreach (var e in list) { if (e.Link != 0) { if (FindLinkedEvent(list, e.Link) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: Entry {0} SourceType {1}, Event {2}, Link Event {3} not found or invalid.", e.EntryOrGuid, e.GetScriptType(), e.EventId, e.Link); } } if (e.GetEventType() == SmartEvents.Link) { if (FindLinkedSourceEvent(list, e.EventId) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr.LoadFromDB: Entry {0} SourceType {1}, Event {2}, Link Source Event not found or invalid. Event will never trigger.", e.EntryOrGuid, e.GetScriptType(), e.EventId); } } } } } Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SmartAI scripts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } static bool EventHasInvoker(SmartEvents smartEvent) { switch (smartEvent) { // white list of events that actually have an invoker passed to them case SmartEvents.Aggro: case SmartEvents.Death: case SmartEvents.Kill: case SmartEvents.SummonedUnit: case SmartEvents.SummonedUnitDies: case SmartEvents.SpellHit: case SmartEvents.SpellHitTarget: case SmartEvents.Damaged: case SmartEvents.ReceiveHeal: case SmartEvents.ReceiveEmote: case SmartEvents.JustSummoned: case SmartEvents.DamagedTarget: case SmartEvents.SummonDespawned: case SmartEvents.PassengerBoarded: case SmartEvents.PassengerRemoved: case SmartEvents.GossipHello: case SmartEvents.GossipSelect: case SmartEvents.AcceptedQuest: case SmartEvents.RewardQuest: case SmartEvents.FollowCompleted: case SmartEvents.OnSpellclick: case SmartEvents.GoLootStateChanged: case SmartEvents.AreatriggerOntrigger: case SmartEvents.IcLos: case SmartEvents.OocLos: case SmartEvents.DistanceCreature: case SmartEvents.FriendlyHealthPCT: case SmartEvents.FriendlyIsCc: case SmartEvents.FriendlyMissingBuff: case SmartEvents.ActionDone: case SmartEvents.Range: case SmartEvents.VictimCasting: case SmartEvents.TargetBuffed: case SmartEvents.InstancePlayerEnter: case SmartEvents.TransportAddcreature: case SmartEvents.DataSet: case SmartEvents.QuestAccepted: case SmartEvents.QuestObjCompletion: case SmartEvents.QuestCompletion: case SmartEvents.QuestFail: case SmartEvents.QuestRewarded: case SmartEvents.SceneStart: case SmartEvents.SceneTrigger: case SmartEvents.SceneCancel: case SmartEvents.SceneComplete: case SmartEvents.SendEventTrigger: return true; default: return false; } } static bool IsTargetValid(SmartScriptHolder e) { if (Math.Abs(e.Target.o) > 2 * MathFunctions.PI) Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} has abs(`target.o` = {e.Target.o}) > 2*PI (orientation is expressed in radians)"); switch (e.GetTargetType()) { case SmartTargets.CreatureDistance: case SmartTargets.CreatureRange: { if (e.Target.unitDistance.creature != 0 && Global.ObjectMgr.GetCreatureTemplate(e.Target.unitDistance.creature) == null) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Creature entry {e.Target.unitDistance.creature} as target_param1, skipped."); return false; } break; } case SmartTargets.GameobjectDistance: case SmartTargets.GameobjectRange: { if (e.Target.goDistance.entry != 0 && Global.ObjectMgr.GetGameObjectTemplate(e.Target.goDistance.entry) == null) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent GameObject entry {e.Target.goDistance.entry} as target_param1, skipped."); return false; } break; } case SmartTargets.CreatureGuid: { if (e.Target.unitGUID.entry != 0 && !IsCreatureValid(e, e.Target.unitGUID.entry)) return false; ulong guid = e.Target.unitGUID.dbGuid; CreatureData data = Global.ObjectMgr.GetCreatureData(guid); if (data == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} using invalid creature guid {guid} as target_param1, skipped."); return false; } else if (e.Target.unitGUID.entry != 0 && e.Target.unitGUID.entry != data.Id) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} using invalid creature entry {e.Target.unitGUID.entry} (expected {data.Id}) for guid {guid} as target_param1, skipped."); return false; } break; } case SmartTargets.GameobjectGuid: { if (e.Target.goGUID.entry != 0 && !IsGameObjectValid(e, e.Target.goGUID.entry)) return false; ulong guid = e.Target.goGUID.dbGuid; GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid); if (data == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} using invalid gameobject guid {guid} as target_param1, skipped."); return false; } else if (e.Target.goGUID.entry != 0 && e.Target.goGUID.entry != data.Id) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} using invalid gameobject entry {e.Target.goGUID.entry} (expected {data.Id}) for guid {guid} as target_param1, skipped."); return false; } break; } case SmartTargets.PlayerDistance: case SmartTargets.ClosestPlayer: { if (e.Target.playerDistance.dist == 0) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} has maxDist 0 as target_param1, skipped."); return false; } break; } case SmartTargets.ActionInvoker: case SmartTargets.ActionInvokerVehicle: case SmartTargets.InvokerParty: if (e.GetScriptType() != SmartScriptType.TimedActionlist && e.GetEventType() != SmartEvents.Link && !EventHasInvoker(e.Event.type)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.GetEventType()} Action {e.GetActionType()} has invoker target, but event does not provide any invoker!"); return false; } break; case SmartTargets.HostileSecondAggro: case SmartTargets.HostileLastAggro: case SmartTargets.HostileRandom: case SmartTargets.HostileRandomNotTop: TC_SAI_IS_BOOLEAN_VALID(e, e.Target.hostilRandom.playerOnly); break; case SmartTargets.Farthest: TC_SAI_IS_BOOLEAN_VALID(e, e.Target.farthest.playerOnly); TC_SAI_IS_BOOLEAN_VALID(e, e.Target.farthest.isInLos); break; case SmartTargets.ClosestCreature: TC_SAI_IS_BOOLEAN_VALID(e, e.Target.unitClosest.dead); break; case SmartTargets.ClosestEnemy: TC_SAI_IS_BOOLEAN_VALID(e, e.Target.closestAttackable.playerOnly); break; case SmartTargets.ClosestFriendly: TC_SAI_IS_BOOLEAN_VALID(e, e.Target.closestFriendly.playerOnly); break; case SmartTargets.OwnerOrSummoner: TC_SAI_IS_BOOLEAN_VALID(e, e.Target.owner.useCharmerOrOwner); break; case SmartTargets.ClosestGameobject: case SmartTargets.PlayerRange: case SmartTargets.Self: case SmartTargets.Victim: case SmartTargets.Position: case SmartTargets.None: case SmartTargets.ThreatList: case SmartTargets.Stored: case SmartTargets.LootRecipients: case SmartTargets.VehiclePassenger: case SmartTargets.ClosestUnspawnedGameobject: break; default: Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled target_type({0}), Entry {1} SourceType {2} Event {3} Action {4}, skipped.", e.GetTargetType(), e.EntryOrGuid, e.GetScriptType(), e.EventId, e.GetActionType()); return false; } if (!CheckUnusedTargetParams(e)) return false; return true; } static bool IsSpellVisualKitValid(SmartScriptHolder e, uint entry) { if (!CliDB.SpellVisualKitStorage.ContainsKey(entry)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.EventId} Action {e.GetActionType()} uses non-existent SpellVisualKit entry {entry}, skipped."); return false; } return true; } static bool CheckUnusedEventParams(SmartScriptHolder e) { int paramsStructSize = e.Event.type switch { SmartEvents.UpdateIc => Marshal.SizeOf(typeof(SmartEvent.MinMaxRepeat)), SmartEvents.UpdateOoc => Marshal.SizeOf(typeof(SmartEvent.MinMaxRepeat)), SmartEvents.HealthPct => Marshal.SizeOf(typeof(SmartEvent.MinMaxRepeat)), SmartEvents.ManaPct => Marshal.SizeOf(typeof(SmartEvent.MinMaxRepeat)), SmartEvents.Aggro => 0, SmartEvents.Kill => Marshal.SizeOf(typeof(SmartEvent.Kill)), SmartEvents.Death => 0, SmartEvents.Evade => 0, SmartEvents.SpellHit => Marshal.SizeOf(typeof(SmartEvent.SpellHit)), SmartEvents.Range => Marshal.SizeOf(typeof(SmartEvent.MinMaxRepeat)), SmartEvents.OocLos => Marshal.SizeOf(typeof(SmartEvent.Los)), SmartEvents.Respawn => Marshal.SizeOf(typeof(SmartEvent.Respawn)), SmartEvents.VictimCasting => Marshal.SizeOf(typeof(SmartEvent.TargetCasting)), SmartEvents.FriendlyIsCc => Marshal.SizeOf(typeof(SmartEvent.FriendlyCC)), SmartEvents.FriendlyMissingBuff => Marshal.SizeOf(typeof(SmartEvent.MissingBuff)), SmartEvents.SummonedUnit => Marshal.SizeOf(typeof(SmartEvent.Summoned)), SmartEvents.AcceptedQuest => Marshal.SizeOf(typeof(SmartEvent.Quest)), SmartEvents.RewardQuest => Marshal.SizeOf(typeof(SmartEvent.Quest)), SmartEvents.ReachedHome => 0, SmartEvents.ReceiveEmote => Marshal.SizeOf(typeof(SmartEvent.Emote)), SmartEvents.HasAura => Marshal.SizeOf(typeof(SmartEvent.Aura)), SmartEvents.TargetBuffed => Marshal.SizeOf(typeof(SmartEvent.Aura)), SmartEvents.Reset => 0, SmartEvents.IcLos => Marshal.SizeOf(typeof(SmartEvent.Los)), SmartEvents.PassengerBoarded => Marshal.SizeOf(typeof(SmartEvent.MinMax)), SmartEvents.PassengerRemoved => Marshal.SizeOf(typeof(SmartEvent.MinMax)), SmartEvents.Charmed => Marshal.SizeOf(typeof(SmartEvent.Charm)), SmartEvents.SpellHitTarget => Marshal.SizeOf(typeof(SmartEvent.SpellHit)), SmartEvents.Damaged => Marshal.SizeOf(typeof(SmartEvent.MinMaxRepeat)), SmartEvents.DamagedTarget => Marshal.SizeOf(typeof(SmartEvent.MinMaxRepeat)), SmartEvents.Movementinform => Marshal.SizeOf(typeof(SmartEvent.MovementInform)), SmartEvents.SummonDespawned => Marshal.SizeOf(typeof(SmartEvent.Summoned)), SmartEvents.CorpseRemoved => 0, SmartEvents.AiInit => 0, SmartEvents.DataSet => Marshal.SizeOf(typeof(SmartEvent.DataSet)), SmartEvents.WaypointReached => Marshal.SizeOf(typeof(SmartEvent.Waypoint)), SmartEvents.TransportAddplayer => 0, SmartEvents.TransportAddcreature => Marshal.SizeOf(typeof(SmartEvent.TransportAddCreature)), SmartEvents.TransportRemovePlayer => 0, SmartEvents.TransportRelocate => Marshal.SizeOf(typeof(SmartEvent.TransportRelocate)), SmartEvents.InstancePlayerEnter => Marshal.SizeOf(typeof(SmartEvent.InstancePlayerEnter)), SmartEvents.AreatriggerOntrigger => Marshal.SizeOf(typeof(SmartEvent.Areatrigger)), SmartEvents.QuestAccepted => 0, SmartEvents.QuestObjCompletion => 0, SmartEvents.QuestCompletion => 0, SmartEvents.QuestRewarded => 0, SmartEvents.QuestFail => 0, SmartEvents.TextOver => Marshal.SizeOf(typeof(SmartEvent.TextOver)), SmartEvents.ReceiveHeal => Marshal.SizeOf(typeof(SmartEvent.MinMaxRepeat)), SmartEvents.JustSummoned => 0, SmartEvents.WaypointPaused => Marshal.SizeOf(typeof(SmartEvent.Waypoint)), SmartEvents.WaypointResumed => Marshal.SizeOf(typeof(SmartEvent.Waypoint)), SmartEvents.WaypointStopped => Marshal.SizeOf(typeof(SmartEvent.Waypoint)), SmartEvents.WaypointEnded => Marshal.SizeOf(typeof(SmartEvent.Waypoint)), SmartEvents.TimedEventTriggered => Marshal.SizeOf(typeof(SmartEvent.TimedEvent)), SmartEvents.Update => Marshal.SizeOf(typeof(SmartEvent.MinMaxRepeat)), SmartEvents.Link => 0, SmartEvents.GossipSelect => Marshal.SizeOf(typeof(SmartEvent.Gossip)), SmartEvents.JustCreated => 0, SmartEvents.GossipHello => Marshal.SizeOf(typeof(SmartEvent.GossipHello)), SmartEvents.FollowCompleted => 0, SmartEvents.GameEventStart => Marshal.SizeOf(typeof(SmartEvent.GameEvent)), SmartEvents.GameEventEnd => Marshal.SizeOf(typeof(SmartEvent.GameEvent)), SmartEvents.GoLootStateChanged => Marshal.SizeOf(typeof(SmartEvent.GoLootStateChanged)), SmartEvents.GoEventInform => Marshal.SizeOf(typeof(SmartEvent.EventInform)), SmartEvents.ActionDone => Marshal.SizeOf(typeof(SmartEvent.DoAction)), SmartEvents.OnSpellclick => 0, SmartEvents.FriendlyHealthPCT => Marshal.SizeOf(typeof(SmartEvent.FriendlyHealthPct)), SmartEvents.DistanceCreature => Marshal.SizeOf(typeof(SmartEvent.Distance)), SmartEvents.DistanceGameobject => Marshal.SizeOf(typeof(SmartEvent.Distance)), SmartEvents.CounterSet => Marshal.SizeOf(typeof(SmartEvent.Counter)), SmartEvents.SceneStart => 0, SmartEvents.SceneTrigger => 0, SmartEvents.SceneCancel => 0, SmartEvents.SceneComplete => 0, SmartEvents.SummonedUnitDies => Marshal.SizeOf(typeof(SmartEvent.Summoned)), SmartEvents.OnSpellCast => Marshal.SizeOf(typeof(SmartEvent.SpellCast)), 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)), }; int rawCount = Marshal.SizeOf(typeof(SmartEvent.Raw)) / sizeof(uint); int paramsCount = paramsStructSize / sizeof(uint); for (int index = paramsCount; index < rawCount; index++) { uint value = 0; switch (index) { case 0: value = e.Event.raw.param1; break; case 1: value = e.Event.raw.param2; break; case 2: value = e.Event.raw.param3; break; case 3: value = e.Event.raw.param4; break; case 4: value = e.Event.raw.param5; break; } if (value != 0) Log.outWarn(LogFilter.Sql, $"SmartAIMgr: {e} has unused event_param{index + 1} with value {value}, it should be 0."); } bool eventUsesStringParam() { switch (e.GetEventType()) { case SmartEvents.SceneTrigger: return true; default: break; } return false; } if (!eventUsesStringParam() && !e.Event.param_string.IsEmpty()) Log.outWarn(LogFilter.Sql, $"SmartAIMgr: {e} has unused event_param_string with value {e.Event.param_string}, it should be NULL."); return true; } static bool CheckUnusedActionParams(SmartScriptHolder e) { int paramsStructSize = e.Action.type switch { SmartActions.None => 0, SmartActions.Talk => Marshal.SizeOf(typeof(SmartAction.Talk)), SmartActions.SetFaction => Marshal.SizeOf(typeof(SmartAction.Faction)), SmartActions.MorphToEntryOrModel => Marshal.SizeOf(typeof(SmartAction.MorphOrMount)), SmartActions.Sound => Marshal.SizeOf(typeof(SmartAction.Sound)), SmartActions.PlayEmote => Marshal.SizeOf(typeof(SmartAction.Emote)), SmartActions.FailQuest => Marshal.SizeOf(typeof(SmartAction.Quest)), SmartActions.OfferQuest => Marshal.SizeOf(typeof(SmartAction.QuestOffer)), SmartActions.SetReactState => Marshal.SizeOf(typeof(SmartAction.React)), SmartActions.ActivateGobject => 0, SmartActions.RandomEmote => Marshal.SizeOf(typeof(SmartAction.RandomEmote)), SmartActions.Cast => Marshal.SizeOf(typeof(SmartAction.Cast)), SmartActions.SummonCreature => Marshal.SizeOf(typeof(SmartAction.SummonCreature)), SmartActions.ThreatSinglePct => Marshal.SizeOf(typeof(SmartAction.ThreatPCT)), SmartActions.ThreatAllPct => Marshal.SizeOf(typeof(SmartAction.ThreatPCT)), SmartActions.SetIngamePhaseGroup => Marshal.SizeOf(typeof(SmartAction.IngamePhaseGroup)), SmartActions.SetEmoteState => Marshal.SizeOf(typeof(SmartAction.Emote)), SmartActions.AutoAttack => Marshal.SizeOf(typeof(SmartAction.AutoAttack)), SmartActions.AllowCombatMovement => Marshal.SizeOf(typeof(SmartAction.CombatMove)), SmartActions.SetEventPhase => Marshal.SizeOf(typeof(SmartAction.SetEventPhase)), SmartActions.IncEventPhase => Marshal.SizeOf(typeof(SmartAction.IncEventPhase)), SmartActions.Evade => Marshal.SizeOf(typeof(SmartAction.Evade)), SmartActions.FleeForAssist => Marshal.SizeOf(typeof(SmartAction.FleeAssist)), SmartActions.CombatStop => 0, SmartActions.RemoveAurasFromSpell => Marshal.SizeOf(typeof(SmartAction.RemoveAura)), SmartActions.Follow => Marshal.SizeOf(typeof(SmartAction.Follow)), SmartActions.RandomPhase => Marshal.SizeOf(typeof(SmartAction.RandomPhase)), SmartActions.RandomPhaseRange => Marshal.SizeOf(typeof(SmartAction.RandomPhaseRange)), SmartActions.ResetGobject => 0, SmartActions.CallKilledmonster => Marshal.SizeOf(typeof(SmartAction.KilledMonster)), SmartActions.SetInstData => Marshal.SizeOf(typeof(SmartAction.SetInstanceData)), SmartActions.SetInstData64 => Marshal.SizeOf(typeof(SmartAction.SetInstanceData64)), SmartActions.UpdateTemplate => Marshal.SizeOf(typeof(SmartAction.UpdateTemplate)), SmartActions.Die => 0, SmartActions.SetInCombatWithZone => 0, SmartActions.CallForHelp => Marshal.SizeOf(typeof(SmartAction.CallHelp)), SmartActions.SetSheath => Marshal.SizeOf(typeof(SmartAction.SetSheath)), SmartActions.ForceDespawn => Marshal.SizeOf(typeof(SmartAction.ForceDespawn)), SmartActions.SetInvincibilityHpLevel => Marshal.SizeOf(typeof(SmartAction.InvincHP)), SmartActions.MountToEntryOrModel => Marshal.SizeOf(typeof(SmartAction.MorphOrMount)), SmartActions.SetIngamePhaseId => Marshal.SizeOf(typeof(SmartAction.IngamePhaseId)), SmartActions.SetData => Marshal.SizeOf(typeof(SmartAction.SetData)), SmartActions.AttackStop => 0, SmartActions.SetVisibility => Marshal.SizeOf(typeof(SmartAction.Visibility)), SmartActions.SetActive => Marshal.SizeOf(typeof(SmartAction.Active)), SmartActions.AttackStart => 0, SmartActions.SummonGo => Marshal.SizeOf(typeof(SmartAction.SummonGO)), SmartActions.KillUnit => 0, SmartActions.ActivateTaxi => Marshal.SizeOf(typeof(SmartAction.Taxi)), SmartActions.WpStart => Marshal.SizeOf(typeof(SmartAction.WpStart)), SmartActions.WpPause => Marshal.SizeOf(typeof(SmartAction.WpPause)), SmartActions.WpStop => Marshal.SizeOf(typeof(SmartAction.WpStop)), SmartActions.AddItem => Marshal.SizeOf(typeof(SmartAction.Item)), SmartActions.RemoveItem => Marshal.SizeOf(typeof(SmartAction.Item)), SmartActions.SetRun => Marshal.SizeOf(typeof(SmartAction.SetRun)), SmartActions.SetDisableGravity => Marshal.SizeOf(typeof(SmartAction.SetDisableGravity)), SmartActions.Teleport => Marshal.SizeOf(typeof(SmartAction.Teleport)), SmartActions.SetCounter => Marshal.SizeOf(typeof(SmartAction.SetCounter)), SmartActions.StoreTargetList => Marshal.SizeOf(typeof(SmartAction.StoreTargets)), SmartActions.WpResume => 0, SmartActions.SetOrientation => 0, SmartActions.CreateTimedEvent => Marshal.SizeOf(typeof(SmartAction.TimeEvent)), SmartActions.Playmovie => Marshal.SizeOf(typeof(SmartAction.Movie)), SmartActions.MoveToPos => Marshal.SizeOf(typeof(SmartAction.MoveToPos)), SmartActions.EnableTempGobj => Marshal.SizeOf(typeof(SmartAction.EnableTempGO)), SmartActions.Equip => Marshal.SizeOf(typeof(SmartAction.Equip)), SmartActions.CloseGossip => 0, SmartActions.TriggerTimedEvent => Marshal.SizeOf(typeof(SmartAction.TimeEvent)), SmartActions.RemoveTimedEvent => Marshal.SizeOf(typeof(SmartAction.TimeEvent)), SmartActions.CallScriptReset => 0, SmartActions.SetRangedMovement => Marshal.SizeOf(typeof(SmartAction.SetRangedMovement)), SmartActions.CallTimedActionlist => Marshal.SizeOf(typeof(SmartAction.TimedActionList)), SmartActions.SetNpcFlag => Marshal.SizeOf(typeof(SmartAction.Flag)), SmartActions.AddNpcFlag => Marshal.SizeOf(typeof(SmartAction.Flag)), SmartActions.RemoveNpcFlag => Marshal.SizeOf(typeof(SmartAction.Flag)), SmartActions.SimpleTalk => Marshal.SizeOf(typeof(SmartAction.SimpleTalk)), SmartActions.SelfCast => Marshal.SizeOf(typeof(SmartAction.Cast)), SmartActions.CrossCast => Marshal.SizeOf(typeof(SmartAction.CrossCast)), SmartActions.CallRandomTimedActionlist => Marshal.SizeOf(typeof(SmartAction.RandTimedActionList)), SmartActions.CallRandomRangeTimedActionlist => Marshal.SizeOf(typeof(SmartAction.RandRangeTimedActionList)), SmartActions.RandomMove => Marshal.SizeOf(typeof(SmartAction.MoveRandom)), SmartActions.SetUnitFieldBytes1 => Marshal.SizeOf(typeof(SmartAction.SetunitByte)), SmartActions.RemoveUnitFieldBytes1 => Marshal.SizeOf(typeof(SmartAction.DelunitByte)), SmartActions.InterruptSpell => Marshal.SizeOf(typeof(SmartAction.InterruptSpellCasting)), SmartActions.AddDynamicFlag => Marshal.SizeOf(typeof(SmartAction.Flag)), SmartActions.RemoveDynamicFlag => Marshal.SizeOf(typeof(SmartAction.Flag)), SmartActions.JumpToPos => Marshal.SizeOf(typeof(SmartAction.Jump)), SmartActions.SendGossipMenu => Marshal.SizeOf(typeof(SmartAction.SendGossipMenu)), SmartActions.GoSetLootState => Marshal.SizeOf(typeof(SmartAction.SetGoLootState)), SmartActions.SendTargetToTarget => Marshal.SizeOf(typeof(SmartAction.SendTargetToTarget)), SmartActions.SetHomePos => 0, SmartActions.SetHealthRegen => Marshal.SizeOf(typeof(SmartAction.SetHealthRegen)), SmartActions.SetRoot => Marshal.SizeOf(typeof(SmartAction.SetRoot)), SmartActions.SummonCreatureGroup => Marshal.SizeOf(typeof(SmartAction.CreatureGroup)), SmartActions.SetPower => Marshal.SizeOf(typeof(SmartAction.Power)), SmartActions.AddPower => Marshal.SizeOf(typeof(SmartAction.Power)), SmartActions.RemovePower => Marshal.SizeOf(typeof(SmartAction.Power)), SmartActions.GameEventStop => Marshal.SizeOf(typeof(SmartAction.GameEventStop)), SmartActions.GameEventStart => Marshal.SizeOf(typeof(SmartAction.GameEventStart)), SmartActions.StartClosestWaypoint => Marshal.SizeOf(typeof(SmartAction.ClosestWaypointFromList)), SmartActions.MoveOffset => Marshal.SizeOf(typeof(SmartAction.MoveOffset)), SmartActions.RandomSound => Marshal.SizeOf(typeof(SmartAction.RandomSound)), SmartActions.SetCorpseDelay => Marshal.SizeOf(typeof(SmartAction.CorpseDelay)), SmartActions.DisableEvade => Marshal.SizeOf(typeof(SmartAction.DisableEvade)), SmartActions.GoSetGoState => Marshal.SizeOf(typeof(SmartAction.GoState)), SmartActions.AddThreat => Marshal.SizeOf(typeof(SmartAction.Threat)), SmartActions.LoadEquipment => Marshal.SizeOf(typeof(SmartAction.LoadEquipment)), SmartActions.TriggerRandomTimedEvent => Marshal.SizeOf(typeof(SmartAction.RandomTimedEvent)), SmartActions.PauseMovement => Marshal.SizeOf(typeof(SmartAction.PauseMovement)), SmartActions.PlayAnimkit => Marshal.SizeOf(typeof(SmartAction.AnimKit)), SmartActions.ScenePlay => Marshal.SizeOf(typeof(SmartAction.Scene)), SmartActions.SceneCancel => Marshal.SizeOf(typeof(SmartAction.Scene)), SmartActions.SpawnSpawngroup => Marshal.SizeOf(typeof(SmartAction.GroupSpawn)), SmartActions.DespawnSpawngroup => Marshal.SizeOf(typeof(SmartAction.GroupSpawn)), SmartActions.RespawnBySpawnId => Marshal.SizeOf(typeof(SmartAction.RespawnData)), SmartActions.InvokerCast => Marshal.SizeOf(typeof(SmartAction.Cast)), SmartActions.PlayCinematic => Marshal.SizeOf(typeof(SmartAction.Cinematic)), SmartActions.SetMovementSpeed => Marshal.SizeOf(typeof(SmartAction.MovementSpeed)), SmartActions.PlaySpellVisualKit => Marshal.SizeOf(typeof(SmartAction.SpellVisualKit)), SmartActions.OverrideLight => Marshal.SizeOf(typeof(SmartAction.OverrideLight)), SmartActions.OverrideWeather => Marshal.SizeOf(typeof(SmartAction.OverrideWeather)), SmartActions.SetAIAnimKit => 0, SmartActions.SetHover => Marshal.SizeOf(typeof(SmartAction.SetHover)), SmartActions.SetHealthPct => Marshal.SizeOf(typeof(SmartAction.SetHealthPct)), SmartActions.CreateConversation => Marshal.SizeOf(typeof(SmartAction.Conversation)), SmartActions.SetImmunePC => Marshal.SizeOf(typeof(SmartAction.SetImmunePC)), SmartActions.SetImmuneNPC => Marshal.SizeOf(typeof(SmartAction.SetImmuneNPC)), SmartActions.SetUninteractible => Marshal.SizeOf(typeof(SmartAction.SetUninteractible)), SmartActions.ActivateGameobject => Marshal.SizeOf(typeof(SmartAction.ActivateGameObject)), SmartActions.AddToStoredTargetList => Marshal.SizeOf(typeof(SmartAction.AddToStoredTargets)), SmartActions.BecomePersonalCloneForPlayer => Marshal.SizeOf(typeof(SmartAction.BecomePersonalClone)), SmartActions.TriggerGameEvent => Marshal.SizeOf(typeof(SmartAction.TriggerGameEvent)), SmartActions.DoAction => Marshal.SizeOf(typeof(SmartAction.DoAction)), SmartActions.CompleteQuest => Marshal.SizeOf(typeof(SmartAction.Quest)), _ => Marshal.SizeOf(typeof(SmartAction.Raw)), }; int rawCount = Marshal.SizeOf(typeof(SmartAction.Raw)) / sizeof(uint); int paramsCount = paramsStructSize / sizeof(uint); for (int index = paramsCount; index < rawCount; index++) { uint value = 0; switch (index) { case 0: value = e.Action.raw.param1; break; case 1: value = e.Action.raw.param2; break; case 2: value = e.Action.raw.param3; break; case 3: value = e.Action.raw.param4; break; case 4: value = e.Action.raw.param5; break; case 5: value = e.Action.raw.param6; break; } if (value != 0) Log.outWarn(LogFilter.Sql, $"SmartAIMgr: {e} has unused action_param{index + 1} with value {value}, it should be 0."); } bool actionUsesStringParam() { switch (e.GetActionType()) { case SmartActions.CrossCast: return true; default: break; } return false; }; if (!actionUsesStringParam() && !e.Action.param_string.IsEmpty()) Log.outWarn(LogFilter.Sql, $"SmartAIMgr: {e} has unused action_param_string with value {e.Action.param_string}, it should be NULL."); return true; } static bool CheckUnusedTargetParams(SmartScriptHolder e) { int paramsStructSize = e.Target.type switch { SmartTargets.None => 0, SmartTargets.Self => 0, SmartTargets.Victim => 0, SmartTargets.HostileSecondAggro => Marshal.SizeOf(typeof(SmartTarget.HostilRandom)), SmartTargets.HostileLastAggro => Marshal.SizeOf(typeof(SmartTarget.HostilRandom)), SmartTargets.HostileRandom => Marshal.SizeOf(typeof(SmartTarget.HostilRandom)), SmartTargets.HostileRandomNotTop => Marshal.SizeOf(typeof(SmartTarget.HostilRandom)), SmartTargets.ActionInvoker => 0, SmartTargets.Position => 0, //Uses X,Y,Z,O SmartTargets.CreatureRange => Marshal.SizeOf(typeof(SmartTarget.UnitRange)), SmartTargets.CreatureGuid => Marshal.SizeOf(typeof(SmartTarget.UnitGUID)), SmartTargets.CreatureDistance => Marshal.SizeOf(typeof(SmartTarget.UnitDistance)), SmartTargets.Stored => Marshal.SizeOf(typeof(SmartTarget.Stored)), SmartTargets.GameobjectRange => Marshal.SizeOf(typeof(SmartTarget.GoRange)), SmartTargets.GameobjectGuid => Marshal.SizeOf(typeof(SmartTarget.GoGUID)), SmartTargets.GameobjectDistance => Marshal.SizeOf(typeof(SmartTarget.GoDistance)), SmartTargets.InvokerParty => 0, SmartTargets.PlayerRange => Marshal.SizeOf(typeof(SmartTarget.PlayerRange)), SmartTargets.PlayerDistance => Marshal.SizeOf(typeof(SmartTarget.PlayerDistance)), SmartTargets.ClosestCreature => Marshal.SizeOf(typeof(SmartTarget.UnitClosest)), SmartTargets.ClosestGameobject => Marshal.SizeOf(typeof(SmartTarget.GoClosest)), SmartTargets.ClosestPlayer => Marshal.SizeOf(typeof(SmartTarget.PlayerDistance)), SmartTargets.ActionInvokerVehicle => 0, SmartTargets.OwnerOrSummoner => Marshal.SizeOf(typeof(SmartTarget.Owner)), SmartTargets.ThreatList => Marshal.SizeOf(typeof(SmartTarget.ThreatList)), SmartTargets.ClosestEnemy => Marshal.SizeOf(typeof(SmartTarget.ClosestAttackable)), SmartTargets.ClosestFriendly => Marshal.SizeOf(typeof(SmartTarget.ClosestFriendly)), SmartTargets.LootRecipients => 0, SmartTargets.Farthest => Marshal.SizeOf(typeof(SmartTarget.Farthest)), SmartTargets.VehiclePassenger => Marshal.SizeOf(typeof(SmartTarget.Vehicle)), SmartTargets.ClosestUnspawnedGameobject => Marshal.SizeOf(typeof(SmartTarget.GoClosest)), _ => Marshal.SizeOf(typeof(SmartTarget.Raw)), }; int rawCount = Marshal.SizeOf(typeof(SmartTarget.Raw)) / sizeof(uint); int paramsCount = paramsStructSize / sizeof(uint); for (int index = paramsCount; index < rawCount; index++) { uint value = 0; switch (index) { case 0: value = e.Target.raw.param1; break; case 1: value = e.Target.raw.param2; break; case 2: value = e.Target.raw.param3; break; case 3: value = e.Target.raw.param4; break; } if (value != 0) Log.outWarn(LogFilter.Sql, $"SmartAIMgr: {e} has unused target_param{index + 1} with value {value}, it must be 0, skipped."); } bool targetUsesStringParam() { switch (e.GetTargetType()) { case SmartTargets.CreatureRange: case SmartTargets.CreatureDistance: case SmartTargets.GameobjectRange: case SmartTargets.GameobjectDistance: case SmartTargets.ClosestCreature: case SmartTargets.ClosestGameobject: return true; default: break; } return false; } if (!targetUsesStringParam() && !e.Target.param_string.IsEmpty()) Log.outWarn(LogFilter.Sql, $"SmartAIMgr: {e} has unused target_param_string with value {e.Target.param_string}, it should be NULL."); return true; } bool IsEventValid(SmartScriptHolder e) { if (e.Event.type >= SmartEvents.End) { Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid event type ({2}), skipped.", e.EntryOrGuid, e.EventId, e.GetEventType()); return false; } // in SMART_SCRIPT_TYPE_TIMED_ACTIONLIST all event types are overriden by core if (e.GetScriptType() != SmartScriptType.TimedActionlist && !Convert.ToBoolean(GetEventMask(e.Event.type) & GetTypeMask(e.GetScriptType()))) { Log.outError(LogFilter.Scripts, "SmartAIMgr: EntryOrGuid {0}, event type {1} can not be used for Script type {2}", e.EntryOrGuid, e.GetEventType(), e.GetScriptType()); return false; } if (e.Action.type <= 0 || e.Action.type >= SmartActions.End) { Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid action type ({2}), skipped.", e.EntryOrGuid, e.EventId, e.GetActionType()); return false; } if (e.Event.event_phase_mask > (uint)SmartEventPhaseBits.All) { Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid phase mask ({2}), skipped.", e.EntryOrGuid, e.EventId, e.Event.event_phase_mask); return false; } if (e.Event.event_flags > SmartEventFlags.All) { Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid event flags ({2}), skipped.", e.EntryOrGuid, e.EventId, e.Event.event_flags); return false; } if (e.Event.event_flags.HasFlag(SmartEventFlags.Deprecated)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: EntryOrGuid {e.EntryOrGuid} using event ({e.EventId}) has deprecated event flags ({e.Event.event_flags}), skipped."); return false; } if (e.Link != 0 && e.Link == e.EventId) { Log.outError(LogFilter.Sql, "SmartAIMgr: EntryOrGuid {0} SourceType {1}, Event {2}, Event is linking self (infinite loop), skipped.", e.EntryOrGuid, e.GetScriptType(), e.EventId); return false; } if (e.GetScriptType() == SmartScriptType.TimedActionlist) { e.Event.type = SmartEvents.UpdateOoc;//force default OOC, can change when calling the script! if (!IsMinMaxValid(e, e.Event.minMaxRepeat.min, e.Event.minMaxRepeat.max)) return false; if (!IsMinMaxValid(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax)) return false; } else { switch (e.Event.type) { case SmartEvents.Update: case SmartEvents.UpdateIc: case SmartEvents.UpdateOoc: case SmartEvents.HealthPct: case SmartEvents.ManaPct: case SmartEvents.Range: case SmartEvents.Damaged: case SmartEvents.DamagedTarget: case SmartEvents.ReceiveHeal: if (!IsMinMaxValid(e, e.Event.minMaxRepeat.min, e.Event.minMaxRepeat.max)) return false; if (!IsMinMaxValid(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax)) return false; break; case SmartEvents.SpellHit: case SmartEvents.SpellHitTarget: if (e.Event.spellHit.spell != 0) { SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(e.Event.spellHit.spell, Difficulty.None); if (spellInfo == null) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Spell entry {e.Event.spellHit.spell}, skipped."); return false; } if (e.Event.spellHit.school != 0 && ((SpellSchoolMask)e.Event.spellHit.school & spellInfo.SchoolMask) != spellInfo.SchoolMask) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses Spell entry {e.Event.spellHit.spell} with invalid school mask, skipped."); return false; } } if (!IsMinMaxValid(e, e.Event.spellHit.cooldownMin, e.Event.spellHit.cooldownMax)) return false; break; case SmartEvents.OnSpellCast: case SmartEvents.OnSpellFailed: case SmartEvents.OnSpellStart: { if (!IsSpellValid(e, e.Event.spellCast.spell)) return false; if (!IsMinMaxValid(e, e.Event.spellCast.cooldownMin, e.Event.spellCast.cooldownMax)) return false; break; } case SmartEvents.OocLos: case SmartEvents.IcLos: if (!IsMinMaxValid(e, e.Event.los.cooldownMin, e.Event.los.cooldownMax)) return false; if (e.Event.los.hostilityMode >= (uint)LOSHostilityMode.End) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses hostilityMode with invalid value {e.Event.los.hostilityMode} (max allowed value {LOSHostilityMode.End - 1}), skipped."); return false; } TC_SAI_IS_BOOLEAN_VALID(e, e.Event.los.playerOnly); break; case SmartEvents.Respawn: if (e.Event.respawn.type == (uint)SmartRespawnCondition.Map && CliDB.MapStorage.LookupByKey(e.Event.respawn.map) == null) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Map entry {e.Event.respawn.map}, skipped."); return false; } if (e.Event.respawn.type == (uint)SmartRespawnCondition.Area && !CliDB.AreaTableStorage.ContainsKey(e.Event.respawn.area)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Area entry {e.Event.respawn.area}, skipped."); return false; } break; case SmartEvents.FriendlyIsCc: if (!IsMinMaxValid(e, e.Event.friendlyCC.repeatMin, e.Event.friendlyCC.repeatMax)) return false; break; case SmartEvents.FriendlyMissingBuff: { if (!IsSpellValid(e, e.Event.missingBuff.spell)) return false; if (!NotNULL(e, e.Event.missingBuff.radius)) return false; if (!IsMinMaxValid(e, e.Event.missingBuff.repeatMin, e.Event.missingBuff.repeatMax)) return false; break; } case SmartEvents.Kill: if (!IsMinMaxValid(e, e.Event.kill.cooldownMin, e.Event.kill.cooldownMax)) return false; if (e.Event.kill.creature != 0 && !IsCreatureValid(e, e.Event.kill.creature)) return false; TC_SAI_IS_BOOLEAN_VALID(e, e.Event.kill.playerOnly); break; case SmartEvents.VictimCasting: if (e.Event.targetCasting.spellId > 0 && !Global.SpellMgr.HasSpellInfo(e.Event.targetCasting.spellId, Difficulty.None)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.EventId} Action {e.GetActionType()} uses non-existent Spell entry {e.Event.spellHit.spell}, skipped."); return false; } if (!IsMinMaxValid(e, e.Event.minMax.repeatMin, e.Event.minMax.repeatMax)) return false; break; case SmartEvents.PassengerBoarded: case SmartEvents.PassengerRemoved: if (!IsMinMaxValid(e, e.Event.minMax.repeatMin, e.Event.minMax.repeatMax)) return false; break; case SmartEvents.SummonDespawned: case SmartEvents.SummonedUnit: case SmartEvents.SummonedUnitDies: if (e.Event.summoned.creature != 0 && !IsCreatureValid(e, e.Event.summoned.creature)) return false; if (!IsMinMaxValid(e, e.Event.summoned.cooldownMin, e.Event.summoned.cooldownMax)) return false; break; case SmartEvents.AcceptedQuest: case SmartEvents.RewardQuest: if (e.Event.quest.questId != 0 && !IsQuestValid(e, e.Event.quest.questId)) return false; if (!IsMinMaxValid(e, e.Event.quest.cooldownMin, e.Event.quest.cooldownMax)) return false; break; case SmartEvents.ReceiveEmote: { if (e.Event.emote.emoteId != 0 && !IsTextEmoteValid(e, e.Event.emote.emoteId)) return false; if (!IsMinMaxValid(e, e.Event.emote.cooldownMin, e.Event.emote.cooldownMax)) return false; break; } case SmartEvents.HasAura: case SmartEvents.TargetBuffed: { if (!IsSpellValid(e, e.Event.aura.spell)) return false; if (!IsMinMaxValid(e, e.Event.aura.repeatMin, e.Event.aura.repeatMax)) return false; break; } case SmartEvents.TransportAddcreature: { if (e.Event.transportAddCreature.creature != 0 && !IsCreatureValid(e, e.Event.transportAddCreature.creature)) return false; break; } case SmartEvents.Movementinform: { if (MotionMaster.IsInvalidMovementGeneratorType((MovementGeneratorType)e.Event.movementInform.type)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses invalid Motion type {e.Event.movementInform.type}, skipped."); return false; } break; } case SmartEvents.DataSet: { if (!IsMinMaxValid(e, e.Event.dataSet.cooldownMin, e.Event.dataSet.cooldownMax)) return false; break; } case SmartEvents.AreatriggerOntrigger: { if (e.Event.areatrigger.id != 0 && (e.GetScriptType() == SmartScriptType.AreaTriggerEntity || e.GetScriptType() == SmartScriptType.AreaTriggerEntityCustom)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.EventId} Action {e.GetActionType()} areatrigger param not supported for SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY and SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY_CUSTOM, skipped."); return false; } if (e.Event.areatrigger.id != 0 && !IsAreaTriggerValid(e, e.Event.areatrigger.id)) return false; break; } case SmartEvents.TextOver: { if (!IsTextValid(e, e.Event.textOver.textGroupID)) return false; break; } case SmartEvents.GameEventStart: case SmartEvents.GameEventEnd: { var events = Global.GameEventMgr.GetEventMap(); if (e.Event.gameEvent.gameEventId >= events.Length || !events[e.Event.gameEvent.gameEventId].IsValid()) return false; break; } case SmartEvents.FriendlyHealthPCT: if (!IsMinMaxValid(e, e.Event.friendlyHealthPct.repeatMin, e.Event.friendlyHealthPct.repeatMax)) return false; if (e.Event.friendlyHealthPct.maxHpPct > 100 || e.Event.friendlyHealthPct.minHpPct > 100) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} has pct value above 100, skipped."); return false; } switch (e.GetTargetType()) { case SmartTargets.CreatureRange: case SmartTargets.CreatureGuid: case SmartTargets.CreatureDistance: case SmartTargets.ClosestCreature: case SmartTargets.ClosestPlayer: case SmartTargets.PlayerRange: case SmartTargets.PlayerDistance: break; case SmartTargets.ActionInvoker: if (!NotNULL(e, e.Event.friendlyHealthPct.radius)) return false; break; default: Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses invalid target_type {e.GetTargetType()}, skipped."); return false; } break; case SmartEvents.DistanceCreature: if (e.Event.distance.guid == 0 && e.Event.distance.entry == 0) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_CREATURE did not provide creature guid or entry, skipped."); return false; } if (e.Event.distance.guid != 0 && e.Event.distance.entry != 0) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_CREATURE provided both an entry and guid, skipped."); return false; } if (e.Event.distance.guid != 0 && Global.ObjectMgr.GetCreatureData(e.Event.distance.guid) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_CREATURE using invalid creature guid {0}, skipped.", e.Event.distance.guid); return false; } if (e.Event.distance.entry != 0 && Global.ObjectMgr.GetCreatureTemplate(e.Event.distance.entry) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_CREATURE using invalid creature entry {0}, skipped.", e.Event.distance.entry); return false; } break; case SmartEvents.DistanceGameobject: if (e.Event.distance.guid == 0 && e.Event.distance.entry == 0) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT did not provide gameobject guid or entry, skipped."); return false; } if (e.Event.distance.guid != 0 && e.Event.distance.entry != 0) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT provided both an entry and guid, skipped."); return false; } if (e.Event.distance.guid != 0 && Global.ObjectMgr.GetGameObjectData(e.Event.distance.guid) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT using invalid gameobject guid {0}, skipped.", e.Event.distance.guid); return false; } if (e.Event.distance.entry != 0 && Global.ObjectMgr.GetGameObjectTemplate(e.Event.distance.entry) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT using invalid gameobject entry {0}, skipped.", e.Event.distance.entry); return false; } break; case SmartEvents.CounterSet: if (!IsMinMaxValid(e, e.Event.counter.cooldownMin, e.Event.counter.cooldownMax)) return false; if (e.Event.counter.id == 0) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_COUNTER_SET using invalid counter id {0}, skipped.", e.Event.counter.id); return false; } if (e.Event.counter.value == 0) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_COUNTER_SET using invalid value {0}, skipped.", e.Event.counter.value); return false; } break; case SmartEvents.Reset: if (e.Action.type == SmartActions.CallScriptReset) { // There might be SMART_TARGET_* cases where this should be allowed, they will be handled if needed Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses event SMART_EVENT_RESET and action SMART_ACTION_CALL_SCRIPT_RESET, skipped."); return false; } break; case SmartEvents.Charmed: TC_SAI_IS_BOOLEAN_VALID(e, e.Event.charm.onRemove); break; case SmartEvents.QuestObjCompletion: if (Global.ObjectMgr.GetQuestObjective(e.Event.questObjective.id) == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr: Event SMART_EVENT_QUEST_OBJ_COMPLETION using invalid objective id {e.Event.questObjective.id}, skipped."); return false; } break; case SmartEvents.QuestAccepted: case SmartEvents.QuestCompletion: case SmartEvents.QuestFail: case SmartEvents.QuestRewarded: break; case SmartEvents.Link: case SmartEvents.GoLootStateChanged: case SmartEvents.GoEventInform: case SmartEvents.TimedEventTriggered: case SmartEvents.InstancePlayerEnter: case SmartEvents.TransportRelocate: case SmartEvents.CorpseRemoved: case SmartEvents.AiInit: case SmartEvents.ActionDone: case SmartEvents.TransportAddplayer: case SmartEvents.TransportRemovePlayer: case SmartEvents.Aggro: case SmartEvents.Death: case SmartEvents.Evade: case SmartEvents.ReachedHome: case SmartEvents.JustSummoned: case SmartEvents.WaypointReached: case SmartEvents.WaypointPaused: case SmartEvents.WaypointResumed: case SmartEvents.WaypointStopped: case SmartEvents.WaypointEnded: case SmartEvents.GossipSelect: case SmartEvents.GossipHello: case SmartEvents.JustCreated: case SmartEvents.FollowCompleted: case SmartEvents.OnSpellclick: case SmartEvents.OnDespawn: case SmartEvents.SceneStart: case SmartEvents.SceneCancel: case SmartEvents.SceneComplete: case SmartEvents.SceneTrigger: case SmartEvents.SendEventTrigger: break; //Unused case SmartEvents.TargetHealthPct: case SmartEvents.FriendlyHealth: case SmartEvents.TargetManaPct: case SmartEvents.CharmedTarget: case SmartEvents.WaypointStart: case SmartEvents.PhaseChange: case SmartEvents.IsBehindTarget: Log.outError(LogFilter.Sql, $"SmartAIMgr: Unused event_type {e} skipped."); return false; default: Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled event_type({0}), Entry {1} SourceType {2} Event {3} Action {4}, skipped.", e.GetEventType(), e.EntryOrGuid, e.GetScriptType(), e.EventId, e.GetActionType()); return false; } if (e.Event.event_flags.HasAnyFlag(SmartEventFlags.ActionlistWaits)) { Log.outError(LogFilter.Sql, "SmartAIMgr: {e}, uses SMART_EVENT_FLAG_ACTIONLIST_WAITS but is not part of a timed actionlist."); return false; } } if (!CheckUnusedEventParams(e)) return false; switch (e.GetActionType()) { case SmartActions.Talk: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.talk.useTalkTarget); if (!IsTextValid(e, e.Action.talk.textGroupId)) return false; break; } case SmartActions.SimpleTalk: { if (!IsTextValid(e, e.Action.simpleTalk.textGroupId)) return false; break; } case SmartActions.SetFaction: if (e.Action.faction.factionId != 0 && CliDB.FactionTemplateStorage.LookupByKey(e.Action.faction.factionId) == null) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Faction {e.Action.faction.factionId}, skipped."); return false; } break; case SmartActions.MorphToEntryOrModel: case SmartActions.MountToEntryOrModel: if (e.Action.morphOrMount.creature != 0 || e.Action.morphOrMount.model != 0) { if (e.Action.morphOrMount.creature > 0 && Global.ObjectMgr.GetCreatureTemplate(e.Action.morphOrMount.creature) == null) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Creature entry {e.Action.morphOrMount.creature}, skipped."); return false; } if (e.Action.morphOrMount.model != 0) { if (e.Action.morphOrMount.creature != 0) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} has ModelID set with also set CreatureId, skipped."); return false; } else if (!CliDB.CreatureDisplayInfoStorage.ContainsKey(e.Action.morphOrMount.model)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Model id {e.Action.morphOrMount.model}, skipped."); return false; } } } break; case SmartActions.Sound: if (!IsSoundValid(e, e.Action.sound.soundId)) return false; TC_SAI_IS_BOOLEAN_VALID(e, e.Action.sound.onlySelf); break; case SmartActions.SetEmoteState: case SmartActions.PlayEmote: if (!IsEmoteValid(e, e.Action.emote.emoteId)) return false; break; case SmartActions.PlayAnimkit: if (e.Action.animKit.animKit != 0 && !IsAnimKitValid(e, e.Action.animKit.animKit)) return false; if (e.Action.animKit.type > 3) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses invalid AnimKit type {e.Action.animKit.type}, skipped."); return false; } break; case SmartActions.PlaySpellVisualKit: if (e.Action.spellVisualKit.spellVisualKitId != 0 && !IsSpellVisualKitValid(e, e.Action.spellVisualKit.spellVisualKitId)) return false; break; case SmartActions.OfferQuest: if (!IsQuestValid(e, e.Action.questOffer.questId)) return false; TC_SAI_IS_BOOLEAN_VALID(e, e.Action.questOffer.directAdd); break; case SmartActions.FailQuest: if (!IsQuestValid(e, e.Action.quest.questId)) return false; break; case SmartActions.ActivateTaxi: { if (!CliDB.TaxiPathStorage.ContainsKey(e.Action.taxi.id)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses invalid Taxi path ID {e.Action.taxi.id}, skipped."); return false; } break; } case SmartActions.RandomEmote: if (e.Action.randomEmote.emote1 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote1)) return false; if (e.Action.randomEmote.emote2 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote2)) return false; if (e.Action.randomEmote.emote3 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote3)) return false; if (e.Action.randomEmote.emote4 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote4)) return false; if (e.Action.randomEmote.emote5 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote5)) return false; if (e.Action.randomEmote.emote6 != 0 && !IsEmoteValid(e, e.Action.randomEmote.emote6)) return false; break; case SmartActions.RandomSound: if (e.Action.randomSound.sound1 != 0 && !IsSoundValid(e, e.Action.randomSound.sound1)) return false; if (e.Action.randomSound.sound2 != 0 && !IsSoundValid(e, e.Action.randomSound.sound2)) return false; if (e.Action.randomSound.sound3 != 0 && !IsSoundValid(e, e.Action.randomSound.sound3)) return false; if (e.Action.randomSound.sound4 != 0 && !IsSoundValid(e, e.Action.randomSound.sound4)) return false; TC_SAI_IS_BOOLEAN_VALID(e, e.Action.randomSound.onlySelf); break; case SmartActions.Cast: { if (!IsSpellValid(e, e.Action.cast.spell)) return false; SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(e.Action.cast.spell, Difficulty.None); foreach (var spellEffectInfo in spellInfo.GetEffects()) { if (spellEffectInfo.IsEffect(SpellEffectName.KillCredit) || spellEffectInfo.IsEffect(SpellEffectName.KillCredit2)) { if (spellEffectInfo.TargetA.GetTarget() == Targets.UnitCaster) Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} Effect: SPELL_EFFECT_KILL_CREDIT: (SpellId: {e.Action.cast.spell} targetA: {spellEffectInfo.TargetA.GetTarget()} - targetB: {spellEffectInfo.TargetB.GetTarget()}) has invalid target for this Action"); } } if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.WaitForHit) && !e.Event.event_flags.HasAnyFlag(SmartEventFlags.ActionlistWaits)) { Log.outError(LogFilter.Sql, "SmartAIMgr: {e} uses SMARTCAST_WAIT_FOR_HIT but is not part of actionlist event that has SMART_EVENT_FLAG_ACTIONLIST_WAITS"); return false; } break; } case SmartActions.CrossCast: { if (!IsSpellValid(e, e.Action.crossCast.spell)) return false; SmartTargets targetType = (SmartTargets)e.Action.crossCast.targetType; if (targetType == SmartTargets.CreatureGuid || targetType == SmartTargets.GameobjectGuid) { if (e.Action.crossCast.targetParam2 != 0) { if (targetType == SmartTargets.CreatureGuid && !IsCreatureValid(e, e.Action.crossCast.targetParam2)) return false; else if (targetType == SmartTargets.GameobjectGuid && !IsGameObjectValid(e, e.Action.crossCast.targetParam2)) return false; } ulong guid = e.Action.crossCast.targetParam1; SpawnObjectType spawnType = targetType == SmartTargets.CreatureGuid ? SpawnObjectType.Creature : SpawnObjectType.GameObject; var data = Global.ObjectMgr.GetSpawnData(spawnType, guid); if (data == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} specifies invalid CasterTargetType guid ({spawnType},{guid})"); return false; } else if (e.Action.crossCast.targetParam2 != 0 && e.Action.crossCast.targetParam2 != data.Id) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} specifies invalid entry {e.Action.crossCast.targetParam2} (expected {data.Id}) for CasterTargetType guid ({spawnType},{guid})"); return false; } } if (e.Action.crossCast.castFlags.HasAnyFlag((uint)SmartCastFlags.WaitForHit) && !e.Event.event_flags.HasAnyFlag(SmartEventFlags.ActionlistWaits)) { Log.outError(LogFilter.Sql, "SmartAIMgr: {e} uses SMARTCAST_WAIT_FOR_HIT but is not part of actionlist event that has SMART_EVENT_FLAG_ACTIONLIST_WAITS"); return false; } break; } case SmartActions.InvokerCast: if (e.GetScriptType() != SmartScriptType.TimedActionlist && e.GetEventType() != SmartEvents.Link && !EventHasInvoker(e.Event.type)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} has invoker cast action, but event does not provide any invoker!"); return false; } if (!IsSpellValid(e, e.Action.cast.spell)) return false; break; case SmartActions.SelfCast: if (!IsSpellValid(e, e.Action.cast.spell)) return false; if (e.Action.cast.castFlags.HasAnyFlag((uint)SmartCastFlags.WaitForHit) && !e.Event.event_flags.HasAnyFlag(SmartEventFlags.ActionlistWaits)) { Log.outError(LogFilter.Sql, "SmartAIMgr: {e} uses SMARTCAST_WAIT_FOR_HIT but is not part of actionlist event that has SMART_EVENT_FLAG_ACTIONLIST_WAITS"); return false; } break; case SmartActions.SetEventPhase: if (e.Action.setEventPhase.phase >= (uint)SmartPhase.Max) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} attempts to set phase {e.Action.setEventPhase.phase}. Phase mask cannot be used past phase {SmartPhase.Max - 1}, skipped."); return false; } break; case SmartActions.IncEventPhase: if (e.Action.incEventPhase.inc == 0 && e.Action.incEventPhase.dec == 0) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} is incrementing phase by 0, skipped."); return false; } else if (e.Action.incEventPhase.inc > (uint)SmartPhase.Max || e.Action.incEventPhase.dec > (uint)SmartPhase.Max) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} attempts to increment phase by too large value, skipped."); return false; } break; case SmartActions.RemoveAurasFromSpell: if (e.Action.removeAura.spell != 0 && !IsSpellValid(e, e.Action.removeAura.spell)) return false; TC_SAI_IS_BOOLEAN_VALID(e, e.Action.removeAura.onlyOwnedAuras); break; case SmartActions.RandomPhase: { if (e.Action.randomPhase.phase1 >= (uint)SmartPhase.Max || e.Action.randomPhase.phase2 >= (uint)SmartPhase.Max || e.Action.randomPhase.phase3 >= (uint)SmartPhase.Max || e.Action.randomPhase.phase4 >= (uint)SmartPhase.Max || e.Action.randomPhase.phase5 >= (uint)SmartPhase.Max || e.Action.randomPhase.phase6 >= (uint)SmartPhase.Max) { Log.outError(LogFilter.Sql, $"SmartAIMgr: Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.EventId} Action {e.GetActionType()} attempts to set invalid phase, skipped."); return false; } break; } case SmartActions.RandomPhaseRange: //PhaseMin, PhaseMax { if (e.Action.randomPhaseRange.phaseMin >= (uint)SmartPhase.Max || e.Action.randomPhaseRange.phaseMax >= (uint)SmartPhase.Max) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} attempts to set invalid phase, skipped."); return false; } if (!IsMinMaxValid(e, e.Action.randomPhaseRange.phaseMin, e.Action.randomPhaseRange.phaseMax)) return false; break; } case SmartActions.SummonCreature: if (!IsCreatureValid(e, e.Action.summonCreature.creature)) return false; if (e.Action.summonCreature.type < (uint)TempSummonType.TimedOrDeadDespawn || e.Action.summonCreature.type > (uint)TempSummonType.ManualDespawn) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses incorrect TempSummonType {e.Action.summonCreature.type}, skipped."); return false; } TC_SAI_IS_BOOLEAN_VALID(e, e.Action.summonCreature.attackInvoker); break; case SmartActions.CallKilledmonster: if (!IsCreatureValid(e, e.Action.killedMonster.creature)) return false; if (e.GetTargetType() == SmartTargets.Position) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses incorrect TargetType {e.GetTargetType()}, skipped."); return false; } break; case SmartActions.UpdateTemplate: if (e.Action.updateTemplate.creature != 0 && !IsCreatureValid(e, e.Action.updateTemplate.creature)) return false; TC_SAI_IS_BOOLEAN_VALID(e, e.Action.updateTemplate.updateLevel); break; case SmartActions.SetSheath: if (e.Action.setSheath.sheath != 0 && e.Action.setSheath.sheath >= (uint)SheathState.Max) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses incorrect Sheath state {e.Action.setSheath.sheath}, skipped."); return false; } break; case SmartActions.SetReactState: { if (e.Action.react.state > (uint)ReactStates.Aggressive) { Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Creature {0} Event {1} Action {2} uses invalid React State {3}, skipped.", e.EntryOrGuid, e.EventId, e.GetActionType(), e.Action.react.state); return false; } break; } case SmartActions.SummonGo: if (!IsGameObjectValid(e, e.Action.summonGO.entry)) return false; break; case SmartActions.RemoveItem: if (!IsItemValid(e, e.Action.item.entry)) return false; if (!NotNULL(e, e.Action.item.count)) return false; break; case SmartActions.AddItem: if (!IsItemValid(e, e.Action.item.entry)) return false; if (!NotNULL(e, e.Action.item.count)) return false; break; case SmartActions.Teleport: if (!CliDB.MapStorage.ContainsKey(e.Action.teleport.mapID)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Map entry {e.Action.teleport.mapID}, skipped."); return false; } break; case SmartActions.WpStop: if (e.Action.wpStop.quest != 0 && !IsQuestValid(e, e.Action.wpStop.quest)) return false; TC_SAI_IS_BOOLEAN_VALID(e, e.Action.wpStop.fail); break; case SmartActions.WpStart: { WaypointPath path = Global.WaypointMgr.GetPath(e.Action.wpStart.pathID); if (path == null || path.Nodes.Empty()) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent WaypointPath id {e.Action.wpStart.pathID}, skipped."); return false; } if (e.Action.wpStart.quest != 0 && !IsQuestValid(e, e.Action.wpStart.quest)) return false; TC_SAI_IS_BOOLEAN_VALID(e, e.Action.wpStart.run); TC_SAI_IS_BOOLEAN_VALID(e, e.Action.wpStart.repeat); break; } case SmartActions.CreateTimedEvent: { if (!IsMinMaxValid(e, e.Action.timeEvent.min, e.Action.timeEvent.max)) return false; if (!IsMinMaxValid(e, e.Action.timeEvent.repeatMin, e.Action.timeEvent.repeatMax)) return false; break; } case SmartActions.CallRandomRangeTimedActionlist: { if (!IsMinMaxValid(e, e.Action.randRangeTimedActionList.idMin, e.Action.randRangeTimedActionList.idMax)) return false; break; } case SmartActions.SetPower: case SmartActions.AddPower: case SmartActions.RemovePower: if (e.Action.power.powerType > (int)PowerType.Max) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent Power {e.Action.power.powerType}, skipped."); return false; } break; case SmartActions.GameEventStop: { uint eventId = e.Action.gameEventStop.id; var events = Global.GameEventMgr.GetEventMap(); if (eventId < 1 || eventId >= events.Length) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent event, eventId {e.Action.gameEventStop.id}, skipped."); return false; } GameEventData eventData = events[eventId]; if (!eventData.IsValid()) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent event, eventId {e.Action.gameEventStop.id}, skipped."); return false; } break; } case SmartActions.GameEventStart: { uint eventId = e.Action.gameEventStart.id; var events = Global.GameEventMgr.GetEventMap(); if (eventId < 1 || eventId >= events.Length) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent event, eventId {e.Action.gameEventStart.id}, skipped."); return false; } GameEventData eventData = events[eventId]; if (!eventData.IsValid()) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent event, eventId {e.Action.gameEventStart.id}, skipped."); return false; } break; } case SmartActions.Equip: { if (e.GetScriptType() == SmartScriptType.Creature) { sbyte equipId = (sbyte)e.Action.equip.entry; if (equipId != 0 && Global.ObjectMgr.GetEquipmentInfo((uint)e.EntryOrGuid, equipId) == null) { Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_EQUIP uses non-existent equipment info id {0} for creature {1}, skipped.", equipId, e.EntryOrGuid); return false; } } bool isValidEquipmentSlot(uint itemEntry, byte slot) { ItemRecord dbcItem = CliDB.ItemStorage.LookupByKey(itemEntry); if (dbcItem == null) { Log.outError(LogFilter.Sql, $"SmartScript: SMART_ACTION_EQUIP uses unknown item {itemEntry} (slot {slot}) for creature {e.EntryOrGuid}, skipped."); return false; } if (ItemConst.InventoryTypesEquipable.All(inventoryType => inventoryType != dbcItem.inventoryType)) { Log.outError(LogFilter.Sql, $"SmartScript: SMART_ACTION_EQUIP uses item {itemEntry} (slot {slot}) not equipable in a hand for creature {e.EntryOrGuid}, skipped."); return false; } return true; }; if (e.Action.equip.slot1 != 0 && !isValidEquipmentSlot(e.Action.equip.slot1, 0)) return false; if (e.Action.equip.slot2 != 0 && !isValidEquipmentSlot(e.Action.equip.slot2, 1)) return false; if (e.Action.equip.slot3 != 0 && !isValidEquipmentSlot(e.Action.equip.slot3, 2)) return false; break; } case SmartActions.SetInstData: { if (e.Action.setInstanceData.type > 1) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses invalid data type {e.Action.setInstanceData.type} (value range 0-1), skipped."); return false; } else if (e.Action.setInstanceData.type == 1) { if (e.Action.setInstanceData.data > (int)EncounterState.ToBeDecided) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses invalid boss state {e.Action.setInstanceData.data} (value range 0-5), skipped."); return false; } } break; } case SmartActions.SetIngamePhaseId: { uint phaseId = e.Action.ingamePhaseId.id; uint apply = e.Action.ingamePhaseId.apply; if (apply != 0 && apply != 1) { Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SET_INGAME_PHASE_ID uses invalid apply value {0} (Should be 0 or 1) for creature {1}, skipped", apply, e.EntryOrGuid); return false; } if (!CliDB.PhaseStorage.ContainsKey(phaseId)) { Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SET_INGAME_PHASE_ID uses invalid phaseid {0} for creature {1}, skipped", phaseId, e.EntryOrGuid); return false; } break; } case SmartActions.SetIngamePhaseGroup: { uint phaseGroup = e.Action.ingamePhaseGroup.groupId; uint apply = e.Action.ingamePhaseGroup.apply; if (apply != 0 && apply != 1) { Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SET_INGAME_PHASE_GROUP uses invalid apply value {0} (Should be 0 or 1) for creature {1}, skipped", apply, e.EntryOrGuid); return false; } if (Global.DB2Mgr.GetPhasesForGroup(phaseGroup).Empty()) { Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SET_INGAME_PHASE_GROUP uses invalid phase group id {0} for creature {1}, skipped", phaseGroup, e.EntryOrGuid); return false; } break; } case SmartActions.ScenePlay: { if (Global.ObjectMgr.GetSceneTemplate(e.Action.scene.sceneId) == null) { Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SCENE_PLAY uses sceneId {0} but scene don't exist, skipped", e.Action.scene.sceneId); return false; } break; } case SmartActions.SceneCancel: { if (Global.ObjectMgr.GetSceneTemplate(e.Action.scene.sceneId) == null) { Log.outError(LogFilter.Sql, "SmartScript: SMART_ACTION_SCENE_CANCEL uses sceneId {0} but scene don't exist, skipped", e.Action.scene.sceneId); return false; } break; } case SmartActions.RespawnBySpawnId: { if (Global.ObjectMgr.GetSpawnData((SpawnObjectType)e.Action.respawnData.spawnType, e.Action.respawnData.spawnId) == null) { Log.outError(LogFilter.Sql, $"Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.EventId} Action {e.GetActionType()} specifies invalid spawn data ({e.Action.respawnData.spawnType},{e.Action.respawnData.spawnId})"); return false; } break; } case SmartActions.EnableTempGobj: { if (e.Action.enableTempGO.duration == 0) { Log.outError(LogFilter.Sql, $"Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.EventId} Action {e.GetActionType()} does not specify duration"); return false; } break; } case SmartActions.PlayCinematic: { if (!CliDB.CinematicSequencesStorage.ContainsKey(e.Action.cinematic.entry)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: SMART_ACTION_PLAY_CINEMATIC {e} uses invalid entry {e.Action.cinematic.entry}, skipped."); return false; } break; } case SmartActions.PauseMovement: { if (e.Action.pauseMovement.pauseTimer == 0) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} does not specify pause duration"); return false; } TC_SAI_IS_BOOLEAN_VALID(e, e.Action.pauseMovement.force); break; } case SmartActions.SetMovementSpeed: { if (e.Action.movementSpeed.movementType >= (int)MovementGeneratorType.Max) { Log.outError(LogFilter.Sql, $"SmartAIMgr: Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.EventId} Action {e.GetActionType()} uses invalid movementType {e.Action.movementSpeed.movementType}, skipped."); return false; } if (e.Action.movementSpeed.speedInteger == 0 && e.Action.movementSpeed.speedFraction == 0) { Log.outError(LogFilter.Sql, $"SmartAIMgr: Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.EventId} Action {e.GetActionType()} uses speed 0, skipped."); return false; } break; } case SmartActions.OverrideLight: { var areaEntry = CliDB.AreaTableStorage.LookupByKey(e.Action.overrideLight.zoneId); if (areaEntry == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent zoneId {e.Action.overrideLight.zoneId}, skipped."); return false; } if (areaEntry.ParentAreaID != 0 && areaEntry.HasFlag(AreaFlags.IsSubzone)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses subzone (ID: {e.Action.overrideLight.zoneId}) instead of zone, skipped."); return false; } if (!CliDB.LightStorage.ContainsKey(e.Action.overrideLight.areaLightId)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent areaLightId {e.Action.overrideLight.areaLightId}, skipped."); return false; } if (e.Action.overrideLight.overrideLightId != 0 && !CliDB.LightStorage.ContainsKey(e.Action.overrideLight.overrideLightId)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent overrideLightId {e.Action.overrideLight.overrideLightId}, skipped."); return false; } break; } case SmartActions.OverrideWeather: { var areaEntry = CliDB.AreaTableStorage.LookupByKey(e.Action.overrideWeather.zoneId); if (areaEntry == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent zoneId {e.Action.overrideWeather.zoneId}, skipped."); return false; } if (areaEntry.ParentAreaID != 0 && areaEntry.HasFlag(AreaFlags.IsSubzone)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses subzone (ID: {e.Action.overrideWeather.zoneId}) instead of zone, skipped."); return false; } break; } case SmartActions.SetAIAnimKit: { Log.outError(LogFilter.Sql, $"SmartAIMgr: Deprecated Event:({e}) skipped."); break; } case SmartActions.SetHealthPct: { if (e.Action.setHealthPct.percent > 100 || e.Action.setHealthPct.percent == 0) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} is trying to set invalid HP percent {e.Action.setHealthPct.percent}, skipped."); return false; } break; } case SmartActions.AutoAttack: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.autoAttack.attack); break; } case SmartActions.AllowCombatMovement: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.combatMove.move); break; } case SmartActions.CallForHelp: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.callHelp.withEmote); break; } case SmartActions.SetVisibility: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.visibility.state); break; } case SmartActions.SetActive: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.active.state); break; } case SmartActions.SetRun: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setRun.run); break; } case SmartActions.SetDisableGravity: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setDisableGravity.disable); break; } case SmartActions.SetCounter: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setCounter.reset); break; } case SmartActions.CallTimedActionlist: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.timedActionList.allowOverride); break; } case SmartActions.InterruptSpell: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.interruptSpellCasting.withDelayed); TC_SAI_IS_BOOLEAN_VALID(e, e.Action.interruptSpellCasting.withInstant); break; } case SmartActions.FleeForAssist: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.fleeAssist.withEmote); break; } case SmartActions.MoveToPos: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.moveToPos.transport); TC_SAI_IS_BOOLEAN_VALID(e, e.Action.moveToPos.disablePathfinding); break; } case SmartActions.SetRoot: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setRoot.root); break; } case SmartActions.DisableEvade: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.disableEvade.disable); break; } case SmartActions.LoadEquipment: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.loadEquipment.force); break; } case SmartActions.SetHover: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setHover.enable); break; } case SmartActions.Evade: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.evade.toRespawnPosition); break; } case SmartActions.SetHealthRegen: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setHealthRegen.regenHealth); break; } case SmartActions.CreateConversation: { if (Global.ConversationDataStorage.GetConversationTemplate(e.Action.conversation.id) == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr: SMART_ACTION_CREATE_CONVERSATION Entry {e.EntryOrGuid} SourceType {e.GetScriptType()} Event {e.EventId} Action {e.GetActionType()} uses invalid entry {e.Action.conversation.id}, skipped."); return false; } break; } case SmartActions.SetImmunePC: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setImmunePC.immunePC); break; } case SmartActions.SetImmuneNPC: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setImmuneNPC.immuneNPC); break; } case SmartActions.SetUninteractible: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setUninteractible.uninteractible); break; } case SmartActions.ActivateGameobject: { if (!NotNULL(e, e.Action.activateGameObject.gameObjectAction)) return false; if (e.Action.activateGameObject.gameObjectAction >= (uint)GameObjectActions.Max) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} has gameObjectAction parameter out of range (max allowed {(uint)GameObjectActions.Max - 1}, current value {e.Action.activateGameObject.gameObjectAction}), skipped."); return false; } break; } case SmartActions.StartClosestWaypoint: case SmartActions.Follow: case SmartActions.SetOrientation: case SmartActions.StoreTargetList: case SmartActions.CombatStop: case SmartActions.Die: case SmartActions.SetInCombatWithZone: case SmartActions.WpResume: case SmartActions.KillUnit: case SmartActions.SetInvincibilityHpLevel: case SmartActions.ResetGobject: case SmartActions.AttackStart: case SmartActions.ThreatAllPct: case SmartActions.ThreatSinglePct: case SmartActions.SetInstData64: case SmartActions.SetData: case SmartActions.AttackStop: case SmartActions.WpPause: case SmartActions.ForceDespawn: case SmartActions.Playmovie: case SmartActions.CloseGossip: case SmartActions.TriggerTimedEvent: case SmartActions.RemoveTimedEvent: case SmartActions.ActivateGobject: case SmartActions.CallScriptReset: case SmartActions.SetRangedMovement: case SmartActions.SetNpcFlag: case SmartActions.AddNpcFlag: case SmartActions.RemoveNpcFlag: case SmartActions.CallRandomTimedActionlist: case SmartActions.RandomMove: case SmartActions.SetUnitFieldBytes1: case SmartActions.RemoveUnitFieldBytes1: case SmartActions.JumpToPos: case SmartActions.SendGossipMenu: case SmartActions.GoSetLootState: case SmartActions.GoSetGoState: case SmartActions.SendTargetToTarget: case SmartActions.SetHomePos: case SmartActions.SummonCreatureGroup: case SmartActions.MoveOffset: case SmartActions.SetCorpseDelay: case SmartActions.AddThreat: case SmartActions.TriggerRandomTimedEvent: case SmartActions.SpawnSpawngroup: case SmartActions.AddToStoredTargetList: case SmartActions.DoAction: break; case SmartActions.BecomePersonalCloneForPlayer: { if (e.Action.becomePersonalClone.type < (uint)TempSummonType.TimedOrDeadDespawn || e.Action.becomePersonalClone.type > (uint)TempSummonType.ManualDespawn) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses incorrect TempSummonType {e.Action.becomePersonalClone.type}, skipped."); return false; } break; } case SmartActions.TriggerGameEvent: { TC_SAI_IS_BOOLEAN_VALID(e, e.Action.triggerGameEvent.useSaiTargetAsGameEventSource); break; } case SmartActions.CompleteQuest: Quest quest = Global.ObjectMgr.GetQuestTemplate(e.Action.quest.questId); if (quest != null) { if (!quest.HasFlag(QuestFlags.CompletionEvent) && !quest.HasFlag(QuestFlags.CompletionAreaTrigger) && !quest.HasFlag(QuestFlags.TrackingEvent)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} Flags for Quest entry {e.Action.quest.questId} does not include QUEST_FLAGS_COMPLETION_EVENT or QUEST_FLAGS_COMPLETION_AREA_TRIGGER or QUEST_FLAGS_TRACKING_EVENT, skipped."); return false; } } else { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent Quest entry {e.Action.quest.questId}, skipped."); return false; } break; // No longer supported case SmartActions.CallAreaexploredoreventhappens: case SmartActions.CallGroupeventhappens: case SmartActions.SetUnitFlag: case SmartActions.RemoveUnitFlag: case SmartActions.InstallAITemplate: case SmartActions.SetSwim: case SmartActions.AddAura: case SmartActions.OverrideScriptBaseObject: case SmartActions.ResetScriptBaseObject: case SmartActions.SendGoCustomAnim: case SmartActions.SetDynamicFlag: case SmartActions.AddDynamicFlag: case SmartActions.RemoveDynamicFlag: case SmartActions.SetGoFlag: case SmartActions.AddGoFlag: case SmartActions.RemoveGoFlag: case SmartActions.SetCanFly: case SmartActions.RemoveAurasByType: case SmartActions.SetSightDist: case SmartActions.Flee: case SmartActions.RemoveAllGameobjects: Log.outError(LogFilter.Sql, $"SmartAIMgr: Unused action_type: {e} Skipped."); return false; default: Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled action_type({0}), event_type({1}), Entry {2} SourceType {3} Event {4}, skipped.", e.GetActionType(), e.GetEventType(), e.EntryOrGuid, e.GetScriptType(), e.EventId); return false; } if (!CheckUnusedActionParams(e)) return false; return true; } static bool IsAnimKitValid(SmartScriptHolder e, uint entry) { if (!CliDB.AnimKitStorage.ContainsKey(entry)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses non-existent AnimKit entry {entry}, skipped."); return false; } return true; } static bool IsTextValid(SmartScriptHolder e, uint id) { if (e.GetScriptType() != SmartScriptType.Creature) return true; uint entry; if (e.GetEventType() == SmartEvents.TextOver) { entry = e.Event.textOver.creatureEntry; } else { switch (e.GetTargetType()) { case SmartTargets.CreatureDistance: case SmartTargets.CreatureRange: case SmartTargets.ClosestCreature: return true; // ignore default: if (e.EntryOrGuid < 0) { ulong guid = (ulong)-e.EntryOrGuid; CreatureData data = Global.ObjectMgr.GetCreatureData(guid); if (data == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} using non-existent Creature guid {guid}, skipped."); return false; } else entry = data.Id; } else entry = (uint)e.EntryOrGuid; break; } } if (entry == 0 || !Global.CreatureTextMgr.TextExist(entry, (byte)id)) { Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} using non-existent Text id {id}, skipped."); return false; } return true; } static bool IsCreatureValid(SmartScriptHolder e, uint entry) { if (Global.ObjectMgr.GetCreatureTemplate(entry) == null) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Creature entry {entry}, skipped."); return false; } return true; } static bool IsGameObjectValid(SmartScriptHolder e, uint entry) { if (Global.ObjectMgr.GetGameObjectTemplate(entry) == null) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent GameObject entry {entry}, skipped."); return false; } return true; } static bool IsQuestValid(SmartScriptHolder e, uint entry) { if (Global.ObjectMgr.GetQuestTemplate(entry) == null) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Quest entry {entry}, skipped."); return false; } return true; } static bool IsSpellValid(SmartScriptHolder e, uint entry) { if (!Global.SpellMgr.HasSpellInfo(entry, Difficulty.None)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Spell entry {entry}, skipped."); return false; } return true; } static bool IsMinMaxValid(SmartScriptHolder e, uint min, uint max) { if (max < min) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses min/max params wrong ({min}/{max}), skipped."); return false; } return true; } static bool NotNULL(SmartScriptHolder e, uint data) { if (data == 0) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} Parameter can not be NULL, skipped."); return false; } return true; } static bool IsEmoteValid(SmartScriptHolder e, uint entry) { if (!CliDB.EmotesStorage.ContainsKey(entry)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Emote entry {entry}, skipped."); return false; } return true; } static bool IsItemValid(SmartScriptHolder e, uint entry) { if (!CliDB.ItemSparseStorage.ContainsKey(entry)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Item entry {entry}, skipped."); return false; } return true; } static bool IsTextEmoteValid(SmartScriptHolder e, uint entry) { if (!CliDB.EmotesTextStorage.ContainsKey(entry)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Text Emote entry {entry}, skipped."); return false; } return true; } static bool IsAreaTriggerValid(SmartScriptHolder e, uint entry) { if (!CliDB.AreaTriggerStorage.ContainsKey(entry)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent AreaTrigger entry {entry}, skipped."); return false; } return true; } static bool IsSoundValid(SmartScriptHolder e, uint entry) { if (!CliDB.SoundKitStorage.ContainsKey(entry)) { Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent Sound entry {entry}, skipped."); return false; } return true; } public List GetScript(int entry, SmartScriptType type) { List temp = new(); if (_eventMap[(uint)type].ContainsKey(entry)) { foreach (var holder in _eventMap[(uint)type][entry]) temp.Add(new SmartScriptHolder(holder)); } else { if (entry > 0)//first search is for guid (negative), do not drop error if not found Log.outDebug(LogFilter.ScriptsAi, "SmartAIMgr.GetScript: Could not load Script for Entry {0} ScriptType {1}.", entry, type); } return temp; } public static SmartScriptHolder FindLinkedSourceEvent(List list, uint eventId) { var sch = list.Find(p => p.Link == eventId); if (sch != null) return sch; return null; } public SmartScriptHolder FindLinkedEvent(List list, uint link) { var sch = list.Find(p => p.EventId == link && p.GetEventType() == SmartEvents.Link); if (sch != null) return sch; return null; } public static uint GetTypeMask(SmartScriptType smartScriptType) => smartScriptType switch { SmartScriptType.Creature => SmartScriptTypeMaskId.Creature, SmartScriptType.GameObject => SmartScriptTypeMaskId.Gameobject, SmartScriptType.AreaTrigger => SmartScriptTypeMaskId.Areatrigger, SmartScriptType.Event => SmartScriptTypeMaskId.Event, SmartScriptType.Gossip => SmartScriptTypeMaskId.Gossip, SmartScriptType.Quest => SmartScriptTypeMaskId.Quest, SmartScriptType.Spell => SmartScriptTypeMaskId.Spell, SmartScriptType.Transport => SmartScriptTypeMaskId.Transport, SmartScriptType.Instance => SmartScriptTypeMaskId.Instance, SmartScriptType.TimedActionlist => SmartScriptTypeMaskId.TimedActionlist, SmartScriptType.Scene => SmartScriptTypeMaskId.Scene, SmartScriptType.AreaTriggerEntity => SmartScriptTypeMaskId.AreatrigggerEntity, SmartScriptType.AreaTriggerEntityCustom => SmartScriptTypeMaskId.AreatrigggerEntity, _ => 0, }; public static uint GetEventMask(SmartEvents smartEvent) => smartEvent switch { SmartEvents.UpdateIc => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.TimedActionlist, SmartEvents.UpdateOoc => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject + SmartScriptTypeMaskId.Instance + SmartScriptTypeMaskId.AreatrigggerEntity, SmartEvents.HealthPct => SmartScriptTypeMaskId.Creature, SmartEvents.ManaPct => SmartScriptTypeMaskId.Creature, SmartEvents.Aggro => SmartScriptTypeMaskId.Creature, SmartEvents.Kill => SmartScriptTypeMaskId.Creature, SmartEvents.Death => SmartScriptTypeMaskId.Creature, SmartEvents.Evade => SmartScriptTypeMaskId.Creature, SmartEvents.SpellHit => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.Range => SmartScriptTypeMaskId.Creature, SmartEvents.OocLos => SmartScriptTypeMaskId.Creature, SmartEvents.Respawn => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.TargetHealthPct => SmartScriptTypeMaskId.Creature, SmartEvents.VictimCasting => SmartScriptTypeMaskId.Creature, SmartEvents.FriendlyHealth => SmartScriptTypeMaskId.Creature, SmartEvents.FriendlyIsCc => SmartScriptTypeMaskId.Creature, SmartEvents.FriendlyMissingBuff => SmartScriptTypeMaskId.Creature, SmartEvents.SummonedUnit => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.TargetManaPct => SmartScriptTypeMaskId.Creature, SmartEvents.AcceptedQuest => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.RewardQuest => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.ReachedHome => SmartScriptTypeMaskId.Creature, SmartEvents.ReceiveEmote => SmartScriptTypeMaskId.Creature, SmartEvents.HasAura => SmartScriptTypeMaskId.Creature, SmartEvents.TargetBuffed => SmartScriptTypeMaskId.Creature, SmartEvents.Reset => SmartScriptTypeMaskId.Creature, SmartEvents.IcLos => SmartScriptTypeMaskId.Creature, SmartEvents.PassengerBoarded => SmartScriptTypeMaskId.Creature, SmartEvents.PassengerRemoved => SmartScriptTypeMaskId.Creature, SmartEvents.Charmed => SmartScriptTypeMaskId.Creature, SmartEvents.CharmedTarget => SmartScriptTypeMaskId.Creature, SmartEvents.SpellHitTarget => SmartScriptTypeMaskId.Creature, SmartEvents.Damaged => SmartScriptTypeMaskId.Creature, SmartEvents.DamagedTarget => SmartScriptTypeMaskId.Creature, SmartEvents.Movementinform => SmartScriptTypeMaskId.Creature, SmartEvents.SummonDespawned => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.CorpseRemoved => SmartScriptTypeMaskId.Creature, SmartEvents.AiInit => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.DataSet => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.WaypointStart => SmartScriptTypeMaskId.Creature, SmartEvents.WaypointReached => SmartScriptTypeMaskId.Creature, SmartEvents.TransportAddplayer => SmartScriptTypeMaskId.Transport, SmartEvents.TransportAddcreature => SmartScriptTypeMaskId.Transport, SmartEvents.TransportRemovePlayer => SmartScriptTypeMaskId.Transport, SmartEvents.TransportRelocate => SmartScriptTypeMaskId.Transport, SmartEvents.InstancePlayerEnter => SmartScriptTypeMaskId.Instance, SmartEvents.AreatriggerOntrigger => SmartScriptTypeMaskId.Areatrigger + SmartScriptTypeMaskId.AreatrigggerEntity, SmartEvents.QuestAccepted => SmartScriptTypeMaskId.Quest, SmartEvents.QuestObjCompletion => SmartScriptTypeMaskId.Quest, SmartEvents.QuestRewarded => SmartScriptTypeMaskId.Quest, SmartEvents.QuestCompletion => SmartScriptTypeMaskId.Quest, SmartEvents.QuestFail => SmartScriptTypeMaskId.Quest, SmartEvents.TextOver => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.ReceiveHeal => SmartScriptTypeMaskId.Creature, SmartEvents.JustSummoned => SmartScriptTypeMaskId.Creature, SmartEvents.WaypointPaused => SmartScriptTypeMaskId.Creature, SmartEvents.WaypointResumed => SmartScriptTypeMaskId.Creature, SmartEvents.WaypointStopped => SmartScriptTypeMaskId.Creature, SmartEvents.WaypointEnded => SmartScriptTypeMaskId.Creature, SmartEvents.TimedEventTriggered => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.Update => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject + SmartScriptTypeMaskId.AreatrigggerEntity, SmartEvents.Link => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject + SmartScriptTypeMaskId.Areatrigger + SmartScriptTypeMaskId.Event + SmartScriptTypeMaskId.Gossip + SmartScriptTypeMaskId.Quest + SmartScriptTypeMaskId.Spell + SmartScriptTypeMaskId.Transport + SmartScriptTypeMaskId.Instance + SmartScriptTypeMaskId.AreatrigggerEntity, SmartEvents.GossipSelect => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.JustCreated => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.GossipHello => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.FollowCompleted => SmartScriptTypeMaskId.Creature, SmartEvents.PhaseChange => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.IsBehindTarget => SmartScriptTypeMaskId.Creature, SmartEvents.GameEventStart => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.GameEventEnd => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.GoLootStateChanged => SmartScriptTypeMaskId.Gameobject, SmartEvents.GoEventInform => SmartScriptTypeMaskId.Gameobject, SmartEvents.ActionDone => SmartScriptTypeMaskId.Creature, SmartEvents.OnSpellclick => SmartScriptTypeMaskId.Creature, SmartEvents.FriendlyHealthPCT => SmartScriptTypeMaskId.Creature, SmartEvents.DistanceCreature => SmartScriptTypeMaskId.Creature, SmartEvents.DistanceGameobject => SmartScriptTypeMaskId.Creature, SmartEvents.CounterSet => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.SceneStart => SmartScriptTypeMaskId.Scene, SmartEvents.SceneTrigger => SmartScriptTypeMaskId.Scene, SmartEvents.SceneCancel => SmartScriptTypeMaskId.Scene, SmartEvents.SceneComplete => SmartScriptTypeMaskId.Scene, SmartEvents.SummonedUnitDies => SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject, SmartEvents.OnSpellCast => SmartScriptTypeMaskId.Creature, SmartEvents.OnSpellFailed => SmartScriptTypeMaskId.Creature, SmartEvents.OnSpellStart => SmartScriptTypeMaskId.Creature, SmartEvents.OnDespawn => SmartScriptTypeMaskId.Creature, SmartEvents.SendEventTrigger => SmartScriptTypeMaskId.Event, _ => 0, }; public static void TC_SAI_IS_BOOLEAN_VALID(SmartScriptHolder e, uint value, [CallerArgumentExpression("value")] string valueName = null) { if (value > 1) Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} uses param {valueName} of type Boolean with value {value}, valid values are 0 or 1, skipped."); } } public class SmartScriptHolder : IComparable { public const uint DefaultPriority = uint.MaxValue; public int EntryOrGuid; public SmartScriptType SourceType; public uint EventId; public uint Link; public List Difficulties = new(); public SmartEvent Event; public SmartAction Action; public SmartTarget Target; public uint Timer; public uint Priority; public bool Active; public bool RunOnce; public bool EnableTimed; public SmartScriptHolder() { } public SmartScriptHolder(SmartScriptHolder other) { EntryOrGuid = other.EntryOrGuid; SourceType = other.SourceType; EventId = other.EventId; Link = other.Link; Event = other.Event; Action = other.Action; Target = other.Target; Timer = other.Timer; Active = other.Active; RunOnce = other.RunOnce; EnableTimed = other.EnableTimed; } public SmartScriptType GetScriptType() { return SourceType; } public SmartEvents GetEventType() { return Event.type; } public SmartActions GetActionType() { return Action.type; } public SmartTargets GetTargetType() { return Target.type; } public override string ToString() { return $"Entry {EntryOrGuid} SourceType {GetScriptType()} Event {EventId} Action {GetActionType()}"; } public int CompareTo(SmartScriptHolder other) { int result = Priority.CompareTo(other.Priority); if (result == 0) result = EntryOrGuid.CompareTo(other.EntryOrGuid); if (result == 0) result = SourceType.CompareTo(other.SourceType); if (result == 0) result = EventId.CompareTo(other.EventId); if (result == 0) result = Link.CompareTo(other.Link); return result; } } [StructLayout(LayoutKind.Explicit)] public struct SmartEvent { [FieldOffset(0)] public SmartEvents type; [FieldOffset(4)] public uint event_phase_mask; [FieldOffset(8)] public uint event_chance; [FieldOffset(12)] public SmartEventFlags event_flags; [FieldOffset(16)] public MinMaxRepeat minMaxRepeat; [FieldOffset(16)] public Kill kill; [FieldOffset(16)] public SpellHit spellHit; [FieldOffset(16)] public Los los; [FieldOffset(16)] public Respawn respawn; [FieldOffset(16)] public MinMax minMax; [FieldOffset(16)] public TargetCasting targetCasting; [FieldOffset(16)] public FriendlyCC friendlyCC; [FieldOffset(16)] public MissingBuff missingBuff; [FieldOffset(16)] public Summoned summoned; [FieldOffset(16)] public Quest quest; [FieldOffset(16)] public QuestObjective questObjective; [FieldOffset(16)] public Emote emote; [FieldOffset(16)] public Aura aura; [FieldOffset(16)] public Charm charm; [FieldOffset(16)] public MovementInform movementInform; [FieldOffset(16)] public DataSet dataSet; [FieldOffset(16)] public Waypoint waypoint; [FieldOffset(16)] public TransportAddCreature transportAddCreature; [FieldOffset(16)] public TransportRelocate transportRelocate; [FieldOffset(16)] public InstancePlayerEnter instancePlayerEnter; [FieldOffset(16)] public Areatrigger areatrigger; [FieldOffset(16)] public TextOver textOver; [FieldOffset(16)] public TimedEvent timedEvent; [FieldOffset(16)] public GossipHello gossipHello; [FieldOffset(16)] public Gossip gossip; [FieldOffset(16)] public GameEvent gameEvent; [FieldOffset(16)] public GoLootStateChanged goLootStateChanged; [FieldOffset(16)] public EventInform eventInform; [FieldOffset(16)] public DoAction doAction; [FieldOffset(16)] public FriendlyHealthPct friendlyHealthPct; [FieldOffset(16)] public Distance distance; [FieldOffset(16)] public Counter counter; [FieldOffset(16)] public SpellCast spellCast; [FieldOffset(16)] public Spell spell; [FieldOffset(16)] public Raw raw; [FieldOffset(40)] public string param_string; #region Structs public struct MinMaxRepeat { public uint min; public uint max; public uint repeatMin; public uint repeatMax; } public struct Kill { public uint cooldownMin; public uint cooldownMax; public uint playerOnly; public uint creature; } public struct SpellHit { public uint spell; public uint school; public uint cooldownMin; public uint cooldownMax; } public struct Los { public uint hostilityMode; public uint maxDist; public uint cooldownMin; public uint cooldownMax; public uint playerOnly; } public struct Respawn { public uint type; public uint map; public uint area; } public struct MinMax { public uint repeatMin; public uint repeatMax; } public struct TargetCasting { public uint repeatMin; public uint repeatMax; public uint spellId; } public struct FriendlyCC { public uint radius; public uint repeatMin; public uint repeatMax; } public struct MissingBuff { public uint spell; public uint radius; public uint repeatMin; public uint repeatMax; } public struct Summoned { public uint creature; public uint cooldownMin; public uint cooldownMax; } public struct Quest { public uint questId; public uint cooldownMin; public uint cooldownMax; } public struct QuestObjective { public uint id; } public struct Emote { public uint emoteId; public uint cooldownMin; public uint cooldownMax; } public struct Aura { public uint spell; public uint count; public uint repeatMin; public uint repeatMax; } public struct Charm { public uint onRemove; } public struct MovementInform { public uint type; public uint id; } public struct DataSet { public uint id; public uint value; public uint cooldownMin; public uint cooldownMax; } public struct Waypoint { public uint pointID; public uint pathID; } public struct TransportAddCreature { public uint creature; } public struct TransportRelocate { public uint pointID; } public struct InstancePlayerEnter { public uint team; public uint cooldownMin; public uint cooldownMax; } public struct Areatrigger { public uint id; } public struct TextOver { public uint textGroupID; public uint creatureEntry; } public struct TimedEvent { public uint id; } public struct GossipHello { public uint filter; } public struct Gossip { public uint sender; public uint action; } public struct GameEvent { public uint gameEventId; } public struct GoLootStateChanged { public uint lootState; } public struct EventInform { public uint eventId; } public struct DoAction { public uint eventId; } public struct FriendlyHealthPct { public uint minHpPct; public uint maxHpPct; public uint repeatMin; public uint repeatMax; public uint radius; } public struct Distance { public uint guid; public uint entry; public uint dist; public uint repeat; } public struct Counter { public uint id; public uint value; public uint cooldownMin; public uint cooldownMax; } public struct SpellCast { public uint spell; public uint cooldownMin; public uint cooldownMax; } public struct Spell { public uint effIndex; } public struct Raw { public uint param1; public uint param2; public uint param3; public uint param4; public uint param5; } #endregion } [StructLayout(LayoutKind.Explicit)] public struct SmartAction { [FieldOffset(0)] public SmartActions type; [FieldOffset(4)] public Talk talk; [FieldOffset(4)] public SimpleTalk simpleTalk; [FieldOffset(4)] public Faction faction; [FieldOffset(4)] public MorphOrMount morphOrMount; [FieldOffset(4)] public Sound sound; [FieldOffset(4)] public Emote emote; [FieldOffset(4)] public Quest quest; [FieldOffset(4)] public QuestOffer questOffer; [FieldOffset(4)] public React react; [FieldOffset(4)] public RandomEmote randomEmote; [FieldOffset(4)] public Cast cast; [FieldOffset(4)] public CrossCast crossCast; [FieldOffset(4)] public SummonCreature summonCreature; [FieldOffset(4)] public ThreatPCT threatPCT; [FieldOffset(4)] public Threat threat; [FieldOffset(4)] public CastCreatureOrGO castCreatureOrGO; [FieldOffset(4)] public AutoAttack autoAttack; [FieldOffset(4)] public CombatMove combatMove; [FieldOffset(4)] public SetEventPhase setEventPhase; [FieldOffset(4)] public IncEventPhase incEventPhase; [FieldOffset(4)] public CastedCreatureOrGO castedCreatureOrGO; [FieldOffset(4)] public RemoveAura removeAura; [FieldOffset(4)] public Follow follow; [FieldOffset(4)] public RandomPhase randomPhase; [FieldOffset(4)] public RandomPhaseRange randomPhaseRange; [FieldOffset(4)] public KilledMonster killedMonster; [FieldOffset(4)] public SetInstanceData setInstanceData; [FieldOffset(4)] public SetInstanceData64 setInstanceData64; [FieldOffset(4)] public UpdateTemplate updateTemplate; [FieldOffset(4)] public CallHelp callHelp; [FieldOffset(4)] public SetSheath setSheath; [FieldOffset(4)] public ForceDespawn forceDespawn; [FieldOffset(4)] public InvincHP invincHP; [FieldOffset(4)] public IngamePhaseId ingamePhaseId; [FieldOffset(4)] public IngamePhaseGroup ingamePhaseGroup; [FieldOffset(4)] public SetData setData; [FieldOffset(4)] public MoveRandom moveRandom; [FieldOffset(4)] public Visibility visibility; [FieldOffset(4)] public SummonGO summonGO; [FieldOffset(4)] public Active active; [FieldOffset(4)] public Taxi taxi; [FieldOffset(4)] public WpStart wpStart; [FieldOffset(4)] public WpPause wpPause; [FieldOffset(4)] public WpStop wpStop; [FieldOffset(4)] public Item item; [FieldOffset(4)] public SetRun setRun; [FieldOffset(4)] public SetDisableGravity setDisableGravity; [FieldOffset(4)] public Teleport teleport; [FieldOffset(4)] public SetCounter setCounter; [FieldOffset(4)] public StoreTargets storeTargets; [FieldOffset(4)] public TimeEvent timeEvent; [FieldOffset(4)] public Movie movie; [FieldOffset(4)] public Equip equip; [FieldOffset(4)] public Flag flag; [FieldOffset(4)] public SetunitByte setunitByte; [FieldOffset(4)] public DelunitByte delunitByte; [FieldOffset(4)] public TimedActionList timedActionList; [FieldOffset(4)] public RandTimedActionList randTimedActionList; [FieldOffset(4)] public RandRangeTimedActionList randRangeTimedActionList; [FieldOffset(4)] public InterruptSpellCasting interruptSpellCasting; [FieldOffset(4)] public Jump jump; [FieldOffset(4)] public FleeAssist fleeAssist; [FieldOffset(4)] public EnableTempGO enableTempGO; [FieldOffset(4)] public MoveToPos moveToPos; [FieldOffset(4)] public SendGossipMenu sendGossipMenu; [FieldOffset(4)] public SetGoLootState setGoLootState; [FieldOffset(4)] public SendTargetToTarget sendTargetToTarget; [FieldOffset(4)] public SetRangedMovement setRangedMovement; [FieldOffset(4)] public SetHealthRegen setHealthRegen; [FieldOffset(4)] public SetRoot setRoot; [FieldOffset(4)] public GoState goState; [FieldOffset(4)] public CreatureGroup creatureGroup; [FieldOffset(4)] public Power power; [FieldOffset(4)] public GameEventStop gameEventStop; [FieldOffset(4)] public GameEventStart gameEventStart; [FieldOffset(4)] public ClosestWaypointFromList closestWaypointFromList; [FieldOffset(4)] public MoveOffset moveOffset; [FieldOffset(4)] public RandomSound randomSound; [FieldOffset(4)] public CorpseDelay corpseDelay; [FieldOffset(4)] public DisableEvade disableEvade; [FieldOffset(4)] public GroupSpawn groupSpawn; [FieldOffset(4)] public AuraType auraType; [FieldOffset(4)] public LoadEquipment loadEquipment; [FieldOffset(4)] public RandomTimedEvent randomTimedEvent; [FieldOffset(4)] public PauseMovement pauseMovement; [FieldOffset(4)] public RespawnData respawnData; [FieldOffset(4)] public AnimKit animKit; [FieldOffset(4)] public Scene scene; [FieldOffset(4)] public Cinematic cinematic; [FieldOffset(4)] public MovementSpeed movementSpeed; [FieldOffset(4)] public SpellVisualKit spellVisualKit; [FieldOffset(4)] public OverrideLight overrideLight; [FieldOffset(4)] public OverrideWeather overrideWeather; [FieldOffset(4)] public SetHover setHover; [FieldOffset(4)] public Evade evade; [FieldOffset(4)] public SetHealthPct setHealthPct; [FieldOffset(4)] public Conversation conversation; [FieldOffset(4)] public SetImmunePC setImmunePC; [FieldOffset(4)] public SetImmuneNPC setImmuneNPC; [FieldOffset(4)] public SetUninteractible setUninteractible; [FieldOffset(4)] public ActivateGameObject activateGameObject; [FieldOffset(4)] public AddToStoredTargets addToStoredTargets; [FieldOffset(4)] public BecomePersonalClone becomePersonalClone; [FieldOffset(4)] public TriggerGameEvent triggerGameEvent; [FieldOffset(4)] public DoAction doAction; [FieldOffset(4)] public Raw raw; [FieldOffset(32)] public string param_string; #region Stucts public struct Talk { public uint textGroupId; public uint duration; public uint useTalkTarget; } public struct SimpleTalk { public uint textGroupId; public uint duration; } public struct Faction { public uint factionId; } public struct MorphOrMount { public uint creature; public uint model; } public struct Sound { public uint soundId; public uint onlySelf; public uint distance; public uint keyBroadcastTextId; } public struct Emote { public uint emoteId; } public struct Quest { public uint questId; } public struct QuestOffer { public uint questId; public uint directAdd; } public struct React { public uint state; } public struct RandomEmote { public uint emote1; public uint emote2; public uint emote3; public uint emote4; public uint emote5; public uint emote6; } public struct Cast { public uint spell; public uint castFlags; public uint triggerFlags; public uint targetsLimit; } public struct CrossCast { public uint spell; public uint castFlags; public uint targetType; public uint targetParam1; public uint targetParam2; public uint targetParam3; public uint targetParam4; } public struct SummonCreature { public uint creature; public uint type; public uint duration; public uint storageID; public uint attackInvoker; public uint flags; // SmartActionSummonCreatureFlags public uint count; } public struct ThreatPCT { public uint threatINC; public uint threatDEC; } public struct CastCreatureOrGO { public uint quest; public uint spell; } public struct Threat { public uint threatINC; public uint threatDEC; } public struct AutoAttack { public uint attack; } public struct CombatMove { public uint move; } public struct SetEventPhase { public uint phase; } public struct IncEventPhase { public uint inc; public uint dec; } public struct CastedCreatureOrGO { public uint creature; public uint spell; } public struct RemoveAura { public uint spell; public uint charges; public uint onlyOwnedAuras; } public struct Follow { public uint dist; public uint angle; public uint entry; public uint credit; public uint creditType; } public struct RandomPhase { public uint phase1; public uint phase2; public uint phase3; public uint phase4; public uint phase5; public uint phase6; } public struct RandomPhaseRange { public uint phaseMin; public uint phaseMax; } public struct KilledMonster { public uint creature; } public struct SetInstanceData { public uint field; public uint data; public uint type; } public struct SetInstanceData64 { public uint field; } public struct UpdateTemplate { public uint creature; public uint updateLevel; } public struct CallHelp { public uint range; public uint withEmote; } public struct SetSheath { public uint sheath; } public struct ForceDespawn { public uint delay; public uint forceRespawnTimer; } public struct InvincHP { public uint minHP; public uint percent; } public struct IngamePhaseId { public uint id; public uint apply; } public struct IngamePhaseGroup { public uint groupId; public uint apply; } public struct SetData { public uint field; public uint data; } public struct MoveRandom { public uint distance; } public struct Visibility { public uint state; } public struct SummonGO { public uint entry; public uint despawnTime; public uint summonType; } public struct Active { public uint state; } public struct Taxi { public uint id; } public struct WpStart { public uint run; public uint pathID; public uint repeat; public uint quest; public uint despawnTime; //public uint reactState; DO NOT REUSE } public struct WpPause { public uint delay; } public struct WpStop { public uint despawnTime; public uint quest; public uint fail; } public struct Item { public uint entry; public uint count; } public struct SetRun { public uint run; } public struct SetDisableGravity { public uint disable; } public struct Teleport { public uint mapID; } public struct SetCounter { public uint counterId; public uint value; public uint reset; } public struct StoreTargets { public uint id; } public struct TimeEvent { public uint id; public uint min; public uint max; public uint repeatMin; public uint repeatMax; public uint chance; } public struct Movie { public uint entry; } public struct Equip { public uint entry; public uint mask; public uint slot1; public uint slot2; public uint slot3; } public struct Flag { public uint flag; } public struct SetunitByte { public uint byte1; public uint type; } public struct DelunitByte { public uint byte1; public uint type; } public struct TimedActionList { public uint id; public uint timerType; public uint allowOverride; } public struct RandTimedActionList { public uint actionList1; public uint actionList2; public uint actionList3; public uint actionList4; public uint actionList5; public uint actionList6; } public struct RandRangeTimedActionList { public uint idMin; public uint idMax; } public struct InterruptSpellCasting { public uint withDelayed; public uint spell_id; public uint withInstant; } public struct Jump { public uint SpeedXY; public uint SpeedZ; public uint Gravity; public uint UseDefaultGravity; public uint PointId; public uint ContactDistance; } public struct FleeAssist { public uint withEmote; } public struct EnableTempGO { public uint duration; } public struct MoveToPos { public uint pointId; public uint transport; public uint disablePathfinding; public uint contactDistance; } public struct SendGossipMenu { public uint gossipMenuId; public uint gossipNpcTextId; } public struct SetGoLootState { public uint state; } public struct SendTargetToTarget { public uint id; } public struct SetRangedMovement { public uint distance; public uint angle; } public struct SetHealthRegen { public uint regenHealth; } public struct SetRoot { public uint root; } public struct GoState { public uint state; } public struct CreatureGroup { public uint group; public uint attackInvoker; } public struct Power { public uint powerType; public uint newPower; } public struct GameEventStop { public uint id; } public struct GameEventStart { public uint id; } public struct ClosestWaypointFromList { public uint wp1; public uint wp2; public uint wp3; public uint wp4; public uint wp5; public uint wp6; } public struct MoveOffset { public uint PointId; } public struct RandomSound { public uint sound1; public uint sound2; public uint sound3; public uint sound4; public uint onlySelf; public uint distance; } public struct CorpseDelay { public uint timer; public uint includeDecayRatio; } public struct DisableEvade { public uint disable; } public struct GroupSpawn { public uint groupId; public uint minDelay; public uint maxDelay; public uint spawnflags; } public struct LoadEquipment { public uint id; public uint force; } public struct RandomTimedEvent { public uint minId; public uint maxId; } public struct PauseMovement { public uint movementSlot; public uint pauseTimer; public uint force; } public struct RespawnData { public uint spawnType; public uint spawnId; } public struct AnimKit { public uint animKit; public uint type; } public struct Scene { public uint sceneId; } public struct Cinematic { public uint entry; } public struct MovementSpeed { public uint movementType; public uint speedInteger; public uint speedFraction; } public struct SpellVisualKit { public uint spellVisualKitId; public uint kitType; public uint duration; } public struct OverrideLight { public uint zoneId; public uint areaLightId; public uint overrideLightId; public uint transitionMilliseconds; } public struct OverrideWeather { public uint zoneId; public uint weatherId; public uint intensity; } public struct SetHover { public uint enable; } public struct Evade { public uint toRespawnPosition; } public struct SetHealthPct { public uint percent; } public struct Conversation { public uint id; } public struct SetImmunePC { public uint immunePC; } public struct SetImmuneNPC { public uint immuneNPC; } public struct SetUninteractible { public uint uninteractible; } public struct ActivateGameObject { public uint gameObjectAction; public uint param; } public struct AddToStoredTargets { public uint id; } public struct BecomePersonalClone { public uint type; public uint duration; } public struct TriggerGameEvent { public uint eventId; public uint useSaiTargetAsGameEventSource; } public struct DoAction { public uint actionId; } public struct Raw { public uint param1; public uint param2; public uint param3; public uint param4; public uint param5; public uint param6; public uint param7; } #endregion } [StructLayout(LayoutKind.Explicit)] public struct SmartTarget { [FieldOffset(0)] public SmartTargets type; [FieldOffset(4)] public float x; [FieldOffset(8)] public float y; [FieldOffset(12)] public float z; [FieldOffset(16)] public float o; [FieldOffset(20)] public HostilRandom hostilRandom; [FieldOffset(20)] public Farthest farthest; [FieldOffset(20)] public UnitRange unitRange; [FieldOffset(20)] public UnitGUID unitGUID; [FieldOffset(20)] public UnitDistance unitDistance; [FieldOffset(20)] public PlayerDistance playerDistance; [FieldOffset(20)] public PlayerRange playerRange; [FieldOffset(20)] public Stored stored; [FieldOffset(20)] public GoRange goRange; [FieldOffset(20)] public GoGUID goGUID; [FieldOffset(20)] public GoDistance goDistance; [FieldOffset(20)] public UnitClosest unitClosest; [FieldOffset(20)] public GoClosest goClosest; [FieldOffset(20)] public ClosestAttackable closestAttackable; [FieldOffset(20)] public ClosestFriendly closestFriendly; [FieldOffset(20)] public Owner owner; [FieldOffset(20)] public Vehicle vehicle; [FieldOffset(20)] public ThreatList threatList; [FieldOffset(20)] public Raw raw; [FieldOffset(40)] public string param_string; #region Structs public struct HostilRandom { public uint maxDist; public uint playerOnly; public uint powerType; } public struct Farthest { public uint maxDist; public uint playerOnly; public uint isInLos; } public struct UnitRange { public uint creature; public uint minDist; public uint maxDist; public uint maxSize; } public struct UnitGUID { public uint dbGuid; public uint entry; } public struct UnitDistance { public uint creature; public uint dist; public uint maxSize; } public struct PlayerDistance { public uint dist; } public struct PlayerRange { public uint minDist; public uint maxDist; } public struct Stored { public uint id; } public struct GoRange { public uint entry; public uint minDist; public uint maxDist; public uint maxSize; } public struct GoGUID { public uint dbGuid; public uint entry; } public struct GoDistance { public uint entry; public uint dist; public uint maxSize; } public struct UnitClosest { public uint entry; public uint dist; public uint dead; } public struct GoClosest { public uint entry; public uint dist; } public struct ClosestAttackable { public uint maxDist; public uint playerOnly; } public struct ClosestFriendly { public uint maxDist; public uint playerOnly; } public struct Owner { public uint useCharmerOrOwner; } public struct Vehicle { public uint seatMask; } public struct ThreatList { public uint maxDist; } public struct Raw { public uint param1; public uint param2; public uint param3; public uint param4; } #endregion } }