From 50cda96b45e3ce74a7bee970b5bbc400d02f1eeb Mon Sep 17 00:00:00 2001 From: hondacrx Date: Fri, 13 Oct 2023 20:25:10 -0400 Subject: [PATCH] More work on scripts. --- Source/Game/Accounts/AccountManager.cs | 21 + Source/Game/Chat/Commands/AccountCommands.cs | 17 +- Source/Game/DungeonFinding/LFGScripts.cs | 2 +- Source/Game/Handlers/CharacterHandler.cs | 27 +- Source/Game/Networking/WorldSocket.cs | 9 + Source/Game/Scripting/CoreScripts.cs | 28 +- Source/Game/Scripting/ScriptManager.cs | 37 +- Source/Scripts/Pets/DeathKnight.cs | 166 ++++--- Source/Scripts/Pets/Generic.cs | 406 ++++++++++-------- Source/Scripts/Pets/Hunter.cs | 157 ++++--- Source/Scripts/Pets/Mage.cs | 276 ++++++------ Source/Scripts/Pets/Priest.cs | 100 +++-- Source/Scripts/Pets/Shaman.cs | 133 +++--- Source/Scripts/Shadowlands/SpellCovenant.cs | 22 +- .../Shadowlands/Torghast/SpellTorghast.cs | 245 ++++++++++- Source/Scripts/World/ActionIpLogger.cs | 159 +++---- 16 files changed, 1073 insertions(+), 732 deletions(-) diff --git a/Source/Game/Accounts/AccountManager.cs b/Source/Game/Accounts/AccountManager.cs index ef3b87af5..10091fdc6 100644 --- a/Source/Game/Accounts/AccountManager.cs +++ b/Source/Game/Accounts/AccountManager.cs @@ -167,10 +167,16 @@ namespace Game string username; if (!GetName(accountId, out username)) + { + Global.ScriptMgr.OnFailedPasswordChange(accountId); return AccountOpResult.NameNotExist; // account doesn't exist + } if (newPassword.Length > MaxAccountLength) + { + Global.ScriptMgr.OnFailedPasswordChange(accountId); return AccountOpResult.PassTooLong; + } (byte[] salt, byte[] verifier) = SRP6.MakeRegistrationData(username, newPassword); @@ -180,38 +186,53 @@ namespace Game stmt.AddValue(2, accountId); DB.Login.Execute(stmt); + Global.ScriptMgr.OnPasswordChange(accountId); return AccountOpResult.Ok; } public AccountOpResult ChangeEmail(uint accountId, string newEmail) { if (!GetName(accountId, out _)) + { + Global.ScriptMgr.OnFailedEmailChange(accountId); return AccountOpResult.NameNotExist; // account doesn't exist + } if (newEmail.Length > MaxEmailLength) + { + Global.ScriptMgr.OnFailedEmailChange(accountId); return AccountOpResult.EmailTooLong; + } PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_EMAIL); stmt.AddValue(0, newEmail); stmt.AddValue(1, accountId); DB.Login.Execute(stmt); + Global.ScriptMgr.OnEmailChange(accountId); return AccountOpResult.Ok; } public AccountOpResult ChangeRegEmail(uint accountId, string newEmail) { if (!GetName(accountId, out _)) + { + Global.ScriptMgr.OnFailedEmailChange(accountId); return AccountOpResult.NameNotExist; // account doesn't exist + } if (newEmail.Length > MaxEmailLength) + { + Global.ScriptMgr.OnFailedEmailChange(accountId); return AccountOpResult.EmailTooLong; + } PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_REG_EMAIL); stmt.AddValue(0, newEmail); stmt.AddValue(1, accountId); DB.Login.Execute(stmt); + Global.ScriptMgr.OnEmailChange(accountId); return AccountOpResult.Ok; } diff --git a/Source/Game/Chat/Commands/AccountCommands.cs b/Source/Game/Chat/Commands/AccountCommands.cs index 3c9ba002e..0541d9a5e 100644 --- a/Source/Game/Chat/Commands/AccountCommands.cs +++ b/Source/Game/Chat/Commands/AccountCommands.cs @@ -197,7 +197,7 @@ namespace Game.Chat } [Command("create", CypherStrings.CommandAccCreateHelp, RBACPermissions.CommandAccountCreate, true)] - static bool HandleAccountCreateCommand(CommandHandler handler, string accountName, string password, [OptionalArg]string email) + static bool HandleAccountCreateCommand(CommandHandler handler, string accountName, string password, [OptionalArg] string email) { if (accountName.Contains("@")) { @@ -277,6 +277,7 @@ namespace Game.Chat if (!Global.AccountMgr.CheckEmail(handler.GetSession().GetAccountId(), oldEmail)) { handler.SendSysMessage(CypherStrings.CommandWrongemail); + Global.ScriptMgr.OnFailedEmailChange(handler.GetSession().GetAccountId()); Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change email, but the provided email [{4}] is not equal to registration email [{5}].", handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), @@ -287,6 +288,7 @@ namespace Game.Chat if (!Global.AccountMgr.CheckPassword(handler.GetSession().GetAccountId(), password)) { handler.SendSysMessage(CypherStrings.CommandWrongoldpassword); + Global.ScriptMgr.OnFailedEmailChange(handler.GetSession().GetAccountId()); Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change email, but the provided password is wrong.", handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); @@ -296,12 +298,14 @@ namespace Game.Chat if (email == oldEmail) { handler.SendSysMessage(CypherStrings.OldEmailIsNewEmail); + Global.ScriptMgr.OnFailedEmailChange(handler.GetSession().GetAccountId()); return false; } if (email != emailConfirm) { handler.SendSysMessage(CypherStrings.NewEmailsNotMatch); + Global.ScriptMgr.OnFailedEmailChange(handler.GetSession().GetAccountId()); Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change email, but the provided password is wrong.", handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); @@ -314,6 +318,7 @@ namespace Game.Chat { case AccountOpResult.Ok: handler.SendSysMessage(CypherStrings.CommandEmail); + Global.ScriptMgr.OnEmailChange(handler.GetSession().GetAccountId()); Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Changed Email from [{4}] to [{5}].", handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), @@ -321,6 +326,7 @@ namespace Game.Chat break; case AccountOpResult.EmailTooLong: handler.SendSysMessage(CypherStrings.EmailTooLong); + Global.ScriptMgr.OnFailedEmailChange(handler.GetSession().GetAccountId()); return false; default: handler.SendSysMessage(CypherStrings.CommandNotchangeemail); @@ -340,7 +346,7 @@ namespace Game.Chat if (!Global.AccountMgr.CheckPassword(handler.GetSession().GetAccountId(), oldPassword)) { handler.SendSysMessage(CypherStrings.CommandWrongoldpassword); - + Global.ScriptMgr.OnFailedPasswordChange(handler.GetSession().GetAccountId()); Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change password, but the provided old password is wrong.", handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); @@ -351,7 +357,7 @@ namespace Game.Chat && !Global.AccountMgr.CheckEmail(handler.GetSession().GetAccountId(), confirmEmail)) // ... and returns false if the comparison fails. { handler.SendSysMessage(CypherStrings.CommandWrongemail); - + Global.ScriptMgr.OnFailedPasswordChange(handler.GetSession().GetAccountId()); Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Tried to change password, but the entered email [{4}] is wrong.", handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(), @@ -363,6 +369,7 @@ namespace Game.Chat if (newPassword != confirmPassword) { handler.SendSysMessage(CypherStrings.NewPasswordsNotMatch); + Global.ScriptMgr.OnFailedPasswordChange(handler.GetSession().GetAccountId()); return false; } @@ -372,12 +379,14 @@ namespace Game.Chat { case AccountOpResult.Ok: handler.SendSysMessage(CypherStrings.CommandPassword); + Global.ScriptMgr.OnPasswordChange(handler.GetSession().GetAccountId()); Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) Changed Password.", handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(), handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString()); break; case AccountOpResult.PassTooLong: handler.SendSysMessage(CypherStrings.PasswordTooLong); + Global.ScriptMgr.OnFailedPasswordChange(handler.GetSession().GetAccountId()); return false; default: handler.SendSysMessage(CypherStrings.CommandNotchangepassword); @@ -552,7 +561,7 @@ namespace Game.Chat { [Command("2fa", CypherStrings.CommandAccSet2faHelp, RBACPermissions.CommandAccountSet2Fa, true)] static bool HandleAccountSet2FACommand(CommandHandler handler, string accountName, string secret) - { + { /*uint targetAccountId = Global.AccountMgr.GetId(accountName); if (targetAccountId == 0) { diff --git a/Source/Game/DungeonFinding/LFGScripts.cs b/Source/Game/DungeonFinding/LFGScripts.cs index fd4c8436f..e62019b27 100644 --- a/Source/Game/DungeonFinding/LFGScripts.cs +++ b/Source/Game/DungeonFinding/LFGScripts.cs @@ -26,7 +26,7 @@ namespace Game.DungeonFinding Global.LFGMgr.LeaveLfg(player.GetGUID(), true); } - public override void OnLogin(Player player) + public override void OnLogin(Player player, bool firstLogin) { if (!Global.LFGMgr.IsOptionEnabled(LfgOptions.EnableDungeonFinder | LfgOptions.EnableRaidBrowser)) return; diff --git a/Source/Game/Handlers/CharacterHandler.cs b/Source/Game/Handlers/CharacterHandler.cs index 864c12e44..14f0db54d 100644 --- a/Source/Game/Handlers/CharacterHandler.cs +++ b/Source/Game/Handlers/CharacterHandler.cs @@ -984,7 +984,8 @@ namespace Game SendNotification(CypherStrings.ResetTalents); } - if (pCurrChar.HasAtLoginFlag(AtLoginFlags.FirstLogin)) + bool firstLogin = pCurrChar.HasAtLoginFlag(AtLoginFlags.FirstLogin); + if (firstLogin) { pCurrChar.RemoveAtLoginFlag(AtLoginFlags.FirstLogin); @@ -1075,7 +1076,7 @@ namespace Game SendNotification(CypherStrings.GmOn); string IP_str = GetRemoteAddress(); - Log.outDebug(LogFilter.Network, $"Account: {GetAccountId()} (IP: {GetRemoteAddress()}) Login Character: [{pCurrChar.GetName()}] ({pCurrChar.GetGUID()}) Level: {pCurrChar.GetLevel()}, XP: { _player.GetXP()}/{_player.GetXPForNextLevel()} ({_player.GetXPForNextLevel() - _player.GetXP()} left)"); + Log.outDebug(LogFilter.Network, $"Account: {GetAccountId()} (IP: {GetRemoteAddress()}) Login Character: [{pCurrChar.GetName()}] ({pCurrChar.GetGUID()}) Level: {pCurrChar.GetLevel()}, XP: {_player.GetXP()}/{_player.GetXPForNextLevel()} ({_player.GetXPForNextLevel() - _player.GetXP()} left)"); if (!pCurrChar.IsStandState() && !pCurrChar.HasUnitState(UnitState.Stunned)) pCurrChar.SetStandState(UnitStandStateType.Stand); @@ -1088,7 +1089,7 @@ namespace Game // Handle Login-Achievements (should be handled after loading) _player.UpdateCriteria(CriteriaType.Login, 1); - Global.ScriptMgr.OnPlayerLogin(pCurrChar); + Global.ScriptMgr.OnPlayerLogin(pCurrChar, firstLogin); } public void AbortLogin(LoginFailureReason reason) @@ -1166,18 +1167,18 @@ namespace Game switch (packet.Action) { case TutorialAction.Update: + { + byte index = (byte)(packet.TutorialBit >> 5); + if (index >= SharedConst.MaxAccountTutorialValues) { - byte index = (byte)(packet.TutorialBit >> 5); - if (index >= SharedConst.MaxAccountTutorialValues) - { - Log.outError(LogFilter.Network, "CMSG_TUTORIAL_FLAG received bad TutorialBit {0}.", packet.TutorialBit); - return; - } - uint flag = GetTutorialInt(index); - flag |= (uint)(1 << (int)(packet.TutorialBit & 0x1F)); - SetTutorialInt(index, flag); - break; + Log.outError(LogFilter.Network, "CMSG_TUTORIAL_FLAG received bad TutorialBit {0}.", packet.TutorialBit); + return; } + uint flag = GetTutorialInt(index); + flag |= (uint)(1 << (int)(packet.TutorialBit & 0x1F)); + SetTutorialInt(index, flag); + break; + } case TutorialAction.Clear: for (byte i = 0; i < SharedConst.MaxAccountTutorialValues; ++i) SetTutorialInt(i, 0xFFFFFFFF); diff --git a/Source/Game/Networking/WorldSocket.cs b/Source/Game/Networking/WorldSocket.cs index ebc5dffe9..80c4a5411 100644 --- a/Source/Game/Networking/WorldSocket.cs +++ b/Source/Game/Networking/WorldSocket.cs @@ -586,6 +586,8 @@ namespace Game.Networking { SendAuthResponseError(BattlenetRpcErrorCode.RiskAccountLocked); Log.outDebug(LogFilter.Network, "HandleAuthSession: Sent Auth Response (Account IP differs)."); + // We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well + Global.ScriptMgr.OnFailedAccountLogin(account.game.Id); CloseSocket(); return; } @@ -596,6 +598,8 @@ namespace Game.Networking { SendAuthResponseError(BattlenetRpcErrorCode.RiskAccountLocked); Log.outDebug(LogFilter.Network, "WorldSocket.HandleAuthSession: Sent Auth Response (Account country differs. Original country: {0}, new country: {1}).", account.battleNet.LockCountry, _ipCountry); + // We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well + Global.ScriptMgr.OnFailedAccountLogin(account.game.Id); CloseSocket(); return; } @@ -617,6 +621,7 @@ namespace Game.Networking { SendAuthResponseError(BattlenetRpcErrorCode.GameAccountBanned); Log.outError(LogFilter.Network, "WorldSocket:HandleAuthSession: Sent Auth Response (Account banned)."); + Global.ScriptMgr.OnFailedAccountLogin(account.game.Id); CloseSocket(); return; } @@ -627,6 +632,7 @@ namespace Game.Networking { SendAuthResponseError(BattlenetRpcErrorCode.ServerIsPrivate); Log.outInfo(LogFilter.Network, "WorldSocket:HandleAuthSession: User tries to login but his security level is not enough"); + Global.ScriptMgr.OnFailedAccountLogin(account.game.Id); CloseSocket(); return; } @@ -642,6 +648,9 @@ namespace Game.Networking DB.Login.Execute(stmt); } + // At this point, we can safely hook a successful login + Global.ScriptMgr.OnAccountLogin(account.game.Id); + _worldSession = new WorldSession(account.game.Id, authSession.RealmJoinTicket, account.battleNet.Id, this, account.game.Security, (Expansion)account.game.Expansion, mutetime, account.game.OS, account.battleNet.Locale, account.game.Recruiter, account.game.IsRectuiter); diff --git a/Source/Game/Scripting/CoreScripts.cs b/Source/Game/Scripting/CoreScripts.cs index c2b578420..5ae72a7a1 100644 --- a/Source/Game/Scripting/CoreScripts.cs +++ b/Source/Game/Scripting/CoreScripts.cs @@ -653,7 +653,7 @@ namespace Game.Scripting public virtual void OnSpellCast(Player player, Spell spell, bool skipCheck) { } // Called when a player logs in. - public virtual void OnLogin(Player player) { } + public virtual void OnLogin(Player player, bool firstLogin) { } // Called when a player logs out. public virtual void OnLogout(Player player) { } @@ -692,6 +692,32 @@ namespace Game.Scripting public virtual void OnPlayerChoiceResponse(Player player, uint choiceId, uint responseId) { } } + public class AccountScript : ScriptObject + { + public AccountScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + // Called when an account logged in succesfully + public virtual void OnAccountLogin(uint accountId) { } + + // Called when an account login failed + public virtual void OnFailedAccountLogin(uint accountId) { } + + // Called when Email is successfully changed for Account + public virtual void OnEmailChange(uint accountId) { } + + // Called when Email failed to change for Account + public virtual void OnFailedEmailChange(uint accountId) { } + + // Called when Password is successfully changed for Account + public virtual void OnPasswordChange(uint accountId) { } + + // Called when Password failed to change for Account + public virtual void OnFailedPasswordChange(uint accountId) { } + } + public class GuildScript : ScriptObject { public GuildScript(string name) : base(name) diff --git a/Source/Game/Scripting/ScriptManager.cs b/Source/Game/Scripting/ScriptManager.cs index 017045ae6..5db226f3e 100644 --- a/Source/Game/Scripting/ScriptManager.cs +++ b/Source/Game/Scripting/ScriptManager.cs @@ -147,6 +147,7 @@ namespace Game.Scripting case "TransportScript": case "AchievementCriteriaScript": case "PlayerScript": + case "AccountScript": case "GuildScript": case "GroupScript": case "AreaTriggerEntityScript": @@ -267,7 +268,7 @@ namespace Game.Scripting { UnitAI.FillAISpellInfo(); } - + public List GetSplineChain(Creature who, ushort chainId) { return GetSplineChain(who.GetEntry(), chainId); @@ -762,7 +763,7 @@ namespace Game.Scripting RunScript(p => p.OnCompleted(player, achievement), Global.AchievementMgr.GetAchievementScriptId(achievement.Id)); } - + // AchievementCriteriaScript public bool OnCriteriaCheck(uint ScriptId, Player source, Unit target) { @@ -855,9 +856,9 @@ namespace Game.Scripting { ForEach(p => p.OnSpellCast(player, spell, skipCheck)); } - public void OnPlayerLogin(Player player) + public void OnPlayerLogin(Player player, bool firstLogin) { - ForEach(p => p.OnLogin(player)); + ForEach(p => p.OnLogin(player, firstLogin)); } public void OnPlayerLogout(Player player) { @@ -904,6 +905,32 @@ namespace Game.Scripting ForEach(p => p.OnPlayerChoiceResponse(player, choiceId, responseId)); } + // Account + public void OnAccountLogin(uint accountId) + { + ForEach(script => script.OnAccountLogin(accountId)); + } + public void OnFailedAccountLogin(uint accountId) + { + ForEach(script => script.OnFailedAccountLogin(accountId)); + } + public void OnEmailChange(uint accountId) + { + ForEach(script => script.OnEmailChange(accountId)); + } + public void OnFailedEmailChange(uint accountId) + { + ForEach(script => script.OnFailedEmailChange(accountId)); + } + public void OnPasswordChange(uint accountId) + { + ForEach(script => script.OnPasswordChange(accountId)); + } + public void OnFailedPasswordChange(uint accountId) + { + ForEach(script => script.OnFailedPasswordChange(accountId)); + } + // GuildScript public void OnGuildAddMember(Guild guild, Player player, byte plRank) { @@ -1172,7 +1199,7 @@ namespace Game.Scripting uint _ScriptCount; public Dictionary spellSummaryStorage = new(); Hashtable ScriptStorage = new(); - + // creature entry + chain ID MultiMap, SplineChainLink> m_mSplineChainsMap = new(); // spline chains } diff --git a/Source/Scripts/Pets/DeathKnight.cs b/Source/Scripts/Pets/DeathKnight.cs index d6cfb745c..e6d76de93 100644 --- a/Source/Scripts/Pets/DeathKnight.cs +++ b/Source/Scripts/Pets/DeathKnight.cs @@ -1,4 +1,4 @@ -// Copyright (c) CypherCore All rights reserved. +// 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; @@ -10,102 +10,96 @@ using Game.Spells; using System; using System.Collections.Generic; -namespace Scripts.Pets +namespace Scripts.Pets.DeathKnight { - namespace DeathKnight + [Script] + class npc_pet_dk_ebon_gargoyle : CasterAI { - struct SpellIds - { - public const uint SummonGargoyle1 = 49206; - public const uint SummonGargoyle2 = 50514; - public const uint DismissGargoyle = 50515; - public const uint Sanctuary = 54661; - } + const uint SpellSummonGargoyle1 = 49206; + const uint SpellSummonGargoyle2 = 50514; + const uint SpellDismissGargoyle = 50515; + const uint SpellSanctuary = 54661; - [Script] - class npc_pet_dk_ebon_gargoyle : CasterAI - { - public npc_pet_dk_ebon_gargoyle(Creature creature) : base(creature) { } + public npc_pet_dk_ebon_gargoyle(Creature creature) : base(creature) { } - public override void InitializeAI() + public override void InitializeAI() + { + base.InitializeAI(); + ObjectGuid ownerGuid = me.GetOwnerGUID(); + if (ownerGuid.IsEmpty()) + return; + + // Find victim of Summon Gargoyle spell + List targets = new(); + AnyUnfriendlyUnitInObjectRangeCheck u_check = new(me, me, 30.0f); + UnitListSearcher searcher = new(me, targets, u_check); + Cell.VisitAllObjects(me, searcher, 30.0f); + foreach (Unit target in targets) { - base.InitializeAI(); - ObjectGuid ownerGuid = me.GetOwnerGUID(); - if (ownerGuid.IsEmpty()) - return; - - // Find victim of Summon Gargoyle spell - List targets = new(); - var u_check = new AnyUnfriendlyUnitInObjectRangeCheck(me, me, 30.0f); - var searcher = new UnitListSearcher(me, targets, u_check); - Cell.VisitAllObjects(me, searcher, 30.0f); - foreach (var target in targets) + if (target.HasAura(SpellSummonGargoyle1, ownerGuid)) { - if (target.HasAura(SpellIds.SummonGargoyle1, ownerGuid)) - { - me.Attack(target, false); - break; - } + me.Attack(target, false); + break; } } - - public override void JustDied(Unit killer) - { - // Stop Feeding Gargoyle when it dies - Unit owner = me.GetOwner(); - if (owner) - owner.RemoveAurasDueToSpell(SpellIds.SummonGargoyle2); - } - - // Fly away when dismissed - public override void SpellHit(WorldObject caster, SpellInfo spellInfo) - { - if (spellInfo.Id != SpellIds.DismissGargoyle || !me.IsAlive()) - return; - - Unit owner = me.GetOwner(); - if (!owner || owner != caster) - return; - - // Stop Fighting - me.SetUnitFlag(UnitFlags.NonAttackable); - - // Sanctuary - me.CastSpell(me, SpellIds.Sanctuary, true); - me.SetReactState(ReactStates.Passive); - - //! HACK: Creature's can't have MOVEMENTFLAG_FLYING - // Fly Away - me.SetCanFly(true); - me.SetSpeedRate(UnitMoveType.Flight, 0.75f); - me.SetSpeedRate(UnitMoveType.Run, 0.75f); - float x = me.GetPositionX() + 20 * (float)Math.Cos(me.GetOrientation()); - float y = me.GetPositionY() + 20 * (float)Math.Sin(me.GetOrientation()); - float z = me.GetPositionZ() + 40; - me.GetMotionMaster().Clear(); - me.GetMotionMaster().MovePoint(0, x, y, z); - - // Despawn as soon as possible - me.DespawnOrUnsummon(TimeSpan.FromSeconds(4)); - } - - } - [Script] - class npc_pet_dk_guardian : AggressorAI + public override void JustDied(Unit killer) { - public npc_pet_dk_guardian(Creature creature) : base(creature) { } + // Stop Feeding Gargoyle when it dies + Unit owner = me.GetOwner(); + if (owner != null) + owner.RemoveAurasDueToSpell(SpellSummonGargoyle2); + } - public override bool CanAIAttack(Unit target) - { - if (!target) - return false; - Unit owner = me.GetOwner(); - if (owner && !target.IsInCombatWith(owner)) - return false; - return base.CanAIAttack(target); - } + // Fly away when dismissed + public override void SpellHit(WorldObject caster, SpellInfo spellInfo) + { + if (spellInfo.Id != SpellDismissGargoyle || !me.IsAlive()) + return; + + Unit owner = me.GetOwner(); + if (owner == null || owner != caster) + return; + + // Stop Fighting + me.SetUnitFlag(UnitFlags.NonAttackable); + + // Sanctuary + me.CastSpell(me, SpellSanctuary, true); + me.SetReactState(ReactStates.Passive); + + //! Hack: Creature's can't have MovementflagFlying + // Fly Away + me.SetCanFly(true); + me.SetSpeedRate(UnitMoveType.Flight, 0.75f); + me.SetSpeedRate(UnitMoveType.Run, 0.75f); + float x = me.GetPositionX() + 20 * MathF.Cos(me.GetOrientation()); + float y = me.GetPositionY() + 20 * MathF.Sin(me.GetOrientation()); + float z = me.GetPositionZ() + 40; + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MovePoint(0, x, y, z); + + // Despawn as soon as possible + me.DespawnOrUnsummon(TimeSpan.FromSeconds(4)); } } -} + + [Script] + class npc_pet_dk_guardian : AggressorAI + { + public npc_pet_dk_guardian(Creature creature) : base(creature) { } + + public override bool CanAIAttack(Unit target) + { + if (target == null) + return false; + + Unit owner = me.GetOwner(); + if (owner != null && !target.IsInCombatWith(owner)) + return false; + + return base.CanAIAttack(target); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/Pets/Generic.cs b/Source/Scripts/Pets/Generic.cs index 7e628539a..85ac238db 100644 --- a/Source/Scripts/Pets/Generic.cs +++ b/Source/Scripts/Pets/Generic.cs @@ -1,200 +1,252 @@ -// Copyright (c) CypherCore All rights reserved. +// 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.Dynamic; using Game.AI; using Game.Entities; using Game.Scripting; using Game.Spells; +using System; using System.Collections.Generic; -namespace Scripts.Pets +namespace Scripts.Pets.Generic { - namespace Generic + [Script] + class npc_pet_gen_pandaren_monk : NullCreatureAI { - struct SpellIds + const uint SpellPandarenMonk = 69800; + + Action focusAction; + + public npc_pet_gen_pandaren_monk(Creature creature) : base(creature) { - //Mojo - public const uint FeelingFroggy = 43906; - public const uint SeductionVisual = 43919; - - //SoulTrader - public const uint EtherealOnSummon = 50052; - public const uint EtherealPetRemoveAura = 50055; - - // LichPet - public const uint LichPetAura = 69732; - public const uint LichPetAuraOnkill = 69731; - public const uint LichPetEmote = 70049; - } - - struct CreatureIds - { - // LichPet - public const uint LichPet = 36979; - } - - struct TextIds - { - //Mojo - public const uint SayMojo = 0; - - //SoulTrader - public const uint SaySoulTraderInto = 0; - } - - [Script] - class npc_pet_gen_soul_trader : ScriptedAI - { - public npc_pet_gen_soul_trader(Creature creature) : base(creature) { } - - public override void OnDespawn() + focusAction = task => { - Unit owner = me.GetOwner(); + Unit owner = me.GetCharmerOrOwner(); if (owner != null) - DoCast(owner, SpellIds.EtherealPetRemoveAura); - } + me.SetFacingToObject(owner); - public override void JustAppeared() + _scheduler.Schedule(TimeSpan.FromSeconds(1), _ => + { + me.HandleEmoteCommand(Emote.OneshotBow); + _scheduler.Schedule(TimeSpan.FromSeconds(1), _ => + { + Unit owner = me.GetCharmerOrOwner(); + if (owner != null) + me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, SharedConst.PetFollowAngle); + }); + }); + }; + } + + public override void Reset() + { + _scheduler.CancelAll(); + _scheduler.Schedule(TimeSpan.FromSeconds(1), focusAction); + } + + public override void EnterEvadeMode(EvadeReason why) + { + if (!_EnterEvadeMode(why)) + return; + + Reset(); + } + + public override void ReceiveEmote(Player player, TextEmotes emote) + { + me.InterruptSpell(CurrentSpellTypes.Channeled); + me.StopMoving(); + + switch (emote) { - Talk(TextIds.SaySoulTraderInto); - - Unit owner = me.GetOwner(); - if (owner != null) - DoCast(owner, SpellIds.EtherealOnSummon); - - base.JustAppeared(); + case TextEmotes.Bow: + _scheduler.Schedule(TimeSpan.FromSeconds(1), focusAction); + break; + case TextEmotes.Drink: + _scheduler.Schedule(TimeSpan.FromSeconds(1), _ => me.CastSpell(me, SpellPandarenMonk, false)); + break; } } - [Script] // 69735 - Lich Pet OnSummon - class spell_gen_lich_pet_onsummon : SpellScript + public override void UpdateAI(uint diff) { - public override bool Validate(SpellInfo spellInfo) - { - return ValidateSpellInfo(SpellIds.LichPetAura); - } + _scheduler.Update(diff); - void HandleScriptEffect(uint effIndex) - { - Unit target = GetHitUnit(); - target.CastSpell(target, SpellIds.LichPetAura, true); - } - - public override void Register() - { - OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); - } - } - - [Script] // 69736 - Lich Pet Aura Remove - class spell_gen_lich_pet_aura_remove : SpellScript - { - public override bool Validate(SpellInfo spellInfo) - { - return ValidateSpellInfo(SpellIds.LichPetAura); - } - - void HandleScriptEffect(uint effIndex) - { - GetHitUnit().RemoveAurasDueToSpell(SpellIds.LichPetAura); - } - - public override void Register() - { - OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); - } - } - - [Script] // 69732 - Lich Pet Aura - class spell_gen_lich_pet_aura : AuraScript - { - public override bool Validate(SpellInfo spellInfo) - { - return ValidateSpellInfo(SpellIds.LichPetAuraOnkill); - } - - bool CheckProc(ProcEventInfo eventInfo) - { - return eventInfo.GetProcTarget().IsPlayer(); - } - - void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) - { - PreventDefaultAction(); - - List minionList = new(); - GetUnitOwner().GetAllMinionsByEntry(minionList, CreatureIds.LichPet); - foreach (Creature minion in minionList) - if (minion.IsAIEnabled()) - minion.GetAI().DoCastSelf(SpellIds.LichPetAuraOnkill); - } - - public override void Register() - { - DoCheckProc.Add(new CheckProcHandler(CheckProc)); - OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); - } - } - - [Script] // 70050 - [DND] Lich Pet - class spell_pet_gen_lich_pet_periodic_emote : AuraScript - { - public override bool Validate(SpellInfo spellInfo) - { - return ValidateSpellInfo(SpellIds.LichPetEmote); - } - - void OnPeriodic(AuraEffect aurEff) - { - // The chance to cast this spell is not 100%. - // Triggered spell roots creature for 3 sec and plays anim and sound (doesn't require any script). - // Emote and sound never shows up in sniffs because both comes from spell visual directly. - // Both 69683 and 70050 can trigger spells at once and are not linked together in any way. - // Effect of 70050 is overlapped by effect of 69683 but not instantly (69683 is a series of spell casts, takes longer to execute). - // However, for some reason emote is not played if creature is idle and only if creature is moving or is already rooted. - // For now it's scripted manually in script below to play emote always. - if (RandomHelper.randChance(50)) - GetTarget().CastSpell(GetTarget(), SpellIds.LichPetEmote, true); - } - - public override void Register() - { - OnEffectPeriodic.Add(new EffectPeriodicHandler(OnPeriodic, 0, AuraType.PeriodicTriggerSpell)); - } - } - - [Script] // 70049 - [DND] Lich Pet - class spell_pet_gen_lich_pet_emote : AuraScript - { - void AfterApply(AuraEffect aurEff, AuraEffectHandleModes mode) - { - GetTarget().HandleEmoteCommand(Emote.OneshotCustomSpell01); - } - - public override void Register() - { - AfterEffectApply.Add(new EffectApplyHandler(AfterApply, 0, AuraType.ModRoot, AuraEffectHandleModes.Real)); - } - } - - [Script] // 69682 - Lil' K.T. Focus - class spell_pet_gen_lich_pet_focus : SpellScript - { - public override bool Validate(SpellInfo spellInfo) - { - return ValidateSpellInfo((uint)spellInfo.GetEffect(0).CalcValue()); - } - - void HandleScript(uint effIndex) - { - GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue()); - } - - public override void Register() - { - OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); - } + Unit owner = me.GetCharmerOrOwner(); + if (owner != null) + if (!me.IsWithinDist(owner, 30.0f)) + me.InterruptSpell(CurrentSpellTypes.Channeled); } } -} + + [Script] + class npc_pet_gen_soul_trader : ScriptedAI + { + const uint SaySoulTraderIntro = 0; + + const uint SpellEtherealOnsummon = 50052; + const uint SpellEtherealPetRemoveAura = 50055; + + public npc_pet_gen_soul_trader(Creature creature) : base(creature) { } + + public override void OnDespawn() + { + Unit owner = me.GetOwner(); + if (owner != null) + DoCast(owner, SpellEtherealPetRemoveAura); + } + + public override void JustAppeared() + { + Talk(SaySoulTraderIntro); + Unit owner = me.GetOwner(); + if (owner != null) + DoCast(owner, SpellEtherealOnsummon); + + base.JustAppeared(); + } + } + + [Script] // 69735 - Lich Pet OnSummon + class spell_pet_gen_lich_pet_onsummon : SpellScript + { + const uint SpellLichPetAura = 69732; + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellLichPetAura); + } + + void HandleScript(uint effIndex) + { + Unit target = GetHitUnit(); + target.CastSpell(target, SpellLichPetAura, true); + } + + public override void Register() + { + OnEffectHitTarget.Add(new(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + [Script] // 69736 - Lich Pet Aura Remove + class spell_pet_gen_lich_pet_aura_Remove : SpellScript + { + const uint SpellLichPetAura = 69732; + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellLichPetAura); + } + + void HandleScript(uint effIndex) + { + GetHitUnit().RemoveAurasDueToSpell(SpellLichPetAura); + } + + public override void Register() + { + OnEffectHitTarget.Add(new(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } + + [Script] // 69732 - Lich Pet Aura + class spell_pet_gen_lich_pet_AuraScript : AuraScript + { + const uint SpellLichPetAuraOnkill = 69731; + + const uint NpcLichPet = 36979; + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellLichPetAuraOnkill); + } + + bool CheckProc(ProcEventInfo eventInfo) + { + return eventInfo.GetProcTarget().IsPlayer(); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) + { + PreventDefaultAction(); + + Unit owner = GetUnitOwner(); + + List minionList = new(); + owner.GetAllMinionsByEntry(minionList, NpcLichPet); + foreach (TempSummon minion in minionList) + owner.CastSpell(minion, SpellLichPetAuraOnkill, true); + } + + public override void Register() + { + DoCheckProc.Add(new(CheckProc)); + OnEffectProc.Add(new(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } + + [Script] // 70050 - [Dnd] Lich Pet + class spell_pet_gen_lich_pet_periodic_emote : AuraScript + { + const uint SpellLichPetEmote = 70049; + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellLichPetEmote); + } + + void OnPeriodic(AuraEffect aurEff) + { + // The chance to cast this spell is not 100%. + // Triggered spell roots creature for 3 sec and plays anim and sound (doesn't require any script). + // Emote and sound never shows up in sniffs because both comes from spell visual directly. + // Both 69683 and 70050 can trigger spells at once and are not linked together in any way. + // Effect of 70050 is overlapped by effect of 69683 but not instantly (69683 is a series of spell casts, takes longer to execute). + // However, for some reason emote is not played if creature is idle and only if creature is moving or is already rooted. + // For now it's scripted manually in script below to play emote always. + if (RandomHelper.randChance(50)) + GetTarget().CastSpell(GetTarget(), SpellLichPetEmote, true); + } + + public override void Register() + { + OnEffectPeriodic.Add(new(OnPeriodic, 0, AuraType.PeriodicTriggerSpell)); + } + } + + [Script] // 70049 - [Dnd] Lich Pet + class spell_pet_gen_lich_pet_emote : AuraScript + { + void AfterApply(AuraEffect aurEff, AuraEffectHandleModes mode) + { + GetTarget().HandleEmoteCommand(Emote.OneshotCustomSpell01); + } + + public override void Register() + { + AfterEffectApply.Add(new(AfterApply, 0, AuraType.ModRoot, AuraEffectHandleModes.Real)); + } + } + + [Script] // 69682 - Lil' K.T. Focus + class spell_pet_gen_lich_pet_focus : SpellScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo((uint)spellInfo.GetEffect(0).CalcValue()); + } + + void HandleScript(uint effIndex) + { + GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue()); + } + + public override void Register() + { + OnEffectHitTarget.Add(new(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/Pets/Hunter.cs b/Source/Scripts/Pets/Hunter.cs index f3572ca75..e0c381a3c 100644 --- a/Source/Scripts/Pets/Hunter.cs +++ b/Source/Scripts/Pets/Hunter.cs @@ -1,4 +1,4 @@ -// Copyright (c) CypherCore All rights reserved. +// 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; @@ -6,108 +6,99 @@ using Game.AI; using Game.Combat; using Game.Entities; using Game.Scripting; -using Game.Spells; using System.Collections.Generic; -namespace Scripts.Pets +namespace Scripts.Pets.Hunter { - namespace Hunter + [Script] + class npc_pet_hunter_snake_trap : ScriptedAI { - struct SpellIds + const uint SpellHunterCripplingPoison = 30981; // Viper + const uint SpellHunterDeadlyPoisonPassive = 34657; // Venomous Snake + const uint SpellHunterMindNumbingPoison = 25810; // Viper + + const uint NpcHunterViper = 19921; + + bool _isViper; + uint _spellTimer; + + public npc_pet_hunter_snake_trap(Creature creature) : base(creature) { } + + public override void JustEngagedWith(Unit who) { } + + public override void JustAppeared() { - public const uint CripplingPoison = 30981; // Viper - public const uint DeadlyPoisonPassive = 34657; // Venomous Snake - public const uint MindNumbingPoison = 25810; // Viper + _isViper = me.GetEntry() == NpcHunterViper ? true : false; + + me.SetMaxHealth((uint)(107 * (me.GetLevel() - 40) * 0.025f)); + // Add delta to make them not all hit the same time + me.SetBaseAttackTime(WeaponAttackType.BaseAttack, me.GetBaseAttackTime(WeaponAttackType.BaseAttack) + RandomHelper.URand(0, 6)); + + if (!_isViper && !me.HasAura(SpellHunterDeadlyPoisonPassive)) + DoCast(me, SpellHunterDeadlyPoisonPassive, true); } - struct CreatureIds + // Redefined for random target selection: + public override void MoveInLineOfSight(Unit who) { } + + public override void UpdateAI(uint diff) { - public const int Viper = 19921; - } - - [Script] - class npc_pet_hunter_snake_trap : ScriptedAI - { - public npc_pet_hunter_snake_trap(Creature creature) : base(creature) { } - - public override void JustEngagedWith(Unit who) { } - - public override void JustAppeared() - { - _isViper = me.GetEntry() == CreatureIds.Viper ? true : false; - - me.SetMaxHealth((uint)(107 * (me.GetLevel() - 40) * 0.025f)); - // Add delta to make them not all hit the same time - me.SetBaseAttackTime(WeaponAttackType.BaseAttack, me.GetBaseAttackTime(WeaponAttackType.BaseAttack) + RandomHelper.URand(0, 6) * Time.InMilliseconds); - - if (!_isViper && !me.HasAura(SpellIds.DeadlyPoisonPassive)) - DoCast(me, SpellIds.DeadlyPoisonPassive, new CastSpellExtraArgs(true)); + if (me.GetVictim() != null && me.GetVictim().HasBreakableByDamageCrowdControlAura()) + { // don't break cc + me.GetThreatManager().ClearFixate(); + me.InterruptNonMeleeSpells(false); + me.AttackStop(); + return; } - // Redefined for random target selection: - public override void MoveInLineOfSight(Unit who) { } + if (me.IsSummon() && me.GetThreatManager().GetFixateTarget() == null) + { // find new target + Unit summoner = me.ToTempSummon().GetSummonerUnit(); - public override void UpdateAI(uint diff) - { - if (me.GetVictim() && me.GetVictim().HasBreakableByDamageCrowdControlAura()) - { // don't break cc - me.GetThreatManager().ClearFixate(); - me.InterruptNonMeleeSpells(false); - me.AttackStop(); - return; + List targets = new(); + + void addTargetIfValid(CombatReference refe) + { + Unit enemy = refe.GetOther(summoner); + if (!enemy.HasBreakableByDamageCrowdControlAura() && me.CanCreatureAttack(enemy) && me.IsWithinDistInMap(enemy, me.GetAttackDistance(enemy))) + targets.Add(enemy); } - if (me.IsSummon() && !me.GetThreatManager().GetFixateTarget()) - { // find new target - Unit summoner = me.ToTempSummon().GetSummonerUnit(); - List targets = new(); + foreach (var pair in summoner.GetCombatManager().GetPvPCombatRefs()) + addTargetIfValid(pair.Value); - void addTargetIfValid(CombatReference refe) - { - Unit enemy = refe.GetOther(summoner); - if (!enemy.HasBreakableByDamageCrowdControlAura() && me.CanCreatureAttack(enemy) && me.IsWithinDistInMap(enemy, me.GetAttackDistance(enemy))) - targets.Add(enemy); - } - - foreach (var pair in summoner.GetCombatManager().GetPvPCombatRefs()) + if (targets.Empty()) + foreach (var pair in summoner.GetCombatManager().GetPvECombatRefs()) addTargetIfValid(pair.Value); - if (targets.Empty()) - foreach (var pair in summoner.GetCombatManager().GetPvECombatRefs()) - addTargetIfValid(pair.Value); + foreach (Unit target in targets) + me.EngageWithTarget(target); - foreach (Unit target in targets) - me.EngageWithTarget(target); - - if (!targets.Empty()) - { - Unit target = targets.SelectRandom(); - me.GetThreatManager().FixateTarget(target); - } - } - - if (!UpdateVictim()) - return; - - // Viper - if (_isViper) + if (!targets.Empty()) { - if (_spellTimer <= diff) - { - if (RandomHelper.URand(0, 2) == 0) // 33% chance to cast - DoCastVictim(RandomHelper.RAND(SpellIds.MindNumbingPoison, SpellIds.CripplingPoison)); - - _spellTimer = 3000; - } - else - _spellTimer -= diff; + Unit target = targets.SelectRandom(); + me.GetThreatManager().FixateTarget(target); } - - DoMeleeAttackIfReady(); } - bool _isViper; - uint _spellTimer; + if (!UpdateVictim()) + return; + + // Viper + if (_isViper) + { + if (_spellTimer <= diff) + { + if (RandomHelper.URand(0, 2) == 0) // 33% chance to cast + DoCastVictim(RandomHelper.RAND(SpellHunterMindNumbingPoison, SpellHunterCripplingPoison)); + + _spellTimer = 3000; + } + else + _spellTimer -= diff; + } + + DoMeleeAttackIfReady(); } } -} +} \ No newline at end of file diff --git a/Source/Scripts/Pets/Mage.cs b/Source/Scripts/Pets/Mage.cs index c4c19bb23..0c659c82a 100644 --- a/Source/Scripts/Pets/Mage.cs +++ b/Source/Scripts/Pets/Mage.cs @@ -1,4 +1,4 @@ -// Copyright (c) CypherCore All rights reserved. +// 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; @@ -6,175 +6,161 @@ using Game.AI; using Game.Entities; using Game.Scripting; -namespace Scripts.Pets +namespace Scripts.Pets.Mage { - namespace Mage + [Script] + class npc_pet_mage_mirror_image : ScriptedAI { - struct SpellIds + const uint SpellMageCloneMe = 45204; + const uint SpellMageFrostBolt = 59638; + const uint SpellMageFireBlast = 59637; + + const uint TimerMirrorImageFireBlast = 6500; + float ChaseDistance = 35.0f; + + uint _fireBlastTimer = 0; + + public npc_pet_mage_mirror_image(Creature creature) : base(creature) { } + + public override void InitializeAI() { - public const uint CloneMe = 45204; - public const uint MastersThreatList = 58838; - public const uint MageFrostBolt = 59638; - public const uint MageFireBlast = 59637; + Unit owner = me.GetOwner(); + if (owner == null) + return; + + // here mirror image casts on summoner spell (not present in client dbc) 49866 + // here should be auras (not present in client dbc): 35657, 35658, 35659, 35660 selfcast by mirror images (stats related?) + // Clone Me! + owner.CastSpell(me, SpellMageCloneMe, true); } - struct MiscConst + // custom UpdateVictim implementation to handle special target selection + // we prioritize between things that are in combat with owner based on the owner's threat to them + bool UpdateVictimCustom() { - public const uint TimerMirrorImageInit = 0; - public const uint TimerMirrorImageFrostBolt = 4000; - public const uint TimerMirrorImageFireBlast = 6000; - } + Unit owner = me.GetOwner(); + if (owner == null) + return false; - [Script] - class npc_pet_mage_mirror_image : ScriptedAI - { - const float CHASE_DISTANCE = 35.0f; + if (!me.HasUnitState(UnitState.Casting) && !me.IsInCombat() && !owner.IsInCombat()) + return false; - uint _fireBlastTimer = 0; - - public npc_pet_mage_mirror_image(Creature creature) : base(creature) { } - - public override void InitializeAI() + Unit currentTarget = me.GetVictim(); + if (currentTarget != null && !CanAIAttack(currentTarget)) { - Unit owner = me.GetOwner(); - if (owner == null) - return; - - // here mirror image casts on summoner spell (not present in client dbc) 49866 - // here should be auras (not present in client dbc): 35657, 35658, 35659, 35660 selfcast by mirror images (stats related?) - // Clone Me! - owner.CastSpell(me, SpellIds.CloneMe, true); + me.InterruptNonMeleeSpells(true); // do not finish casting on invalid targets + me.AttackStop(); + currentTarget = null; } - // custom UpdateVictim implementation to handle special target selection - // we prioritize between things that are in combat with owner based on the owner's threat to them - new bool UpdateVictim() - { - Unit owner = me.GetOwner(); - if (owner == null) - return false; - - if (!me.HasUnitState(UnitState.Casting) && !me.IsInCombat() && !owner.IsInCombat()) - return false; - - Unit currentTarget = me.GetVictim(); - if (currentTarget && !CanAIAttack(currentTarget)) - { - me.InterruptNonMeleeSpells(true); // do not finish casting on invalid targets - me.AttackStop(); - currentTarget = null; - } - - // don't reselect if we're currently casting anyway - if (currentTarget && me.HasUnitState(UnitState.Casting)) - return true; - - Unit selectedTarget = null; - var mgr = owner.GetCombatManager(); - if (mgr.HasPvPCombat()) - { // select pvp target - float minDistance = 0.0f; - foreach (var pair in mgr.GetPvPCombatRefs()) - { - Unit target = pair.Value.GetOther(owner); - if (!target.IsPlayer()) - continue; - - if (!CanAIAttack(target)) - continue; - - float dist = owner.GetDistance(target); - if (!selectedTarget || dist < minDistance) - { - selectedTarget = target; - minDistance = dist; - } - } - } - - if (!selectedTarget) - { // select pve target - float maxThreat = 0.0f; - foreach (var pair in mgr.GetPvECombatRefs()) - { - Unit target = pair.Value.GetOther(owner); - if (!CanAIAttack(target)) - continue; - - float threat = target.GetThreatManager().GetThreat(owner); - if (threat >= maxThreat) - { - selectedTarget = target; - maxThreat = threat; - } - } - } - - if (!selectedTarget) - { - EnterEvadeMode(EvadeReason.NoHostiles); - return false; - } - - if (selectedTarget != me.GetVictim()) - AttackStartCaster(selectedTarget, CHASE_DISTANCE); + // don't reselect if we're currently casting anyway + if (currentTarget != null && me.HasUnitState(UnitState.Casting)) return true; + + Unit selectedTarget = null; + var mgr = owner.GetCombatManager(); + if (mgr.HasPvPCombat()) + { // select pvp target + float minDistance = 0.0f; + foreach (var pair in mgr.GetPvPCombatRefs()) + { + Unit target = pair.Value.GetOther(owner); + if (!target.IsPlayer()) + continue; + if (!CanAIAttack(target)) + continue; + + float dist = owner.GetDistance(target); + if (selectedTarget == null || dist < minDistance) + { + selectedTarget = target; + minDistance = dist; + } + } } - public override void UpdateAI(uint diff) + if (selectedTarget == null) + { // select pve target + float maxThreat = 0.0f; + foreach (var pair in mgr.GetPvECombatRefs()) + { + Unit target = pair.Value.GetOther(owner); + if (!CanAIAttack(target)) + continue; + + float threat = target.GetThreatManager().GetThreat(owner); + if (threat >= maxThreat) + { + selectedTarget = target; + maxThreat = threat; + } + } + } + + if (selectedTarget == null) { - Unit owner = me.GetOwner(); - if (owner == null) - { - me.DespawnOrUnsummon(); - return; - } + EnterEvadeMode(EvadeReason.NoHostiles); + return false; + } - if (_fireBlastTimer != 0) - { - if (_fireBlastTimer <= diff) - _fireBlastTimer = 0; - else - _fireBlastTimer -= diff; - } + if (selectedTarget != me.GetVictim()) + AttackStartCaster(selectedTarget, ChaseDistance); + return true; + } - if (!UpdateVictim()) - return; + public override void UpdateAI(uint diff) + { + Unit owner = me.GetOwner(); + if (owner == null) + { + me.DespawnOrUnsummon(); + return; + } - if (me.HasUnitState(UnitState.Casting)) - return; - - if (_fireBlastTimer == 0) - { - DoCastVictim(SpellIds.MageFireBlast); - _fireBlastTimer = MiscConst.TimerMirrorImageFireBlast; - } + if (_fireBlastTimer != 0) + { + if (_fireBlastTimer <= diff) + _fireBlastTimer = 0; else - DoCastVictim(SpellIds.MageFrostBolt); + _fireBlastTimer -= diff; } - public override bool CanAIAttack(Unit who) + if (!UpdateVictimCustom()) + return; + + if (me.HasUnitState(UnitState.Casting)) + return; + + if (_fireBlastTimer == 0) { - Unit owner = me.GetOwner(); - return owner && who.IsAlive() && me.IsValidAttackTarget(who) && - !who.HasBreakableByDamageCrowdControlAura() && - who.IsInCombatWith(owner) && CanAIAttack(who); + DoCastVictim(SpellMageFireBlast); + _fireBlastTimer = TimerMirrorImageFireBlast; } + else + DoCastVictim(SpellMageFrostBolt); + } - // Do not reload Creature templates on evade mode enter - prevent visual lost - public override void EnterEvadeMode(EvadeReason why) + public override bool CanAIAttack(Unit who) + { + Unit owner = me.GetOwner(); + return owner != null && who.IsAlive() && me.IsValidAttackTarget(who) && + !who.HasBreakableByDamageCrowdControlAura() && + who.IsInCombatWith(owner) && base.CanAIAttack(who); + } + + // Do not reload Creature templates on evade mode enter - prevent visual lost + public override void EnterEvadeMode(EvadeReason why) + { + if (me.IsInEvadeMode() || !me.IsAlive()) + return; + + Unit owner = me.GetCharmerOrOwner(); + + me.CombatStop(true); + if (owner != null && !me.HasUnitState(UnitState.Follow)) { - if (me.IsInEvadeMode() || !me.IsAlive()) - return; - - Unit owner = me.GetCharmerOrOwner(); - - me.CombatStop(true); - if (owner && !me.HasUnitState(UnitState.Follow)) - { - me.GetMotionMaster().Clear(); - me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle()); - } + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle()); } } } diff --git a/Source/Scripts/Pets/Priest.cs b/Source/Scripts/Pets/Priest.cs index c8e7518c3..4179e24a7 100644 --- a/Source/Scripts/Pets/Priest.cs +++ b/Source/Scripts/Pets/Priest.cs @@ -1,4 +1,4 @@ -// Copyright (c) CypherCore All rights reserved. +// 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; @@ -6,50 +6,70 @@ using Game.AI; using Game.Entities; using Game.Scripting; -namespace Scripts.Pets +namespace Scripts.Pets.Priest { - namespace Priest + [Script] // 198236 - Divine Image + class npc_pet_pri_divine_image : PassiveAI { - struct SpellIds + const uint SpellPriestDivineImageSpellCheck = 405216; + const uint SpellPriestInvokeTheNaaru = 196687; + + public npc_pet_pri_divine_image(Creature creature) : base(creature) { } + + public override void IsSummonedBy(WorldObject summoner) { - public const uint GlyphOfShadowFiend = 58228; - public const uint ShadowFiendDeath = 57989; - public const uint LightWellCharges = 59907; + me.CastSpell(me, SpellPriestInvokeTheNaaru); + + if (me.ToTempSummon().IsGuardian() && summoner.IsUnit()) + (me as Guardian).SetBonusDamage((int)summoner.ToUnit().SpellBaseHealingBonusDone(SpellSchoolMask.Holy)); } - [Script] - class npc_pet_pri_lightwell : PassiveAI + public override void OnDespawn() { - public npc_pet_pri_lightwell(Creature creature) : base(creature) - { - DoCast(creature, SpellIds.LightWellCharges, new Game.Spells.CastSpellExtraArgs(false)); - } - - public override void EnterEvadeMode(EvadeReason why) - { - if (!me.IsAlive()) - return; - - me.CombatStop(true); - EngagementOver(); - me.ResetPlayerDamageReq(); - } - } - - [Script] - class npc_pet_pri_shadowfiend : PetAI - { - public npc_pet_pri_shadowfiend(Creature creature) : base(creature) { } - - public override void IsSummonedBy(WorldObject summoner) - { - Unit unitSummoner = summoner.ToUnit(); - if (unitSummoner == null) - return; - - if (unitSummoner.HasAura(SpellIds.GlyphOfShadowFiend)) - DoCastAOE(SpellIds.ShadowFiendDeath); - } + Unit owner = me.GetOwner(); + if (owner != null) + owner.RemoveAura(SpellPriestDivineImageSpellCheck); } } -} + + [Script] // 189820 - Lightwell + class npc_pet_pri_lightwell : PassiveAI + { + const uint SpellPriestLightwellCharges = 59907; + + public npc_pet_pri_lightwell(Creature creature) : base(creature) + { + DoCast(me, SpellPriestLightwellCharges, false); + } + + public override void EnterEvadeMode(EvadeReason why) + { + if (!me.IsAlive()) + return; + + me.CombatStop(true); + EngagementOver(); + me.ResetPlayerDamageReq(); + } + } + + // 19668 - Shadowfiend + [Script] // 62982 - Mindbender + class npc_pet_pri_shadowfiend_mindbender : PetAI + { + const uint SpellPriestAtonement = 81749; + const uint SpellPriestAtonementPassive = 195178; + + public npc_pet_pri_shadowfiend_mindbender(Creature creature) : base(creature) { } + + public override void IsSummonedBy(WorldObject summonerWO) + { + Unit summoner = summonerWO.ToUnit(); + if (summoner == null) + return; + + if (summoner.HasAura(SpellPriestAtonement)) + DoCastSelf(SpellPriestAtonementPassive, TriggerCastFlags.FullMask); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/Pets/Shaman.cs b/Source/Scripts/Pets/Shaman.cs index f190f0bcc..ab033fbfc 100644 --- a/Source/Scripts/Pets/Shaman.cs +++ b/Source/Scripts/Pets/Shaman.cs @@ -1,4 +1,4 @@ -// Copyright (c) CypherCore All rights reserved. +// 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; @@ -7,85 +7,74 @@ using Game.Entities; using Game.Scripting; using System; -namespace Scripts.Pets +namespace Scripts.Pets.Shaman { - namespace Shaman + [Script] + class npc_pet_shaman_earth_elemental : ScriptedAI { - struct SpellIds - { - //npc_pet_shaman_earth_elemental - public const uint AngeredEarth = 36213; + const uint SpellShamanAngeredearth = 36213; - //npc_pet_shaman_fire_elemental - public const uint FireBlast = 57984; - public const uint FireNova = 12470; - public const uint FireShield = 13376; + public npc_pet_shaman_earth_elemental(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.CancelAll(); + _scheduler.Schedule(TimeSpan.FromSeconds(0), task => + { + DoCastVictim(SpellShamanAngeredearth); + task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)); + }); } - [Script] - class npc_pet_shaman_earth_elemental : ScriptedAI + public override void UpdateAI(uint diff) { - public npc_pet_shaman_earth_elemental(Creature creature) : base(creature) { } + if (!UpdateVictim()) + return; - public override void Reset() - { - _scheduler.CancelAll(); - _scheduler.Schedule(TimeSpan.FromSeconds(0), task => - { - DoCastVictim(SpellIds.AngeredEarth); - task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)); - }); - } + _scheduler.Update(diff); - public override void UpdateAI(uint diff) - { - if (!UpdateVictim()) - return; - - _scheduler.Update(diff); - - DoMeleeAttackIfReady(); - } - } - - [Script] - public class npc_pet_shaman_fire_elemental : ScriptedAI - { - public npc_pet_shaman_fire_elemental(Creature creature) : base(creature) { } - - public override void Reset() - { - _scheduler.CancelAll(); - _scheduler.Schedule(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20), task => - { - DoCastVictim(SpellIds.FireNova); - task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)); - }); - _scheduler.Schedule(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20), task => - { - DoCastVictim(SpellIds.FireShield); - task.Repeat(TimeSpan.FromSeconds(2)); - }); - _scheduler.Schedule(TimeSpan.FromSeconds(0), task => - { - DoCastVictim(SpellIds.FireBlast); - task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)); - }); - } - - public override void UpdateAI(uint diff) - { - if (!UpdateVictim()) - return; - - _scheduler.Update(diff); - - if (me.HasUnitState(UnitState.Casting)) - return; - - DoMeleeAttackIfReady(); - } + DoMeleeAttackIfReady(); } } -} + [Script] + class npc_pet_shaman_fire_elemental : ScriptedAI + { + const uint SpellShamanFireblast = 57984; + const uint SpellShamanFirenova = 12470; + const uint SpellShamanFireshield = 13376; + + public npc_pet_shaman_fire_elemental(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.CancelAll(); + _scheduler.Schedule(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20), task => + { + DoCastVictim(SpellShamanFirenova); + task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20), task => + { + DoCastVictim(SpellShamanFireblast); + task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(0), task => + { + DoCastVictim(SpellShamanFireshield); + task.Repeat(TimeSpan.FromSeconds(2)); + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (me.HasUnitState(UnitState.Casting)) + return; + + _scheduler.Update(diff, DoMeleeAttackIfReady); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/Shadowlands/SpellCovenant.cs b/Source/Scripts/Shadowlands/SpellCovenant.cs index 377bba03b..0ee867f9f 100644 --- a/Source/Scripts/Shadowlands/SpellCovenant.cs +++ b/Source/Scripts/Shadowlands/SpellCovenant.cs @@ -1,4 +1,4 @@ -// Copyright (c) CypherCore All rights reserved. +// 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; @@ -11,11 +11,11 @@ namespace Scripts.Shadowlands [Script] // 323916 - Sulfuric Emission class spell_soulbind_sulfuric_emission : AuraScript { - static uint SPELL_SULFURIC_EMISSION_COOLDOWN_AURA = 347684; + uint SpellSulfuricEmissionCooldownAura = 347684; public override bool Validate(SpellInfo spellInfo) { - return ValidateSpellInfo(SPELL_SULFURIC_EMISSION_COOLDOWN_AURA); + return ValidateSpellInfo(SpellSulfuricEmissionCooldownAura); } bool CheckProc(AuraEffect aurEff, ProcEventInfo procInfo) @@ -23,7 +23,7 @@ namespace Scripts.Shadowlands if (!procInfo.GetProcTarget().HealthBelowPct(aurEff.GetAmount())) return false; - if (procInfo.GetProcTarget().HasAura(SPELL_SULFURIC_EMISSION_COOLDOWN_AURA)) + if (procInfo.GetProcTarget().HasAura(SpellSulfuricEmissionCooldownAura)) return false; return true; @@ -31,27 +31,27 @@ namespace Scripts.Shadowlands public override void Register() { - DoCheckEffectProc.Add(new CheckEffectProcHandler(CheckProc, 0, AuraType.ProcTriggerSpell)); + DoCheckEffectProc.Add(new(CheckProc, 0, AuraType.ProcTriggerSpell)); } } [Script] // 332753 - Superior Tactics class spell_soulbind_superior_tactics : AuraScript { - static uint SPELL_SUPERIOR_TACTICS_COOLDOWN_AURA = 332926; + uint SpellSuperiorTacticsCooldownAura = 332926; public override bool Validate(SpellInfo spellInfo) { - return ValidateSpellInfo(SPELL_SUPERIOR_TACTICS_COOLDOWN_AURA); + return ValidateSpellInfo(SpellSuperiorTacticsCooldownAura); } bool CheckProc(AuraEffect aurEff, ProcEventInfo procInfo) { - if (GetTarget().HasAura(SPELL_SUPERIOR_TACTICS_COOLDOWN_AURA)) + if (GetTarget().HasAura(SpellSuperiorTacticsCooldownAura)) return false; // only dispels from friendly targets count - if (procInfo.GetHitMask().HasFlag(ProcFlagsHit.Dispel) && !procInfo.GetTypeMask().HasFlag(ProcFlags.DealHelpfulAbility | ProcFlags.DealHelpfulSpell | ProcFlags.DealHelpfulPeriodic)) + if ((procInfo.GetHitMask() & ProcFlagsHit.Dispel) != 0 && !(procInfo.GetTypeMask() & new ProcFlagsInit(ProcFlags.DealHelpfulAbility | ProcFlags.DealHelpfulSpell | ProcFlags.DealHelpfulPeriodic))) return false; return true; @@ -59,7 +59,7 @@ namespace Scripts.Shadowlands public override void Register() { - DoCheckEffectProc.Add(new CheckEffectProcHandler(CheckProc, 0, AuraType.ProcTriggerSpell)); + DoCheckEffectProc.Add(new(CheckProc, 0, AuraType.ProcTriggerSpell)); } } -} +} \ No newline at end of file diff --git a/Source/Scripts/Shadowlands/Torghast/SpellTorghast.cs b/Source/Scripts/Shadowlands/Torghast/SpellTorghast.cs index bded2d4ac..65e01a874 100644 --- a/Source/Scripts/Shadowlands/Torghast/SpellTorghast.cs +++ b/Source/Scripts/Shadowlands/Torghast/SpellTorghast.cs @@ -1,4 +1,4 @@ -// Copyright (c) CypherCore All rights reserved. +// 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; @@ -6,11 +6,37 @@ using Game.Entities; using Game.Scripting; using Game.Spells; using System.Collections.Generic; - using static Global; namespace Scripts.Shadowlands.Torghast { + [Script] // 297721 - Subjugator's Manacles + class spell_torghast_subjugators_manacles : AuraScript + { + List _triggeredTargets = new(); + + bool CheckProc(AuraEffect aurEff, ProcEventInfo procInfo) + { + if (_triggeredTargets.Contains(procInfo.GetProcTarget().GetGUID())) + return false; + + _triggeredTargets.Add(procInfo.GetProcTarget().GetGUID()); + return true; + } + + void ResetMarkedTargets(bool isNowInCombat) + { + if (!isNowInCombat) + _triggeredTargets.Clear(); + } + + public override void Register() + { + DoCheckEffectProc.Add(new(CheckProc, 0, AuraType.ProcTriggerSpell)); + OnEnterLeaveCombat.Add(new(ResetMarkedTargets)); + } + } + [Script] // 300771 - Blade of the Lifetaker class spell_torghast_blade_of_the_lifetaker : AuraScript { @@ -25,23 +51,23 @@ namespace Scripts.Shadowlands.Torghast public override void Register() { - OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + OnEffectProc.Add(new(HandleProc, 0, AuraType.ProcTriggerSpell)); } } [Script] // 300796 - Touch of the Unseen class spell_torghast_touch_of_the_unseen : AuraScript { - static uint SPELL_DOOR_OF_SHADOWS = 300728; + uint SpellDoorOfShadows = 300728; public override bool Validate(SpellInfo spellInfo) { - return ValidateSpellInfo(SPELL_DOOR_OF_SHADOWS); + return ValidateSpellInfo(SpellDoorOfShadows); } bool CheckProc(ProcEventInfo procInfo) { - return procInfo.GetSpellInfo() != null && procInfo.GetSpellInfo().Id == SPELL_DOOR_OF_SHADOWS; + return procInfo.GetSpellInfo() != null && procInfo.GetSpellInfo().Id == SpellDoorOfShadows; } void HandleProc(AuraEffect aurEff, ProcEventInfo procInfo) @@ -55,48 +81,48 @@ namespace Scripts.Shadowlands.Torghast public override void Register() { - DoCheckProc.Add(new CheckProcHandler(CheckProc)); - OnEffectProc.Add(new EffectProcHandler(HandleProc, 0, AuraType.ProcTriggerSpell)); + DoCheckProc.Add(new(CheckProc)); + OnEffectProc.Add(new(HandleProc, 0, AuraType.ProcTriggerSpell)); } } [Script] // 305060 - Yel'Shir's Powerglove class spell_torghast_yelshirs_powerglove : SpellScript { - void HandleEffect(uint effIndex) + void CalculateDamage(Unit victim, ref int damage, ref int flatMod, ref float pctMod) { SpellInfo triggeringSpell = GetTriggeringSpell(); if (triggeringSpell != null) { Aura triggerAura = GetCaster().GetAura(triggeringSpell.Id); if (triggerAura != null) - SetEffectValue(GetEffectValue() * triggerAura.GetStackAmount()); + pctMod *= triggerAura.GetStackAmount(); } } public override void Register() { - OnEffectLaunchTarget.Add(new EffectHandler(HandleEffect, 0, SpellEffectName.SchoolDamage)); + CalcDamage.Add(new(CalculateDamage)); } } [Script] // 321706 - Dimensional Blade class spell_torghast_dimensional_blade : SpellScript { - static uint SPELL_MAGE_BLINK = 1953; - static uint SPELL_MAGE_SHIMMER = 212653; + uint SpellMageBlink = 1953; + uint SpellMageShimmer = 212653; public override bool Validate(SpellInfo spellInfo) { - return ValidateSpellInfo(SPELL_MAGE_BLINK, SPELL_MAGE_SHIMMER); + return ValidateSpellInfo(SpellMageBlink, SpellMageShimmer); } void FilterTargets(List targets) { if (!targets.Empty()) { - GetCaster().GetSpellHistory().RestoreCharge(SpellMgr.GetSpellInfo(SPELL_MAGE_BLINK, Difficulty.None).ChargeCategoryId); - GetCaster().GetSpellHistory().RestoreCharge(SpellMgr.GetSpellInfo(SPELL_MAGE_SHIMMER, Difficulty.None).ChargeCategoryId); + GetCaster().GetSpellHistory().RestoreCharge(SpellMgr.GetSpellInfo(SpellMageBlink, Difficulty.None).ChargeCategoryId); + GetCaster().GetSpellHistory().RestoreCharge(SpellMgr.GetSpellInfo(SpellMageShimmer, Difficulty.None).ChargeCategoryId); } // filter targets by entry here and not with conditions table because we need to know if any enemy was hit for charge restoration, not just mawrats @@ -118,7 +144,190 @@ namespace Scripts.Shadowlands.Torghast public override void Register() { - OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(FilterTargets, 1, Targets.UnitDestAreaEnemy)); + OnObjectAreaTargetSelect.Add(new(FilterTargets, 1, Targets.UnitDestAreaEnemy)); } } -} + + [Script] // 341324 - Uncontrolled Darkness + class spell_torghast_uncontrolled_darkness : AuraScript + { + public int KillCounter; + + public override void Register() + { + // just a value holder, no hooks + } + } + + [Script] // 343174 - Uncontrolled Darkness + class spell_torghast_uncontrolled_darkness_proc : AuraScript + { + uint SpellUncontrolledDarkness = 341324; + uint SpellUncontrolledDarknessBuff = 341375; + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellEffect((SpellUncontrolledDarkness, 1)) + && ValidateSpellInfo(SpellUncontrolledDarknessBuff); + } + + void HandleProc(AuraEffect aurEff, ProcEventInfo procInfo) + { + Unit caster = GetCaster(); + if (caster == null) + return; + + Aura uncontrolledDarkness = caster.GetAura(SpellUncontrolledDarkness, caster.GetGUID()); + if (uncontrolledDarkness == null) + return; + + var script = uncontrolledDarkness.GetScript(); + if (script == null) + return; + + if (caster.HasAura(SpellUncontrolledDarknessBuff)) + { + if (++script.KillCounter >= uncontrolledDarkness.GetSpellInfo().GetEffect(1).CalcValue()) + { + caster.RemoveAura(SpellUncontrolledDarknessBuff); + script.KillCounter = 0; + } + } + else + { + if (++script.KillCounter >= uncontrolledDarkness.GetSpellInfo().GetEffect(0).CalcValue()) + { + caster.CastSpell(caster, SpellUncontrolledDarknessBuff, true); + script.KillCounter = 0; + } + } + } + + public override void Register() + { + OnEffectProc.Add(new(HandleProc, 0, AuraType.Dummy)); + } + } + + [Script] // 342632 - Malevolent Stitching + class spell_torghast_fleshcraft_shield_proc : AuraScript + { + uint SpellLabelFleshcraftBuff = 1103; + + bool CheckProc(ProcEventInfo procInfo) + { + return procInfo.GetSpellInfo() != null && procInfo.GetSpellInfo().HasLabel(SpellLabelFleshcraftBuff); + } + + public override void Register() + { + DoCheckProc.Add(new(CheckProc)); + } + } + + [Script] // 342779 - Crystallized Dreams + class spell_torghast_soulshape_proc : AuraScript + { + uint SpellLabelSoulshape = 1100; + + bool CheckProc(ProcEventInfo procInfo) + { + return procInfo.GetSpellInfo() != null && procInfo.GetSpellInfo().HasLabel(SpellLabelSoulshape); + } + + public override void Register() + { + DoCheckProc.Add(new(CheckProc)); + } + } + + // 342793 - Murmuring Shawl + [Script] // 342799 - Gnarled Key + class spell_torghast_door_of_shadows_proc : AuraScript + { + uint SpellLabelDoorOfShadows = 726; + + bool CheckProc(ProcEventInfo procInfo) + { + return procInfo.GetSpellInfo() != null && procInfo.GetSpellInfo().HasLabel(SpellLabelDoorOfShadows); + } + + public override void Register() + { + DoCheckProc.Add(new(CheckProc)); + } + } + + [Script] // 348908 - Ethereal Wildseed + class spell_torghast_flicker_proc : AuraScript + { + uint SpellLabelFlicker = 1105; + + bool CheckProc(ProcEventInfo procInfo) + { + return procInfo.GetSpellInfo() != null && procInfo.GetSpellInfo().HasLabel(SpellLabelFlicker); + } + + public override void Register() + { + DoCheckProc.Add(new(CheckProc)); + } + } + + [Script] // 354569 - Potent Potion + class spell_torghast_potent_potion_proc : AuraScript + { + uint SpellLabelRejuvenatingSiphonedEssence = 1290; + + bool CheckProc(ProcEventInfo procInfo) + { + return procInfo.GetSpellInfo() != null && procInfo.GetSpellInfo().HasLabel(SpellLabelRejuvenatingSiphonedEssence); + } + + public override void Register() + { + DoCheckProc.Add(new(CheckProc)); + } + } + + [Script] // 354706 - Spiritual Rejuvenation Potion + class spell_torghast_potent_potion_calc : SpellScript + { + uint SpellLabelSpiritualRejuvenationPotion = 354568; + + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellEffect((SpellLabelSpiritualRejuvenationPotion, 1)); + } + + void SetValue(uint effIndex) + { + SetEffectValue(SpellMgr.GetSpellInfo(SpellLabelSpiritualRejuvenationPotion, GetCastDifficulty()).GetEffect(effIndex) + .CalcValue(GetCaster(), null, GetHitUnit())); + } + + public override void Register() + { + OnEffectLaunchTarget.Add(new(SetValue, 0, SpellEffectName.Heal)); + OnEffectHitTarget.Add(new(SetValue, 1, SpellEffectName.Energize)); + } + } + + [Script] // 373761 - Poisonous Spores + class spell_torghast_poisonous_spores : AuraScript + { + void HandleProc(AuraEffect aurEff, ProcEventInfo procInfo) + { + PreventDefaultAction(); + + Spell procSpell = procInfo.GetProcSpell(); + procInfo.GetActor().CastSpell(procSpell.m_targets.GetDst(), aurEff.GetSpellEffectInfo().TriggerSpell, + new CastSpellExtraArgs(aurEff).SetTriggeringSpell(procSpell)); + } + + public override void Register() + { + OnEffectProc.Add(new(HandleProc, 0, AuraType.ProcTriggerSpell)); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/World/ActionIpLogger.cs b/Source/Scripts/World/ActionIpLogger.cs index 7b8d16a9e..285cbf185 100644 --- a/Source/Scripts/World/ActionIpLogger.cs +++ b/Source/Scripts/World/ActionIpLogger.cs @@ -34,47 +34,48 @@ namespace Scripts.World.Achievements UnknownAction = 12 } + [Script] class AccountActionIpLogger : AccountScript { public AccountActionIpLogger() : base("AccountActionIpLogger") { } // We log last_ip instead of last_attempt_ip, as login was successful // AccountLogin = 0 - void OnAccountLogin(uint accountId) + public override void OnAccountLogin(uint accountId) { - AccountIPLogAction(accountId, AccountLogin); + AccountIPLogAction(accountId, IPLoggingTypes.AccountLogin); } // We log last_attempt_ip instead of last_ip, as failed login doesn't necessarily mean approperiate user // AccountFailLogin = 1 - void OnFailedAccountLogin(uint accountId) + public override void OnFailedAccountLogin(uint accountId) { - AccountIPLogAction(accountId, AccountFailLogin); + AccountIPLogAction(accountId, IPLoggingTypes.AccountFailLogin); } // AccountChangePw = 2 - void OnPasswordChange(uint accountId) + public override void OnPasswordChange(uint accountId) { - AccountIPLogAction(accountId, AccountChangePw); + AccountIPLogAction(accountId, IPLoggingTypes.AccountChangePw); } // AccountChangePwFail = 3 - void OnFailedPasswordChange(uint accountId) + public override void OnFailedPasswordChange(uint accountId) { - AccountIPLogAction(accountId, AccountChangePwFail); + AccountIPLogAction(accountId, IPLoggingTypes.AccountChangePwFail); } // Registration Email can Not be changed apart from Gm level users. Thus, we do not require to log them... // AccountChangeEmail = 4 - void OnEmailChange(uint accountId) + public override void OnEmailChange(uint accountId) { - AccountIPLogAction(accountId, AccountChangeEmail); // ... they get logged by gm command logger anyway + AccountIPLogAction(accountId, IPLoggingTypes.AccountChangeEmail); // ... they get logged by gm command logger anyway } // AccountChangeEmailFail = 5 - void OnFailedEmailChange(uint accountId) + public override void OnFailedEmailChange(uint accountId) { - AccountIPLogAction(accountId, AccountChangeEmailFail); + AccountIPLogAction(accountId, IPLoggingTypes.AccountChangeEmailFail); } // AccountLogout = 6 @@ -85,36 +86,36 @@ namespace Scripts.World.Achievements // We declare all the required variables uint playerGuid = accountId; - uint realmId = realm.Id.Realm; - std.string systemNote = "Error"; // "Error" is a placeholder here. We change it later. + uint realmId = Global.WorldMgr.GetRealmId().Index; + string systemNote = "Error"; // "Error" is a placeholder here. We change it later. // With this switch, we change systemNote so that we have a more accurate phraMath.Sing of what type it is. // Avoids Magicnumbers in Sql table switch (aType) { - case AccountLogin: + case IPLoggingTypes.AccountLogin: systemNote = "Logged into WoW"; break; - case AccountFailLogin: + case IPLoggingTypes.AccountFailLogin: systemNote = "Login to WoW Failed"; break; - case AccountChangePw: + case IPLoggingTypes.AccountChangePw: systemNote = "Password Reset Completed"; break; - case AccountChangePwFail: + case IPLoggingTypes.AccountChangePwFail: systemNote = "Password Reset Failed"; break; - case AccountChangeEmail: + case IPLoggingTypes.AccountChangeEmail: systemNote = "Email Change Completed"; break; - case AccountChangeEmailFail: + case IPLoggingTypes.AccountChangeEmailFail: systemNote = "Email Change Failed"; break; - case AccountLogout: + /*case IPLoggingTypes.AccountLogout: systemNote = "Logged on AccountLogout"; //Can not be logged - break; + break;*/ // Neither should happen. Ever. Period. If it does, call Ghostbusters and all your local software defences to investigate. - case UnknownAction: + case IPLoggingTypes.UnknownAction: default: systemNote = "Error! Unknown action!"; break; @@ -123,54 +124,57 @@ namespace Scripts.World.Achievements // Once we have done everything, we can Add the new log. // Seeing as the time differences should be minimal, we do not get unixtime and the timestamp right now; // Rather, we let it be added with the Sql query. - if (aType != AccountFailLogin) + if (aType != IPLoggingTypes.AccountFailLogin) { // As we can assume most account actions are Not failed login, so this is the more accurate check. // For those, we need last_ip... + PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_ALDL_IP_LOGGING); - stmt.setUint(0, playerGuid); - stmt.setUInt64(1, 0); - stmt.setUint(2, realmId); - stmt.setUInt8(3, aType); - stmt.setUint(4, playerGuid); - stmt.setString(5, systemNote); - LoginDatabase.Execute(stmt); + stmt.AddValue(0, playerGuid); + stmt.AddValue(1, 0ul); + stmt.AddValue(2, realmId); + stmt.AddValue(3, (byte)aType); + stmt.AddValue(4, playerGuid); + stmt.AddValue(5, systemNote); + DB.Login.Execute(stmt); } else // ... but for failed login, we query last_attempt_ip from account table. Which we do with an unique query { + PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_FACL_IP_LOGGING); - stmt.setUint(0, playerGuid); - stmt.setUInt64(1, 0); - stmt.setUint(2, realmId); - stmt.setUInt8(3, aType); - stmt.setUint(4, playerGuid); - stmt.setString(5, systemNote); - LoginDatabase.Execute(stmt); + stmt.AddValue(0, playerGuid); + stmt.AddValue(1, 0ul); + stmt.AddValue(2, realmId); + stmt.AddValue(3, (byte)aType); + stmt.AddValue(4, playerGuid); + stmt.AddValue(5, systemNote); + DB.Login.Execute(stmt); } return; } } + [Script] class CharacterActionIpLogger : PlayerScript { public CharacterActionIpLogger() : base("CharacterActionIpLogger") { } // CharacterCreate = 7 - void OnCreate(Player player) + public override void OnCreate(Player player) { - CharacterIPLogAction(player, CharacterCreate); + CharacterIPLogAction(player, IPLoggingTypes.CharacterCreate); } // CharacterLogin = 8 - void OnLogin(Player player, bool firstLogin) + public override void OnLogin(Player player, bool firstLogin) { - CharacterIPLogAction(player, CharacterLogin); + CharacterIPLogAction(player, IPLoggingTypes.CharacterLogin); } // CharacterLogout = 9 - void OnLogout(Player player) + public override void OnLogout(Player player) { - CharacterIPLogAction(player, CharacterLogout); + CharacterIPLogAction(player, IPLoggingTypes.CharacterLogout); } // CharacterDelete = 10 @@ -189,65 +193,67 @@ namespace Scripts.World.Achievements // We declare all the required variables uint playerGuid = player.GetSession().GetAccountId(); - uint realmId = realm.Id.Realm; - std.string currentIp = player.GetSession().GetRemoteAddress(); - std.string systemNote = "Error"; // "Error" is a placeholder here. We change it... + uint realmId = Global.WorldMgr.GetRealmId().Index; + string currentIp = player.GetSession().GetRemoteAddress(); + string systemNote; // ... with this switch, so that we have a more accurate phraMath.Sing of what type it is switch (aType) { - case CharacterCreate: + case IPLoggingTypes.CharacterCreate: systemNote = "Character Created"; break; - case CharacterLogin: + case IPLoggingTypes.CharacterLogin: systemNote = "Logged onto Character"; break; - case CharacterLogout: + case IPLoggingTypes.CharacterLogout: systemNote = "Logged out of Character"; break; - case CharacterDelete: + case IPLoggingTypes.CharacterDelete: systemNote = "Character Deleted"; break; - case CharacterFailedDelete: + case IPLoggingTypes.CharacterFailedDelete: systemNote = "Character Deletion Failed"; break; // Neither should happen. Ever. Period. If it does, call Mythbusters. - case UnknownAction: + case IPLoggingTypes.UnknownAction: default: systemNote = "Error! Unknown action!"; break; } // Once we have done everything, we can Add the new log. + PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_CHAR_IP_LOGGING); - stmt.setUint(0, playerGuid); - stmt.setUInt64(1, player.GetGUID().GetCounter()); - stmt.setUint(2, realmId); - stmt.setUInt8(3, aType); - stmt.setString(4, currentIp); // We query the ip here. - stmt.setString(5, systemNote); + stmt.AddValue(0, playerGuid); + stmt.AddValue(1, player.GetGUID().GetCounter()); + stmt.AddValue(2, realmId); + stmt.AddValue(3, (byte)aType); + stmt.AddValue(4, currentIp); // We query the ip here. + stmt.AddValue(5, systemNote); // Seeing as the time differences should be minimal, we do not get unixtime and the timestamp right now; // Rather, we let it be added with the Sql query. - LoginDatabase.Execute(stmt); + DB.Login.Execute(stmt); return; } } + [Script] class CharacterDeleteActionIpLogger : PlayerScript { public CharacterDeleteActionIpLogger() : base("CharacterDeleteActionIpLogger") { } // CharacterDelete = 10 - void OnDelete(ObjectGuid guid, uint accountId) + public override void OnDelete(ObjectGuid guid, uint accountId) { - DeleteIPLogAction(guid, accountId, CharacterDelete); + DeleteIPLogAction(guid, accountId, IPLoggingTypes.CharacterDelete); } // CharacterFailedDelete = 11 - void OnFailedDelete(ObjectGuid guid, uint accountId) + public override void OnFailedDelete(ObjectGuid guid, uint accountId) { - DeleteIPLogAction(guid, accountId, CharacterFailedDelete); + DeleteIPLogAction(guid, accountId, IPLoggingTypes.CharacterFailedDelete); } void DeleteIPLogAction(ObjectGuid guid, uint playerGuid, IPLoggingTypes aType) @@ -255,40 +261,41 @@ namespace Scripts.World.Achievements // Action Ip Logger is only intialized if config is set up // Else, this script isn't loaded in the first place: We require no config check. - uint realmId = realm.Id.Realm; + uint realmId = Global.WorldMgr.GetRealmId().Index; // Query playerGuid/accountId, as we only have characterGuid - std.string systemNote = "Error"; // "Error" is a placeholder here. We change it later. + string systemNote; // With this switch, we change systemNote so that we have a more accurate phraMath.Sing of what type it is. // Avoids Magicnumbers in Sql table switch (aType) { - case CharacterDelete: + case IPLoggingTypes.CharacterDelete: systemNote = "Character Deleted"; break; - case CharacterFailedDelete: + case IPLoggingTypes.CharacterFailedDelete: systemNote = "Character Deletion Failed"; break; // Neither should happen. Ever. Period. If it does, call to whatever god you have for mercy and guidance. - case UnknownAction: + case IPLoggingTypes.UnknownAction: default: systemNote = "Error! Unknown action!"; break; } // Once we have done everything, we can Add the new log. + PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_ALDL_IP_LOGGING); - stmt.setUint(0, playerGuid); - stmt.setUInt64(1, guid.GetCounter()); - stmt.setUint(2, realmId); - stmt.setUInt8(3, aType); - stmt.setUint(4, playerGuid); - stmt.setString(5, systemNote); + stmt.AddValue(0, playerGuid); + stmt.AddValue(1, guid.GetCounter()); + stmt.AddValue(2, realmId); + stmt.AddValue(3, (byte)aType); + stmt.AddValue(4, playerGuid); + stmt.AddValue(5, systemNote); // Seeing as the time differences should be minimal, we do not get unixtime and the timestamp right now; // Rather, we let it be added with the Sql query. - LoginDatabase.Execute(stmt); + DB.Login.Execute(stmt); return; } }