diff --git a/Source/Framework/Constants/GameObjectConst.cs b/Source/Framework/Constants/GameObjectConst.cs index b69800985..924f50cc8 100644 --- a/Source/Framework/Constants/GameObjectConst.cs +++ b/Source/Framework/Constants/GameObjectConst.cs @@ -198,5 +198,6 @@ namespace Framework.Constants PlaySpellVisual = 42, // Play Spell Visual "{SpellVisual}" StopSpellVisual = 43, // Stop Spell Visual SetTappedToChallengePlayers = 44, // Set Tapped to Challenge Players + Max } } diff --git a/Source/Game/AI/SmartScripts/SmartAIManager.cs b/Source/Game/AI/SmartScripts/SmartAIManager.cs index 13aa16c41..6a221a882 100644 --- a/Source/Game/AI/SmartScripts/SmartAIManager.cs +++ b/Source/Game/AI/SmartScripts/SmartAIManager.cs @@ -814,7 +814,7 @@ namespace Game.AI 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.Raw)), + SmartActions.ActivateGameobject => Marshal.SizeOf(typeof(SmartAction.ActivateGameObject)), SmartActions.AddToStoredTargetList => Marshal.SizeOf(typeof(SmartAction.AddToStoredTargets)), SmartActions.BecomePersonalCloneForPlayer => Marshal.SizeOf(typeof(SmartAction.BecomePersonalClone)), _ => Marshal.SizeOf(typeof(SmartAction.Raw)), @@ -2062,6 +2062,16 @@ namespace Game.AI 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); @@ -2077,11 +2087,14 @@ namespace Game.AI TC_SAI_IS_BOOLEAN_VALID(e, e.Action.setUninteractible.uninteractible); break; } - case SmartActions.CreateConversation: + case SmartActions.ActivateGameobject: { - if (Global.ConversationDataStorage.GetConversationTemplate(e.Action.conversation.id) == null) + if (!NotNULL(e, e.Action.activateGameObject.gameObjectAction)) + return false; + + if (e.Action.activateGameObject.gameObjectAction >= (uint)GameObjectActions.Max) { - 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."); + Log.outError(LogFilter.Sql, $"SmartAIMgr: {e} has gameObjectAction parameter out of range (max allowed {(uint)GameObjectActions.Max}, current value {e.Action.activateGameObject.gameObjectAction}), skipped."); return false; } @@ -3196,6 +3209,9 @@ namespace Game.AI [FieldOffset(4)] public SetHealthPct setHealthPct; + [FieldOffset(4)] + public Conversation conversation; + [FieldOffset(4)] public SetImmunePC setImmunePC; @@ -3206,7 +3222,7 @@ namespace Game.AI public SetUninteractible setUninteractible; [FieldOffset(4)] - public Conversation conversation; + public ActivateGameObject activateGameObject; [FieldOffset(4)] public AddToStoredTargets addToStoredTargets; @@ -3745,6 +3761,10 @@ namespace Game.AI { public uint percent; } + public struct Conversation + { + public uint id; + } public struct SetImmunePC { public uint immunePC; @@ -3757,9 +3777,10 @@ namespace Game.AI { public uint uninteractible; } - public struct Conversation + public struct ActivateGameObject { - public uint id; + public uint gameObjectAction; + public uint param; } public struct AddToStoredTargets { diff --git a/Source/Game/AI/SmartScripts/SmartScript.cs b/Source/Game/AI/SmartScripts/SmartScript.cs index 1c5b2cd4a..2b4f7bc54 100644 --- a/Source/Game/AI/SmartScripts/SmartScript.cs +++ b/Source/Game/AI/SmartScripts/SmartScript.cs @@ -2432,6 +2432,24 @@ namespace Game.AI break; } + case SmartActions.CreateConversation: + { + WorldObject baseObject = GetBaseObject(); + + foreach (WorldObject target in targets) + { + Player playerTarget = target.ToPlayer(); + if (playerTarget != null) + { + Conversation conversation = Conversation.CreateConversation(e.Action.conversation.id, playerTarget, + playerTarget, playerTarget.GetGUID(), null); + if (!conversation) + Log.outWarn(LogFilter.ScriptsAi, $"SmartScript.ProcessAction: SMART_ACTION_CREATE_CONVERSATION: id {e.Action.conversation.id}, baseObject {baseObject?.GetName()}, target {playerTarget.GetName()} - failed to create"); + } + } + + break; + } case SmartActions.SetImmunePC: { foreach (var target in targets) @@ -2474,22 +2492,14 @@ namespace Game.AI } break; } - case SmartActions.CreateConversation: + case SmartActions.ActivateGameobject: { - WorldObject baseObject = GetBaseObject(); - foreach (WorldObject target in targets) { - Player playerTarget = target.ToPlayer(); - if (playerTarget != null) - { - Conversation conversation = Conversation.CreateConversation(e.Action.conversation.id, playerTarget, - playerTarget, playerTarget.GetGUID(), null); - if (!conversation) - Log.outWarn(LogFilter.ScriptsAi, $"SmartScript.ProcessAction: SMART_ACTION_CREATE_CONVERSATION: id {e.Action.conversation.id}, baseObject {baseObject?.GetName()}, target {playerTarget.GetName()} - failed to create"); - } + GameObject targetGo = target.ToGameObject(); + if (targetGo != null) + targetGo.ActivateObject((GameObjectActions)e.Action.activateGameObject.gameObjectAction, (int)e.Action.activateGameObject.param); } - break; } case SmartActions.AddToStoredTargetList: diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index d49465fe8..d1958e1a7 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -1484,6 +1484,132 @@ namespace Game.Entities m_cooldownTime = time_to_restore != 0 ? GameTime.GetGameTimeMS() + time_to_restore : 0; } + public void ActivateObject(GameObjectActions action, int param, WorldObject spellCaster = null, uint spellId = 0, int effectIndex = -1) + { + Unit unitCaster = spellCaster ? spellCaster.ToUnit() : null; + + switch (action) + { + case GameObjectActions.AnimateCustom0: + case GameObjectActions.AnimateCustom1: + case GameObjectActions.AnimateCustom2: + case GameObjectActions.AnimateCustom3: + SendCustomAnim((uint)(action - GameObjectActions.AnimateCustom0)); + break; + case GameObjectActions.Disturb: // What's the difference with Open? + case GameObjectActions.Open: + if (unitCaster) + Use(unitCaster); + break; + case GameObjectActions.OpenAndUnlock: + if (unitCaster) + UseDoorOrButton(0, false, unitCaster); + goto case GameObjectActions.Unlock; + case GameObjectActions.Unlock: + RemoveFlag(GameObjectFlags.Locked); + break; + case GameObjectActions.Lock: + AddFlag(GameObjectFlags.Locked); + break; + case GameObjectActions.Close: + case GameObjectActions.Rebuild: + ResetDoorOrButton(); + break; + case GameObjectActions.Despawn: + DespawnOrUnsummon(); + break; + case GameObjectActions.MakeInert: + AddFlag(GameObjectFlags.NotSelectable); + break; + case GameObjectActions.MakeActive: + RemoveFlag(GameObjectFlags.NotSelectable); + break; + case GameObjectActions.CloseAndLock: + ResetDoorOrButton(); + AddFlag(GameObjectFlags.Locked); + break; + case GameObjectActions.Destroy: + if (unitCaster) + UseDoorOrButton(0, true, unitCaster); + break; + case GameObjectActions.UseArtKit0: + case GameObjectActions.UseArtKit1: + case GameObjectActions.UseArtKit2: + case GameObjectActions.UseArtKit3: + case GameObjectActions.UseArtKit4: + { + GameObjectTemplateAddon templateAddon = GetTemplateAddon(); + + uint artKitIndex = action != GameObjectActions.UseArtKit4 ? (uint)(action - GameObjectActions.UseArtKit0) : 4; + + uint artKitValue = 0; + if (templateAddon != null) + artKitValue = templateAddon.ArtKits[artKitIndex]; + + if (artKitValue == 0) + Log.outError(LogFilter.Sql, $"GameObject {GetEntry()} hit by spell {spellId} needs `artkit{artKitIndex}` in `gameobject_template_addon`"); + else + SetGoArtKit((byte)artKitValue); + + break; + } + case GameObjectActions.GoTo1stFloor: + case GameObjectActions.GoTo2ndFloor: + case GameObjectActions.GoTo3rdFloor: + case GameObjectActions.GoTo4thFloor: + case GameObjectActions.GoTo5thFloor: + case GameObjectActions.GoTo6thFloor: + case GameObjectActions.GoTo7thFloor: + case GameObjectActions.GoTo8thFloor: + case GameObjectActions.GoTo9thFloor: + case GameObjectActions.GoTo10thFloor: + if (GetGoType() == GameObjectTypes.Transport) + SetTransportState(GameObjectState.TransportStopped, (uint)(action - GameObjectActions.GoTo1stFloor)); + else + Log.outError(LogFilter.Spells, $"Spell {spellId} targeted non-transport gameobject for transport only action \"Go to Floor\" {action} in effect {effectIndex}"); + break; + case GameObjectActions.PlayAnimKit: + SetAnimKitId((ushort)param, false); + break; + case GameObjectActions.OpenAndPlayAnimKit: + if (unitCaster) + UseDoorOrButton(0, false, unitCaster); + SetAnimKitId((ushort)param, false); + break; + case GameObjectActions.CloseAndPlayAnimKit: + ResetDoorOrButton(); + SetAnimKitId((ushort)param, false); + break; + case GameObjectActions.PlayOneShotAnimKit: + SetAnimKitId((ushort)param, true); + break; + case GameObjectActions.StopAnimKit: + SetAnimKitId(0, false); + break; + case GameObjectActions.OpenAndStopAnimKit: + if (unitCaster) + UseDoorOrButton(0, false, unitCaster); + SetAnimKitId(0, false); + break; + case GameObjectActions.CloseAndStopAnimKit: + ResetDoorOrButton(); + SetAnimKitId(0, false); + break; + case GameObjectActions.PlaySpellVisual: + SetSpellVisualId((uint)param, spellCaster.GetGUID()); + break; + case GameObjectActions.StopSpellVisual: + SetSpellVisualId(0); + break; + case GameObjectActions.None: + Log.outFatal(LogFilter.Spells, $"Spell {spellId} has action type NONE in effect {effectIndex}"); + break; + default: + Log.outError(LogFilter.Spells, $"Spell {spellId} has unhandled action {action} in effect {effectIndex}"); + break; + } + } + public void SetGoArtKit(byte kit) { SetUpdateFieldValue(m_values.ModifyValue(m_gameObjectData).ModifyValue(m_gameObjectData.ArtKit), kit); diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index f5df01c4a..18558dc79 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -3054,144 +3054,7 @@ namespace Game.Spells if (gameObjTarget == null) return; - GameObjectActions action = (GameObjectActions)effectInfo.MiscValue; - - switch (action) - { - case GameObjectActions.AnimateCustom0: - case GameObjectActions.AnimateCustom1: - case GameObjectActions.AnimateCustom2: - case GameObjectActions.AnimateCustom3: - gameObjTarget.SendCustomAnim((uint)(action - GameObjectActions.AnimateCustom0)); - break; - case GameObjectActions.Disturb: // What's the difference with Open? - case GameObjectActions.Open: - { - Unit unitCaster = m_caster.ToUnit(); - if (unitCaster != null) - gameObjTarget.Use(unitCaster); - break; - } - case GameObjectActions.OpenAndUnlock: - { - Unit unitCaster = m_caster.ToUnit(); - if (unitCaster != null) - gameObjTarget.UseDoorOrButton(0, false, unitCaster); - - gameObjTarget.RemoveFlag(GameObjectFlags.Locked); - break; - } - case GameObjectActions.Unlock: - gameObjTarget.RemoveFlag(GameObjectFlags.Locked); - break; - case GameObjectActions.Lock: - gameObjTarget.AddFlag(GameObjectFlags.Locked); - break; - case GameObjectActions.Close: - case GameObjectActions.Rebuild: - gameObjTarget.ResetDoorOrButton(); - break; - case GameObjectActions.Despawn: - gameObjTarget.DespawnOrUnsummon(); - break; - case GameObjectActions.MakeInert: - gameObjTarget.AddFlag(GameObjectFlags.NotSelectable); - break; - case GameObjectActions.MakeActive: - gameObjTarget.RemoveFlag(GameObjectFlags.NotSelectable); - break; - case GameObjectActions.CloseAndLock: - gameObjTarget.ResetDoorOrButton(); - gameObjTarget.AddFlag(GameObjectFlags.Locked); - break; - case GameObjectActions.Destroy: - { - Unit unitCaster = m_caster.ToUnit(); - if (unitCaster != null) - gameObjTarget.UseDoorOrButton(0, true, unitCaster); - break; - } - case GameObjectActions.UseArtKit0: - case GameObjectActions.UseArtKit1: - case GameObjectActions.UseArtKit2: - case GameObjectActions.UseArtKit3: - case GameObjectActions.UseArtKit4: - { - GameObjectTemplateAddon templateAddon = gameObjTarget.GetTemplateAddon(); - - uint artKitIndex = action != GameObjectActions.UseArtKit4 ? (uint)(action - GameObjectActions.UseArtKit0) : 4; - uint artKitValue = 0; - if (templateAddon != null) - artKitValue = templateAddon.ArtKits[artKitIndex]; - - if (artKitValue == 0) - Log.outError(LogFilter.Sql, $"GameObject {gameObjTarget.GetEntry()} hit by spell {m_spellInfo.Id} needs `artkit{artKitIndex}` in `gameobject_template_addon`"); - else - gameObjTarget.SetGoArtKit((byte)artKitValue); - - break; - } - case GameObjectActions.GoTo1stFloor: - case GameObjectActions.GoTo2ndFloor: - case GameObjectActions.GoTo3rdFloor: - case GameObjectActions.GoTo4thFloor: - case GameObjectActions.GoTo5thFloor: - case GameObjectActions.GoTo6thFloor: - case GameObjectActions.GoTo7thFloor: - case GameObjectActions.GoTo8thFloor: - case GameObjectActions.GoTo9thFloor: - case GameObjectActions.GoTo10thFloor: - if (gameObjTarget.GetGoType() == GameObjectTypes.Transport) - gameObjTarget.SetTransportState(GameObjectState.TransportStopped, (uint)(action - GameObjectActions.GoTo1stFloor)); - else - Log.outError(LogFilter.Spells, $"Spell {m_spellInfo.Id} targeted non-transport gameobject for transport only action \"Go to Floor\" {action} in effect {effectInfo.EffectIndex}"); - break; - case GameObjectActions.PlayAnimKit: - gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, false); - break; - case GameObjectActions.OpenAndPlayAnimKit: - { - Unit unitCaster = m_caster.ToUnit(); - if (unitCaster != null) - gameObjTarget.UseDoorOrButton(0, false, unitCaster); - gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, false); - break; - } - case GameObjectActions.CloseAndPlayAnimKit: - gameObjTarget.ResetDoorOrButton(); - gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, false); - break; - case GameObjectActions.PlayOneShotAnimKit: - gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, true); - break; - case GameObjectActions.StopAnimKit: - gameObjTarget.SetAnimKitId(0, false); - break; - case GameObjectActions.OpenAndStopAnimKit: - { - Unit unitCaster = m_caster.ToUnit(); - if (unitCaster != null) - gameObjTarget.UseDoorOrButton(0, false, unitCaster); - gameObjTarget.SetAnimKitId(0, false); - break; - } - case GameObjectActions.CloseAndStopAnimKit: - gameObjTarget.ResetDoorOrButton(); - gameObjTarget.SetAnimKitId(0, false); - break; - case GameObjectActions.PlaySpellVisual: - gameObjTarget.SetSpellVisualId((ushort)effectInfo.MiscValueB, m_originalCasterGUID); - break; - case GameObjectActions.StopSpellVisual: - gameObjTarget.SetSpellVisualId(0); - break; - case GameObjectActions.None: - Log.outFatal(LogFilter.Spells, $"Spell {m_spellInfo.Id} has action type NONE in effect {effectInfo.EffectIndex}"); - break; - default: - Log.outFatal(LogFilter.Spells, $"Spell {m_spellInfo.Id} has unhandled action {action} in effect {effectInfo.EffectIndex}"); - break; - } + gameObjTarget.ActivateObject((GameObjectActions)effectInfo.MiscValue, effectInfo.MiscValueB, m_caster, m_spellInfo.Id, (int)effectInfo.EffectIndex); } [SpellEffectHandler(SpellEffectName.ApplyGlyph)]