diff --git a/Source/Game/AI/CoreAI/CombatAI.cs b/Source/Game/AI/CoreAI/CombatAI.cs index 704e69679..82a8eaa16 100644 --- a/Source/Game/AI/CoreAI/CombatAI.cs +++ b/Source/Game/AI/CoreAI/CombatAI.cs @@ -322,11 +322,12 @@ namespace Game.AI public override void AttackStart(Unit victim) { } - public override void OnCharmed(bool apply) + public override void OnCharmed(bool isNew) { - if (!me.GetVehicleKit().IsVehicleInUse() && !apply && _hasConditions)//was used and has conditions + bool charmed = me.IsCharmed(); + if (!me.GetVehicleKit().IsVehicleInUse() && !charmed && _hasConditions)//was used and has conditions _doDismiss = true;//needs reset - else if (apply) + else if (charmed) _doDismiss = false;//in use again _dismissTimer = VEHICLE_DISMISS_TIME;//reset timer diff --git a/Source/Game/AI/CoreAI/CreatureAI.cs b/Source/Game/AI/CoreAI/CreatureAI.cs index 1c515325e..ca96923c6 100644 --- a/Source/Game/AI/CoreAI/CreatureAI.cs +++ b/Source/Game/AI/CoreAI/CreatureAI.cs @@ -44,13 +44,20 @@ namespace Game.AI _moveInLineOfSightLocked = false; } - public override void OnCharmed(bool apply) + public override void OnCharmed(bool isNew) { - if (apply) + if (isNew && !me.IsCharmed() && !me.LastCharmerGUID.IsEmpty()) { - me.NeedChangeAI = true; - me.IsAIEnabled = false; + if (!me.HasReactState(ReactStates.Passive)) + { + Unit lastCharmer = Global.ObjAccessor.GetUnit(me, me.LastCharmerGUID); + if (lastCharmer != null) + me.EngageWithTarget(lastCharmer); + } + me.LastCharmerGUID.Clear(); } + + base.OnCharmed(isNew); } public void Talk(uint id, WorldObject whisperTarget = null) diff --git a/Source/Game/AI/CoreAI/PassiveAI.cs b/Source/Game/AI/CoreAI/PassiveAI.cs index 1513316fc..a93f5ec49 100644 --- a/Source/Game/AI/CoreAI/PassiveAI.cs +++ b/Source/Game/AI/CoreAI/PassiveAI.cs @@ -76,12 +76,6 @@ namespace Game.AI me.RemoveDynamicFlag(UnitDynFlags.Lootable); } - public override void OnCharmed(bool apply) - { - me.NeedChangeAI = true; - me.IsAIEnabled = false; - } - public override void MoveInLineOfSight(Unit who) { } public override void EnterEvadeMode(EvadeReason why) { } @@ -98,7 +92,7 @@ namespace Game.AI public override void AttackStart(Unit unit) { } public override void UpdateAI(uint diff) { } public override void EnterEvadeMode(EvadeReason why) { } - public override void OnCharmed(bool apply) { } + public override void OnCharmed(bool isNew) { } } public class CritterAI : PassiveAI diff --git a/Source/Game/AI/CoreAI/PetAI.cs b/Source/Game/AI/CoreAI/PetAI.cs index d86a64ffa..c89de9e35 100644 --- a/Source/Game/AI/CoreAI/PetAI.cs +++ b/Source/Game/AI/CoreAI/PetAI.cs @@ -601,12 +601,6 @@ namespace Game.AI } } - public override void OnCharmed(bool apply) - { - me.NeedChangeAI = true; - me.IsAIEnabled = false; - } - void ClearCharmInfoFlags() { // Quick access to set all flags to FALSE diff --git a/Source/Game/AI/CoreAI/UnitAI.cs b/Source/Game/AI/CoreAI/UnitAI.cs index 11e0e1a8b..933c46f8d 100644 --- a/Source/Game/AI/CoreAI/UnitAI.cs +++ b/Source/Game/AI/CoreAI/UnitAI.cs @@ -463,7 +463,18 @@ namespace Game.AI public virtual void Reset() { } - public virtual void OnCharmed(bool apply) { } + /// + // Called when unit's charm state changes with isNew = false + // Implementation should call me->ScheduleAIChange() if AI replacement is desired + // If this call is made, AI will be replaced on the next tick + // When replacement is made, OnCharmed is called with isNew = true + /// + /// + public virtual void OnCharmed(bool isNew) + { + if (!isNew) + me.ScheduleAIChange(); + } public virtual bool ShouldSparWith(Unit target) { return false; } diff --git a/Source/Game/AI/PlayerAI/PlayerAI.cs b/Source/Game/AI/PlayerAI/PlayerAI.cs index 41e89d06a..885fc2b42 100644 --- a/Source/Game/AI/PlayerAI/PlayerAI.cs +++ b/Source/Game/AI/PlayerAI/PlayerAI.cs @@ -655,8 +655,6 @@ namespace Game.AI return null; } - public override void OnCharmed(bool apply) { } - // helper functions to determine player info public bool IsHealer(Player who = null) { @@ -710,7 +708,14 @@ namespace Game.AI { Unit charmer = me.GetCharmer(); if (charmer) - return charmer.IsAIEnabled ? charmer.GetAI().SelectTarget(SelectAggroTarget.Random, 0, new ValidTargetSelectPredicate(this)) : charmer.GetVictim(); + { + UnitAI charmerAI = charmer.GetAI(); + if (charmerAI != null) + return charmerAI.SelectTarget(SelectAggroTarget.Random, 0, new ValidTargetSelectPredicate(this)); + + return charmer.GetVictim(); + } + return null; } @@ -1344,9 +1349,9 @@ namespace Game.AI } } - public override void OnCharmed(bool apply) + public override void OnCharmed(bool isNew) { - if (apply) + if (me.IsCharmed()) { me.CastStop(); me.AttackStop(); @@ -1362,6 +1367,8 @@ namespace Game.AI me.GetMotionMaster().Clear(); me.StopMoving(); } + + base.OnCharmed(isNew); } } diff --git a/Source/Game/AI/ScriptedAI/ScriptedAI.cs b/Source/Game/AI/ScriptedAI/ScriptedAI.cs index 735c34984..6bd5d3c02 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedAI.cs @@ -673,7 +673,7 @@ namespace Game.AI foreach (var id in this) { Creature summon = ObjectAccessor.GetCreature(_me, id); - if (summon && summon.IsAIEnabled && (entry == 0 || summon.GetEntry() == entry)) + if (summon && summon.IsAIEnabled() && (entry == 0 || summon.GetEntry() == entry)) { summon.GetAI().DoZoneInCombat(null); } @@ -760,7 +760,7 @@ namespace Game.AI foreach (var guid in summons) { Creature summon = ObjectAccessor.GetCreature(_me, guid); - if (summon && summon.IsAIEnabled) + if (summon && summon.IsAIEnabled()) summon.GetAI().DoAction(action); } } diff --git a/Source/Game/AI/SmartScripts/SmartAI.cs b/Source/Game/AI/SmartScripts/SmartAI.cs index 70c4e6ecd..4f8288e05 100644 --- a/Source/Game/AI/SmartScripts/SmartAI.cs +++ b/Source/Game/AI/SmartScripts/SmartAI.cs @@ -714,17 +714,18 @@ namespace Game.AI GetScript().ProcessEventsFor(apply ? SmartEvents.PassengerBoarded : SmartEvents.PassengerRemoved, passenger, (uint)seatId, 0, apply); } - public override void OnCharmed(bool apply) + public override void OnCharmed(bool isNew) { - if (apply) // do this before we change charmed state, as charmed state might prevent these things from processing + bool charmed = me.IsCharmed(); + if (charmed) // do this before we change charmed state, as charmed state might prevent these things from processing { if (HasEscortState(SmartEscortState.Escorting | SmartEscortState.Paused | SmartEscortState.Returning)) EndPath(true); } - _isCharmed = apply; + _isCharmed = charmed; - if (!apply && !me.IsInEvadeMode()) + if (!charmed && !me.IsInEvadeMode()) { if (_repeatWaypointPath) StartPath(_run, GetScript().GetPathId(), true); @@ -736,7 +737,7 @@ namespace Game.AI AttackStart(charmer); } - GetScript().ProcessEventsFor(SmartEvents.Charmed, null, 0, 0, apply); + GetScript().ProcessEventsFor(SmartEvents.Charmed, null, 0, 0, charmed); } public override void DoAction(int param) diff --git a/Source/Game/AI/SmartScripts/SmartScript.cs b/Source/Game/AI/SmartScripts/SmartScript.cs index 458190aa8..79090af38 100644 --- a/Source/Game/AI/SmartScripts/SmartScript.cs +++ b/Source/Game/AI/SmartScripts/SmartScript.cs @@ -958,7 +958,7 @@ namespace Game.AI } case SmartActions.SetInCombatWithZone: { - if (_me != null && _me.IsAIEnabled) + if (_me != null && _me.IsAIEnabled()) { _me.GetAI().DoZoneInCombat(); Log.outDebug(LogFilter.ScriptsAi, $"SmartScript.ProcessAction: SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: Creature: {_me.GetGUID()}"); diff --git a/Source/Game/Chat/Commands/DebugCommands.cs b/Source/Game/Chat/Commands/DebugCommands.cs index 07337b738..270bf787b 100644 --- a/Source/Game/Chat/Commands/DebugCommands.cs +++ b/Source/Game/Chat/Commands/DebugCommands.cs @@ -86,7 +86,7 @@ namespace Game.Chat return false; Creature target = handler.GetSelectedCreature(); - if (!target || !target.IsAIEnabled || target.GetAI() == null) + if (!target || !target.IsAIEnabled()) return false; string fill_str = args.NextString(); diff --git a/Source/Game/Chat/Commands/NPCCommands.cs b/Source/Game/Chat/Commands/NPCCommands.cs index 3013829d5..baa47207e 100644 --- a/Source/Game/Chat/Commands/NPCCommands.cs +++ b/Source/Game/Chat/Commands/NPCCommands.cs @@ -78,7 +78,7 @@ namespace Game.Chat return false; } - if (!creatureTarget.IsAIEnabled) + if (!creatureTarget.IsAIEnabled()) { handler.SendSysMessage(CypherStrings.CreatureNotAiEnabled); return false; diff --git a/Source/Game/Combat/CombatManager.cs b/Source/Game/Combat/CombatManager.cs index 083a20730..710f306ab 100644 --- a/Source/Game/Combat/CombatManager.cs +++ b/Source/Game/Combat/CombatManager.cs @@ -16,6 +16,7 @@ */ using Framework.Constants; +using Game.AI; using Game.Entities; using System.Collections.Generic; using System.Linq; @@ -209,8 +210,11 @@ namespace Game.Combat pair.Value.Suppress(_owner); if (UpdateOwnerCombatState()) - if (_owner.IsAIEnabled) - _owner.GetAI().JustExitedCombat(); + { + UnitAI ownerAI = _owner.GetAI(); + if (ownerAI != null) + ownerAI.JustExitedCombat(); + } } public void EndAllPvECombat() @@ -230,8 +234,9 @@ namespace Game.Combat public static void NotifyAICombat(Unit me, Unit other) { - if (!me.IsAIEnabled) + if (!me.IsAIEnabled()) return; + me.GetAI().JustEnteredCombat(other); Creature cMe = me.ToCreature(); @@ -334,10 +339,18 @@ namespace Game.Combat bool needSecondAI = second.GetCombatManager().UpdateOwnerCombatState(); // ...and if that happened, also notify the AI of it... - if (needFirstAI && first.IsAIEnabled) - first.GetAI().JustExitedCombat(); - if (needSecondAI && second.IsAIEnabled) - second.GetAI().JustExitedCombat(); + if (needFirstAI) + { + UnitAI firstAI = first.GetAI(); + if (firstAI != null) + firstAI.JustExitedCombat(); + } + if (needSecondAI) + { + UnitAI secondAI = second.GetAI(); + if (secondAI != null) + secondAI.JustExitedCombat(); + } } public Unit GetOther(Unit me) { return (first == me) ? second : first; } @@ -383,12 +396,15 @@ namespace Game.Combat CombatManager.NotifyAICombat(second, first); } - void SuppressFor(Unit who) + public void SuppressFor(Unit who) { Suppress(who); if (who.GetCombatManager().UpdateOwnerCombatState()) - if (who.IsAIEnabled) - who.GetAI().JustExitedCombat(); + { + UnitAI ai = who.GetAI(); + if (ai != null) + ai.JustExitedCombat(); + } } // suppressed combat refs do not generate a combat state for one side of the relation diff --git a/Source/Game/Combat/ThreatManager.cs b/Source/Game/Combat/ThreatManager.cs index 964cf6226..287d8765e 100644 --- a/Source/Game/Combat/ThreatManager.cs +++ b/Source/Game/Combat/ThreatManager.cs @@ -16,6 +16,7 @@ */ using Framework.Constants; +using Game.AI; using Game.Entities; using Game.Networking.Packets; using Game.Spells; @@ -198,7 +199,7 @@ namespace Game.Combat static void SaveCreatureHomePositionIfNeed(Creature c) { MovementGeneratorType movetype = c.GetMotionMaster().GetCurrentMovementGeneratorType(); - if (movetype == MovementGeneratorType.Waypoint || movetype == MovementGeneratorType.Point || (c.IsAIEnabled && c.GetAI().IsEscorted())) + if (movetype == MovementGeneratorType.Waypoint || movetype == MovementGeneratorType.Point || (c.IsAIEnabled() && c.GetAI().IsEscorted())) c.SetHomePosition(c.GetPosition()); } @@ -311,8 +312,9 @@ namespace Game.Combat Creature cOwner = _owner.ToCreature(); Cypher.Assert(cOwner != null); // if we got here the owner can have a threat list, and must be a creature! SaveCreatureHomePositionIfNeed(cOwner); - if (cOwner.IsAIEnabled) - cOwner.GetAI().JustEngagedWith(target); + CreatureAI ownerAI = cOwner.GetAI(); + if (ownerAI != null) + ownerAI.JustEngagedWith(target); } } @@ -835,7 +837,7 @@ namespace Game.Combat if (!FlagsAllowFighting(_owner, _victim) || !FlagsAllowFighting(_victim, _owner)) return OnlineState.Offline; - if (_owner.IsAIEnabled && !_owner.GetAI().CanAIAttack(_victim)) + if (_owner.IsAIEnabled() && !_owner.GetAI().CanAIAttack(_victim)) return OnlineState.Offline; // next, check suppression (immunity to chosen melee attack school) diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 8cdbb02d1..6c267dc2f 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -58,13 +58,6 @@ namespace Game.Entities _currentWaypointNodeInfo = new(); } - public override void Dispose() - { - i_AI = null; - - base.Dispose(); - } - public override void AddToWorld() { // Register the creature for guid lookup @@ -170,8 +163,9 @@ namespace Game.Entities //DestroyForNearbyPlayers(); // old UpdateObjectVisibility() loot.Clear(); uint respawnDelay = m_respawnDelay; - if (IsAIEnabled) - GetAI().CorpseRemoved(respawnDelay); + CreatureAI ai = GetAI(); + if (ai != null) + ai.CorpseRemoved(respawnDelay); if (destroyForNearbyPlayers) DestroyForNearbyPlayers(); @@ -429,7 +423,7 @@ namespace Game.Entities public override void Update(uint diff) { - if (IsAIEnabled && triggerJustAppeared && m_deathState != DeathState.Dead) + if (IsAIEnabled() && triggerJustAppeared && m_deathState != DeathState.Dead) { if (m_respawnCompatibilityMode && VehicleKit != null) VehicleKit.Reset(); @@ -541,24 +535,8 @@ namespace Game.Entities m_shouldReacquireTarget = false; } - // if creature is charmed, switch to charmed AI (and back) - if (NeedChangeAI) - { - UpdateCharmAI(); - NeedChangeAI = false; - IsAIEnabled = true; - if (!IsInEvadeMode() && !LastCharmerGUID.IsEmpty()) - { - Unit charmer = Global.ObjAccessor.GetUnit(this, LastCharmerGUID); - if (charmer) - EngageWithTarget(charmer); - } - - LastCharmerGUID.Clear(); - } - // periodic check to see if the creature has passed an evade boundary - if (IsAIEnabled && !IsInEvadeMode() && IsEngaged()) + if (IsAIEnabled() && !IsInEvadeMode() && IsEngaged()) { if (diff >= m_boundaryCheckTime) { @@ -593,13 +571,10 @@ namespace Game.Entities } } - if (!IsInEvadeMode() && IsAIEnabled) - { - // do not allow the AI to be changed during update - m_AI_locked = true; - i_AI.UpdateAI(diff); - m_AI_locked = false; - } + // do not allow the AI to be changed during update + m_AI_locked = true; + base.AIUpdateTick(diff); + m_AI_locked = false; if (!IsAlive()) break; @@ -633,8 +608,11 @@ namespace Game.Entities { m_cannotReachTimer += diff; if (m_cannotReachTimer >= SharedConst.CreatureNoPathEvadeTime) - if (IsAIEnabled) - GetAI().EnterEvadeMode(EvadeReason.NoPath); + { + CreatureAI ai = GetAI(); + if (ai != null) + ai.EnterEvadeMode(EvadeReason.NoPath); + } } break; } @@ -747,19 +725,15 @@ namespace Game.Entities } } - bool AIDestory() + bool DestoryAI() { if (m_AI_locked) { - Log.outDebug(LogFilter.Scripts, "AIM_Destroy: failed to destroy, locked."); + Log.outDebug(LogFilter.Scripts, "DestroyAI: failed to destroy, locked."); return false; } - Cypher.Assert(i_disabledAI == null, "The disabled AI wasn't cleared!"); - - i_AI = null; - - IsAIEnabled = false; + SetAI(null); return true; } @@ -772,14 +746,15 @@ namespace Game.Entities return false; } - AIDestory(); + if (ai == null) + ai = AISelector.SelectAI(this); + + SetAI(ai); InitializeMovementAI(); - i_AI = ai ?? AISelector.SelectAI(this); - - IsAIEnabled = true; i_AI.InitializeAI(); + // Initialize vehicle if (GetVehicleKit() != null) GetVehicleKit().Reset(); @@ -1108,10 +1083,11 @@ namespace Game.Entities public bool IsEscortNPC(bool onlyIfActive = true) { - if (!IsAIEnabled) - return false; + CreatureAI ai = GetAI(); + if (ai != null) + return ai.IsEscortNPC(onlyIfActive); - return GetAI().IsEscortNPC(onlyIfActive); + return false; } public override bool IsMovementPreventedByCasting() @@ -1695,7 +1671,7 @@ namespace Game.Entities public override bool CanAlwaysSee(WorldObject obj) { - if (IsAIEnabled && GetAI().CanSeeAlways(obj)) + if (IsAIEnabled() && GetAI().CanSeeAlways(obj)) return true; return false; @@ -1936,8 +1912,9 @@ namespace Game.Entities //Re-initialize reactstate that could be altered by movementgenerators InitializeReactState(); - if (IsAIEnabled) // reset the AI to be sure no dirty or uninitialized values will be used till next tick - GetAI().Reset(); + UnitAI ai = GetAI(); + if (ai != null) // reset the AI to be sure no dirty or uninitialized values will be used till next tick + ai.Reset(); triggerJustAppeared = true; @@ -2299,7 +2276,7 @@ namespace Game.Entities if (!victim.IsInAccessiblePlaceFor(this)) return false; - if (IsAIEnabled && !GetAI().CanAIAttack(victim)) + if (IsAIEnabled() && !GetAI().CanAIAttack(victim)) return false; // we cannot attack in evade mode diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index ed6d6bf8e..67e2c737d 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -390,14 +390,7 @@ namespace Game.Entities aura.SetDuration(aura.GetSpellInfo().GetMaxDuration()); } - if (IsAIEnabled && GetAI() != null) - GetAI().UpdateAI(diff); - else if (NeedChangeAI) - { - UpdateCharmAI(); - NeedChangeAI = false; - IsAIEnabled = GetAI() != null; - } + AIUpdateTick(diff); // Update items that have just a limited lifetime if (now > m_Last_tick) @@ -5175,6 +5168,7 @@ namespace Game.Entities if (guildId != 0) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.GuildGUID), ObjectGuid.Create(HighGuid.Guild, guildId)); + SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.GuildClubMemberID), GetGUID().GetCounter()); AddPlayerFlag(PlayerFlags.GuildLevelEnabled); } else diff --git a/Source/Game/Entities/TemporarySummon.cs b/Source/Game/Entities/TemporarySummon.cs index 8cdc27c37..b53a7ecf7 100644 --- a/Source/Game/Entities/TemporarySummon.cs +++ b/Source/Game/Entities/TemporarySummon.cs @@ -207,9 +207,9 @@ namespace Game.Entities Unit owner = GetSummoner(); if (owner != null) { - if (owner.IsTypeId(TypeId.Unit) && owner.ToCreature().IsAIEnabled) + if (owner.IsTypeId(TypeId.Unit) && owner.ToCreature().IsAIEnabled()) owner.ToCreature().GetAI().JustSummoned(this); - if (IsAIEnabled) + if (IsAIEnabled()) GetAI().IsSummonedBy(owner); } } @@ -243,7 +243,7 @@ namespace Game.Entities } Unit owner = GetSummoner(); - if (owner != null && owner.IsTypeId(TypeId.Unit) && owner.ToCreature().IsAIEnabled) + if (owner != null && owner.IsTypeId(TypeId.Unit) && owner.ToCreature().IsAIEnabled()) owner.ToCreature().GetAI().SummonedCreatureDespawn(this); AddObjectToRemoveList(); diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index 367fe9353..e7d741bec 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -16,10 +16,10 @@ */ using Framework.Constants; +using Game.AI; using Game.BattleFields; using Game.BattleGrounds; using Game.Combat; -using Game.DataStorage; using Game.Loots; using Game.Maps; using Game.Networking.Packets; @@ -354,8 +354,11 @@ namespace Game.Entities { Creature cControlled = controlled.ToCreature(); if (cControlled != null) - if (cControlled.IsAIEnabled) - cControlled.GetAI().OwnerAttacked(victim); + { + CreatureAI controlledAI = cControlled.GetAI(); + if (controlledAI != null) + controlledAI.OwnerAttacked(victim); + } } } return true; @@ -880,7 +883,7 @@ namespace Game.Entities plrVictim.SendDurabilityLoss(plrVictim, loss); } // Call KilledUnit for creatures - if (attacker != null && attacker.IsCreature() && attacker.IsAIEnabled) + if (attacker != null && attacker.IsCreature() && attacker.IsAIEnabled()) attacker.ToCreature().GetAI().KilledUnit(victim); // last damage from non duel opponent or opponent controlled creature @@ -907,19 +910,20 @@ namespace Game.Entities } // Call KilledUnit for creatures, this needs to be called after the lootable flag is set - if (attacker != null && attacker.IsCreature() && attacker.IsAIEnabled) + if (attacker != null && attacker.IsCreature() && attacker.IsAIEnabled()) attacker.ToCreature().GetAI().KilledUnit(victim); // Call creature just died function - if (creature.IsAIEnabled) - creature.GetAI().JustDied(attacker); + CreatureAI ai = creature.GetAI(); + if (ai != null) + ai.JustDied(attacker); TempSummon summon = creature.ToTempSummon(); if (summon != null) { Unit summoner = summon.GetSummoner(); if (summoner != null) - if (summoner.IsTypeId(TypeId.Unit) && summoner.IsAIEnabled) + if (summoner.IsTypeId(TypeId.Unit) && summoner.IsAIEnabled()) summoner.ToCreature().GetAI().SummonedCreatureDies(creature, attacker); } diff --git a/Source/Game/Entities/Unit/Unit.Fields.cs b/Source/Game/Entities/Unit/Unit.Fields.cs index 8d62f36bb..8b8778a71 100644 --- a/Source/Game/Entities/Unit/Unit.Fields.cs +++ b/Source/Game/Entities/Unit/Unit.Fields.cs @@ -36,8 +36,6 @@ namespace Game.Entities //AI protected UnitAI i_AI; protected UnitAI i_disabledAI; - public bool IsAIEnabled { get; set; } - public bool NeedChangeAI { get; set; } //Movement protected float[] m_speed_rate = new float[(int)UnitMoveType.Max]; diff --git a/Source/Game/Entities/Unit/Unit.Pets.cs b/Source/Game/Entities/Unit/Unit.Pets.cs index 5a24c4b64..82edf1a72 100644 --- a/Source/Game/Entities/Unit/Unit.Pets.cs +++ b/Source/Game/Entities/Unit/Unit.Pets.cs @@ -48,83 +48,48 @@ namespace Game.Entities public void UpdateCharmAI() { - switch (GetTypeId()) + if (IsCharmed()) { - case TypeId.Unit: - if (i_disabledAI != null) // disabled AI must be primary AI + UnitAI newAI = null; + if (IsPlayer()) + { + Unit charmer = GetCharmer(); + if (charmer != null) { - if (!IsCharmed()) + // first, we check if the creature's own AI specifies an override playerai for its owned players + Creature creatureCharmer = charmer.ToCreature(); + if (creatureCharmer != null) { - i_AI = i_disabledAI; - i_disabledAI = null; - - if (IsTypeId(TypeId.Unit)) - ToCreature().GetAI().OnCharmed(false); - } - } - else - { - if (IsCharmed()) - { - i_disabledAI = i_AI; - if (IsPossessed() || IsVehicle()) - i_AI = new PossessedAI(ToCreature()); - else - i_AI = new PetAI(ToCreature()); - } - } - break; - case TypeId.Player: - { - if (IsCharmed()) // if we are currently being charmed, then we should apply charm AI - { - i_disabledAI = i_AI; - - UnitAI newAI = null; - // first, we check if the creature's own AI specifies an override playerai for its owned players - Unit charmer = GetCharmer(); - if (charmer) - { - Creature creatureCharmer = charmer.ToCreature(); - if (creatureCharmer) - { - PlayerAI charmAI = creatureCharmer.IsAIEnabled ? creatureCharmer.GetAI().GetAIForCharmedPlayer(ToPlayer()) : null; - if (charmAI != null) - newAI = charmAI; - } - else - { - Log.outError(LogFilter.Misc, "Attempt to assign charm AI to player {0} who is charmed by non-creature {1}.", GetGUID().ToString(), GetCharmerGUID().ToString()); - } - } - if (newAI == null) // otherwise, we default to the generic one - newAI = new SimpleCharmedPlayerAI(ToPlayer()); - i_AI = newAI; - newAI.OnCharmed(true); + CreatureAI charmerAI = creatureCharmer.GetAI(); + if (charmerAI != null) + newAI = charmerAI.GetAIForCharmedPlayer(ToPlayer()); } else - { - if (i_AI != null) - { - // we allow the charmed PlayerAI to clean up - i_AI.OnCharmed(false); - } - else - { - Log.outError(LogFilter.Misc, "Attempt to remove charm AI from player {0} who doesn't currently have charm AI.", GetGUID().ToString()); - } - // and restore our previous PlayerAI (if we had one) - i_AI = i_disabledAI; - i_disabledAI = null; - // IsAIEnabled gets handled in the caller - } - break; + Log.outError(LogFilter.Misc, $"Attempt to assign charm AI to player {GetGUID()} who is charmed by non-creature {GetCharmerGUID()}."); } - default: - Log.outError(LogFilter.Misc, "Attempt to update charm AI for unit {0}, which is neither player nor creature.", GetGUID().ToString()); - break; - } + if (newAI == null) // otherwise, we default to the generic one + newAI = new SimpleCharmedPlayerAI(ToPlayer()); + } + else + { + Cypher.Assert(IsCreature()); + if (IsPossessed() || IsVehicle()) + newAI = new PossessedAI(ToCreature()); + else + newAI = new PetAI(ToCreature()); + } + Cypher.Assert(newAI != null); + i_AI = newAI; + newAI.OnCharmed(true); + AIUpdateTick(0, true); + } + else + { + RestoreDisabledAI(); + if (i_AI != null) + i_AI.OnCharmed(true); + } } public void SetMinion(Minion minion, bool apply) @@ -365,7 +330,12 @@ namespace Game.Entities StopMoving(); - ToCreature().GetAI().OnCharmed(true); + // AI will schedule its own change if appropriate + UnitAI ai = GetAI(); + if (ai != null) + ai.OnCharmed(false); + else + ScheduleAIChange(); } else { @@ -378,12 +348,11 @@ namespace Game.Entities if (charmer.IsTypeId(TypeId.Unit)) // we are charmed by a creature { // change AI to charmed AI on next Update tick - NeedChangeAI = true; - if (IsAIEnabled) - { - IsAIEnabled = false; - player.GetAI().OnCharmed(true); - } + UnitAI ai = GetAI(); + if (ai != null) + ai.OnCharmed(false); + else + player.ScheduleAIChange(); } player.SetClientControl(this, false); @@ -482,17 +451,9 @@ namespace Game.Entities ///@todo Handle SLOT_IDLE motion resume GetMotionMaster().InitializeDefault(); - Creature creature = ToCreature(); - if (creature) - { - // Creature will restore its old AI on next update - if (creature.GetAI() != null) - creature.GetAI().OnCharmed(false); - - // Vehicle should not attack its passenger after he exists the seat - if (type != CharmType.Vehicle) - LastCharmerGUID = charmer ? charmer.GetGUID() : ObjectGuid.Empty; - } + // Vehicle should not attack its passenger after he exists the seat + if (type != CharmType.Vehicle) + LastCharmerGUID = charmer.GetGUID(); // If charmer still exists if (!charmer) @@ -539,17 +500,12 @@ namespace Game.Entities } } - Player player = ToPlayer(); - if (player) - { - if (charmer.IsTypeId(TypeId.Unit)) // charmed by a creature, this means we had PlayerAI - { - NeedChangeAI = true; - IsAIEnabled = false; - } + if (!IsPlayer() || charmer.IsCreature()) + GetAI().OnCharmed(false); // AI will potentially schedule a charm ai update + Player player = ToPlayer(); + if (player != null) player.SetClientControl(this, true); - } // a guardian should always have charminfo if (playerCharmer && this != charmer.GetFirstControlled()) diff --git a/Source/Game/Entities/Unit/Unit.Spells.cs b/Source/Game/Entities/Unit/Unit.Spells.cs index b2ff8fc3d..0abe7050a 100644 --- a/Source/Game/Entities/Unit/Unit.Spells.cs +++ b/Source/Game/Entities/Unit/Unit.Spells.cs @@ -17,6 +17,7 @@ using Framework.Constants; using Framework.Dynamic; +using Game.AI; using Game.BattleGrounds; using Game.Networking.Packets; using Game.Spells; @@ -1801,14 +1802,13 @@ namespace Game.Entities Unit victim = healInfo.GetTarget(); uint addhealth = healInfo.GetHeal(); - if (healer) - { - if (victim.IsAIEnabled) - victim.GetAI().HealReceived(healer, addhealth); + UnitAI victimAI = victim.GetAI(); + if (victimAI != null) + victimAI.HealReceived(healer, addhealth); - if (healer.IsAIEnabled) - healer.GetAI().HealDone(victim, addhealth); - } + UnitAI healerAI = healer != null ? healer.GetAI() : null; + if (healerAI != null) + healerAI.HealDone(victim, addhealth); if (addhealth != 0) gain = (int)victim.ModifyHealth(addhealth); @@ -2424,7 +2424,7 @@ namespace Game.Entities spell.SetReferencedFromCurrent(false); } - if (IsCreature() && IsAIEnabled) + if (IsCreature() && IsAIEnabled()) ToCreature().GetAI().OnSpellCastInterrupt(spell.GetSpellInfo()); } } @@ -2595,7 +2595,7 @@ namespace Game.Entities } Creature creature = ToCreature(); - if (creature && creature.IsAIEnabled) + if (creature && creature.IsAIEnabled()) creature.GetAI().OnSpellClick(clicker, ref spellClickHandled); } diff --git a/Source/Game/Entities/Unit/Unit.cs b/Source/Game/Entities/Unit/Unit.cs index a617509fb..a72b3cbb6 100644 --- a/Source/Game/Entities/Unit/Unit.cs +++ b/Source/Game/Entities/Unit/Unit.cs @@ -172,6 +172,9 @@ namespace Game.Entities UpdateSplineMovement(diff); GetMotionMaster().Update(diff); + + if (i_AI == null && (!IsPlayer() || IsCharmed())) + UpdateCharmAI(); } void _UpdateSpells(uint diff) @@ -438,8 +441,9 @@ namespace Game.Entities if (IsInWorld) { m_duringRemoveFromWorld = true; - if (IsAIEnabled) - GetAI().LeavingWorld(); + UnitAI ai = GetAI(); + if (ai != null) + ai.LeavingWorld(); if (IsVehicle()) RemoveVehicleKit(true); @@ -537,14 +541,14 @@ namespace Game.Entities public void _RegisterDynObject(DynamicObject dynObj) { m_dynObj.Add(dynObj); - if (IsTypeId(TypeId.Unit) && IsAIEnabled) + if (IsTypeId(TypeId.Unit) && IsAIEnabled()) ToCreature().GetAI().JustRegisteredDynObject(dynObj); } public void _UnregisterDynObject(DynamicObject dynObj) { m_dynObj.Remove(dynObj); - if (IsTypeId(TypeId.Unit) && IsAIEnabled) + if (IsTypeId(TypeId.Unit) && IsAIEnabled()) ToCreature().GetAI().JustUnregisteredDynObject(dynObj); } @@ -611,7 +615,7 @@ namespace Game.Entities GetSpellHistory().StartCooldown(createBySpell, 0, null, true); } - if (IsTypeId(TypeId.Unit) && ToCreature().IsAIEnabled) + if (IsTypeId(TypeId.Unit) && ToCreature().IsAIEnabled()) ToCreature().GetAI().JustSummonedGameobject(gameObj); } @@ -646,7 +650,7 @@ namespace Game.Entities m_gameObj.Remove(gameObj); - if (IsTypeId(TypeId.Unit) && ToCreature().IsAIEnabled) + if (IsTypeId(TypeId.Unit) && ToCreature().IsAIEnabled()) ToCreature().GetAI().SummonedGameobjectDespawn(gameObj); if (del) @@ -694,14 +698,14 @@ namespace Game.Entities public void _RegisterAreaTrigger(AreaTrigger areaTrigger) { m_areaTrigger.Add(areaTrigger); - if (IsTypeId(TypeId.Unit) && IsAIEnabled) + if (IsTypeId(TypeId.Unit) && IsAIEnabled()) ToCreature().GetAI().JustRegisteredAreaTrigger(areaTrigger); } public void _UnregisterAreaTrigger(AreaTrigger areaTrigger) { m_areaTrigger.Remove(areaTrigger); - if (IsTypeId(TypeId.Unit) && IsAIEnabled) + if (IsTypeId(TypeId.Unit) && IsAIEnabled()) ToCreature().GetAI().JustUnregisteredAreaTrigger(areaTrigger); } @@ -1160,8 +1164,51 @@ namespace Game.Entities return m_vehicle != null && m_vehicle == vehicle.GetVehicleKit(); } + public bool IsAIEnabled() { return i_AI != null; } + public virtual UnitAI GetAI() { return i_AI; } - public void SetAI(UnitAI newAI) { i_AI = newAI; } + + public void AIUpdateTick(uint diff, bool force = false) + { + if (diff == 0) // some places call with diff = 0, which does nothing (for now), see PR #22296 + return; + + UnitAI ai = GetAI(); + if (ai != null) + ai.UpdateAI(diff); + } + + public void SetAI(UnitAI newAI) + { + if (i_AI != null) + AIUpdateTick(0, true); // old AI gets a final tick if enabled + + i_AI = newAI; + AIUpdateTick(0, true); // new AI gets its initial tick + } + + public void ScheduleAIChange() + { + bool charmed = IsCharmed(); + // if charm is applied, we can't have disabled AI already, and vice versa + if (charmed) + Cypher.Assert(i_disabledAI == null, "Attempt to schedule charm AI change on unit that already has disabled AI"); + else if (!IsPlayer()) + Cypher.Assert(i_disabledAI != null, "Attempt to schedule charm ID change on unit that doesn't have disabled AI"); + + if (charmed) + i_disabledAI = i_AI; + else + i_AI = null; + } + + void RestoreDisabledAI() + { + Cypher.Assert(IsPlayer() || i_disabledAI != null, "Attempt to restore disabled AI on creature without disabled AI"); + i_AI = i_disabledAI; + AIUpdateTick(0, true); + } + public bool IsPossessing() { @@ -2270,11 +2317,13 @@ namespace Game.Entities public static uint DealDamage(Unit attacker, Unit victim, uint damage, CleanDamage cleanDamage = null, DamageEffectType damagetype = DamageEffectType.Direct, SpellSchoolMask damageSchoolMask = SpellSchoolMask.Normal, SpellInfo spellProto = null, bool durabilityLoss = true) { - if (victim.IsAIEnabled) - victim.GetAI().DamageTaken(attacker, ref damage); + UnitAI victimAI = victim.GetAI(); + if (victimAI != null) + victimAI.DamageTaken(attacker, ref damage); - if (attacker != null && attacker.IsAIEnabled) - attacker.GetAI().DamageDealt(victim, ref damage, damagetype); + UnitAI attackerAI = attacker ? attacker.GetAI() : null; + if (attackerAI != null) + attackerAI.DamageDealt(victim, ref damage, damagetype); // Hook for OnDamage Event Global.ScriptMgr.OnDamage(attacker, victim, ref damage); @@ -2288,8 +2337,11 @@ namespace Game.Entities { Creature cControlled = controlled.ToCreature(); if (cControlled != null) - if (cControlled.IsAIEnabled) - cControlled.GetAI().OwnerAttackedBy(attacker); + { + CreatureAI controlledAI = cControlled.GetAI(); + if (controlledAI != null) + controlledAI.OwnerAttackedBy(attacker); + } } } diff --git a/Source/Game/Entities/Vehicle.cs b/Source/Game/Entities/Vehicle.cs index 2de86dab4..dadd1f25d 100644 --- a/Source/Game/Entities/Vehicle.cs +++ b/Source/Game/Entities/Vehicle.cs @@ -17,6 +17,7 @@ using Framework.Constants; using Framework.Dynamic; +using Game.AI; using Game.BattleGrounds; using Game.DataStorage; using Game.Movement; @@ -361,7 +362,7 @@ namespace Game.Entities if (unit.IsFlying()) _me.CastSpell(unit, SharedConst.VehicleSpellParachute, true); - if (_me.IsTypeId(TypeId.Unit) && _me.ToCreature().IsAIEnabled) + if (_me.IsTypeId(TypeId.Unit) && _me.ToCreature().IsAIEnabled()) _me.ToCreature().GetAI().PassengerBoarded(unit, seat.Key, false); if (GetBase().IsTypeId(TypeId.Unit)) @@ -646,8 +647,9 @@ namespace Game.Entities Creature creature = Target.GetBase().ToCreature(); if (creature != null) { - if (creature.IsAIEnabled) - creature.GetAI().PassengerBoarded(Passenger, Seat.Key, true); + CreatureAI ai = creature.GetAI(); + if (ai != null) + ai.PassengerBoarded(Passenger, Seat.Key, true); Global.ScriptMgr.OnAddPassenger(Target, Passenger, Seat.Key); diff --git a/Source/Game/Events/GameEventManager.cs b/Source/Game/Events/GameEventManager.cs index c4dfdf3f1..650b86028 100644 --- a/Source/Game/Events/GameEventManager.cs +++ b/Source/Game/Events/GameEventManager.cs @@ -1749,7 +1749,7 @@ namespace Game for (var i = 0; i < objs.Count; ++i) { Creature creature = objs[i]; - if (creature.IsInWorld && creature.IsAIEnabled) + if (creature.IsInWorld && creature.IsAIEnabled()) creature.GetAI().OnGameEvent(_activate, _eventId); } } diff --git a/Source/Game/Handlers/PetHandler.cs b/Source/Game/Handlers/PetHandler.cs index 6a6089340..5d59a0f71 100644 --- a/Source/Game/Handlers/PetHandler.cs +++ b/Source/Game/Handlers/PetHandler.cs @@ -165,68 +165,68 @@ namespace Game charmInfo.SetIsFollowing(false); break; case CommandStates.Attack: //spellid=1792 //ATTACK + { + // Can't attack if owner is pacified + if (GetPlayer().HasAuraType(AuraType.ModPacify)) { - // Can't attack if owner is pacified - if (GetPlayer().HasAuraType(AuraType.ModPacify)) - { - // @todo Send proper error message to client - return; - } + // @todo Send proper error message to client + return; + } - // only place where pet can be player - Unit TargetUnit = Global.ObjAccessor.GetUnit(GetPlayer(), guid2); - if (!TargetUnit) + // only place where pet can be player + Unit TargetUnit = Global.ObjAccessor.GetUnit(GetPlayer(), guid2); + if (!TargetUnit) + return; + + Unit owner = pet.GetOwner(); + if (owner) + if (!owner.IsValidAttackTarget(TargetUnit)) return; - Unit owner = pet.GetOwner(); - if (owner) - if (!owner.IsValidAttackTarget(TargetUnit)) - return; + pet.ClearUnitState(UnitState.Follow); + // This is true if pet has no target or has target but targets differs. + if (pet.GetVictim() != TargetUnit || !pet.GetCharmInfo().IsCommandAttack()) + { + if (pet.GetVictim()) + pet.AttackStop(); - pet.ClearUnitState(UnitState.Follow); - // This is true if pet has no target or has target but targets differs. - if (pet.GetVictim() != TargetUnit || !pet.GetCharmInfo().IsCommandAttack()) + if (!pet.IsTypeId(TypeId.Player) && pet.ToCreature().IsAIEnabled()) { - if (pet.GetVictim()) - pet.AttackStop(); + charmInfo.SetIsCommandAttack(true); + charmInfo.SetIsAtStay(false); + charmInfo.SetIsFollowing(false); + charmInfo.SetIsCommandFollow(false); + charmInfo.SetIsReturning(false); - if (!pet.IsTypeId(TypeId.Player) && pet.ToCreature().IsAIEnabled) + CreatureAI AI = pet.ToCreature().GetAI(); + if (AI is PetAI) + ((PetAI)AI)._AttackStart(TargetUnit); // force target switch + else + AI.AttackStart(TargetUnit); + + //10% chance to play special pet attack talk, else growl + if (pet.IsPet() && pet.ToPet().GetPetType() == PetType.Summon && pet != TargetUnit && RandomHelper.IRand(0, 100) < 10) + pet.SendPetTalk(PetTalk.Attack); + else { - charmInfo.SetIsCommandAttack(true); - charmInfo.SetIsAtStay(false); - charmInfo.SetIsFollowing(false); - charmInfo.SetIsCommandFollow(false); - charmInfo.SetIsReturning(false); - - CreatureAI AI = pet.ToCreature().GetAI(); - if (AI is PetAI) - ((PetAI)AI)._AttackStart(TargetUnit); // force target switch - else - AI.AttackStart(TargetUnit); - - //10% chance to play special pet attack talk, else growl - if (pet.IsPet() && pet.ToPet().GetPetType() == PetType.Summon && pet != TargetUnit && RandomHelper.IRand(0, 100) < 10) - pet.SendPetTalk(PetTalk.Attack); - else - { - // 90% chance for pet and 100% chance for charmed creature - pet.SendPetAIReaction(guid1); - } - } - else // charmed player - { - charmInfo.SetIsCommandAttack(true); - charmInfo.SetIsAtStay(false); - charmInfo.SetIsFollowing(false); - charmInfo.SetIsCommandFollow(false); - charmInfo.SetIsReturning(false); - - pet.Attack(TargetUnit, true); + // 90% chance for pet and 100% chance for charmed creature pet.SendPetAIReaction(guid1); } } - break; + else // charmed player + { + charmInfo.SetIsCommandAttack(true); + charmInfo.SetIsAtStay(false); + charmInfo.SetIsFollowing(false); + charmInfo.SetIsCommandFollow(false); + charmInfo.SetIsReturning(false); + + pet.Attack(TargetUnit, true); + pet.SendPetAIReaction(guid1); + } } + break; + } case CommandStates.Abandon: // abandon (hunter pet) or dismiss (summoned pet) if (pet.GetCharmerGUID() == GetPlayer().GetGUID()) GetPlayer().StopCastingCharm(); @@ -280,127 +280,127 @@ namespace Game case ActiveStates.Disabled: // 0x81 spell (disabled), ignore case ActiveStates.Passive: // 0x01 case ActiveStates.Enabled: // 0xC1 spell + { + Unit unit_target = null; + + if (!guid2.IsEmpty()) + unit_target = Global.ObjAccessor.GetUnit(GetPlayer(), guid2); + + // do not cast unknown spells + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellid, pet.GetMap().GetDifficultyID()); + if (spellInfo == null) { - Unit unit_target = null; + Log.outError(LogFilter.Network, "WORLD: unknown PET spell id {0}", spellid); + return; + } - if (!guid2.IsEmpty()) - unit_target = Global.ObjAccessor.GetUnit(GetPlayer(), guid2); - - // do not cast unknown spells - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellid, pet.GetMap().GetDifficultyID()); - if (spellInfo == null) - { - Log.outError(LogFilter.Network, "WORLD: unknown PET spell id {0}", spellid); + foreach (var spellEffectInfo in spellInfo.GetEffects()) + { + if (spellEffectInfo.TargetA.GetTarget() == Targets.UnitSrcAreaEnemy || spellEffectInfo.TargetA.GetTarget() == Targets.UnitDestAreaEnemy || spellEffectInfo.TargetA.GetTarget() == Targets.DestDynobjEnemy) return; - } + } - foreach (var spellEffectInfo in spellInfo.GetEffects()) + // do not cast not learned spells + if (!pet.HasSpell(spellid) || spellInfo.IsPassive()) + return; + + // Clear the flags as if owner clicked 'attack'. AI will reset them + // after AttackStart, even if spell failed + if (pet.GetCharmInfo() != null) + { + pet.GetCharmInfo().SetIsAtStay(false); + pet.GetCharmInfo().SetIsCommandAttack(true); + pet.GetCharmInfo().SetIsReturning(false); + pet.GetCharmInfo().SetIsFollowing(false); + } + + Spell spell = new(pet, spellInfo, TriggerCastFlags.None); + + SpellCastResult result = spell.CheckPetCast(unit_target); + + //auto turn to target unless possessed + if (result == SpellCastResult.UnitNotInfront && !pet.IsPossessed() && !pet.IsVehicle()) + { + Unit unit_target2 = spell.m_targets.GetUnitTarget(); + if (unit_target) { - if (spellEffectInfo.TargetA.GetTarget() == Targets.UnitSrcAreaEnemy || spellEffectInfo.TargetA.GetTarget() == Targets.UnitDestAreaEnemy || spellEffectInfo.TargetA.GetTarget() == Targets.DestDynobjEnemy) - return; + if (!pet.IsFocusing()) + pet.SetInFront(unit_target); + Player player = unit_target.ToPlayer(); + if (player) + pet.SendUpdateToPlayer(player); } - - // do not cast not learned spells - if (!pet.HasSpell(spellid) || spellInfo.IsPassive()) - return; - - // Clear the flags as if owner clicked 'attack'. AI will reset them - // after AttackStart, even if spell failed - if (pet.GetCharmInfo() != null) + else if (unit_target2) { - pet.GetCharmInfo().SetIsAtStay(false); - pet.GetCharmInfo().SetIsCommandAttack(true); - pet.GetCharmInfo().SetIsReturning(false); - pet.GetCharmInfo().SetIsFollowing(false); + if (!pet.IsFocusing()) + pet.SetInFront(unit_target2); + Player player = unit_target2.ToPlayer(); + if (player) + pet.SendUpdateToPlayer(player); } - - Spell spell = new(pet, spellInfo, TriggerCastFlags.None); - - SpellCastResult result = spell.CheckPetCast(unit_target); - - //auto turn to target unless possessed - if (result == SpellCastResult.UnitNotInfront && !pet.IsPossessed() && !pet.IsVehicle()) + Unit powner = pet.GetCharmerOrOwner(); + if (powner) { - Unit unit_target2 = spell.m_targets.GetUnitTarget(); - if (unit_target) - { - if (!pet.IsFocusing()) - pet.SetInFront(unit_target); - Player player = unit_target.ToPlayer(); - if (player) - pet.SendUpdateToPlayer(player); - } - else if (unit_target2) - { - if (!pet.IsFocusing()) - pet.SetInFront(unit_target2); - Player player = unit_target2.ToPlayer(); - if (player) - pet.SendUpdateToPlayer(player); - } - Unit powner = pet.GetCharmerOrOwner(); - if (powner) - { - Player player = powner.ToPlayer(); - if (player) - pet.SendUpdateToPlayer(player); - } - - result = SpellCastResult.SpellCastOk; + Player player = powner.ToPlayer(); + if (player) + pet.SendUpdateToPlayer(player); } - if (result == SpellCastResult.SpellCastOk) - { - unit_target = spell.m_targets.GetUnitTarget(); + result = SpellCastResult.SpellCastOk; + } - //10% chance to play special pet attack talk, else growl - //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell - if (pet.IsPet() && (pet.ToPet().GetPetType() == PetType.Summon) && (pet != unit_target) && (RandomHelper.IRand(0, 100) < 10)) - pet.SendPetTalk(PetTalk.SpecialSpell); - else - { - pet.SendPetAIReaction(guid1); - } + if (result == SpellCastResult.SpellCastOk) + { + unit_target = spell.m_targets.GetUnitTarget(); - if (unit_target && !GetPlayer().IsFriendlyTo(unit_target) && !pet.IsPossessed() && !pet.IsVehicle()) - { - // This is true if pet has no target or has target but targets differs. - if (pet.GetVictim() != unit_target) - { - pet.GetMotionMaster().Clear(); - if (pet.ToCreature().IsAIEnabled) - { - CreatureAI AI = pet.ToCreature().GetAI(); - PetAI petAI = (PetAI)AI; - if (petAI != null) - petAI._AttackStart(unit_target); // force victim switch - else - AI.AttackStart(unit_target); - } - } - } - - spell.Prepare(spell.m_targets); - } + //10% chance to play special pet attack talk, else growl + //actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell + if (pet.IsPet() && (pet.ToPet().GetPetType() == PetType.Summon) && (pet != unit_target) && (RandomHelper.IRand(0, 100) < 10)) + pet.SendPetTalk(PetTalk.SpecialSpell); else { - if (pet.IsPossessed() || pet.IsVehicle()) // @todo: confirm this check - Spell.SendCastResult(GetPlayer(), spellInfo, spell.m_SpellVisual, spell.m_castId, result); - else - spell.SendPetCastResult(result); - - if (!pet.GetSpellHistory().HasCooldown(spellid)) - pet.GetSpellHistory().ResetCooldown(spellid, true); - - spell.Finish(false); - spell.Dispose(); - - // reset specific flags in case of spell fail. AI will reset other flags - if (pet.GetCharmInfo() != null) - pet.GetCharmInfo().SetIsCommandAttack(false); + pet.SendPetAIReaction(guid1); } - break; + + if (unit_target && !GetPlayer().IsFriendlyTo(unit_target) && !pet.IsPossessed() && !pet.IsVehicle()) + { + // This is true if pet has no target or has target but targets differs. + if (pet.GetVictim() != unit_target) + { + pet.GetMotionMaster().Clear(); + CreatureAI ai = pet.ToCreature().GetAI(); + if (ai != null) + { + PetAI petAI = (PetAI)ai; + if (petAI != null) + petAI._AttackStart(unit_target); // force victim switch + else + ai.AttackStart(unit_target); + } + } + } + + spell.Prepare(spell.m_targets); } + else + { + if (pet.IsPossessed() || pet.IsVehicle()) // @todo: confirm this check + Spell.SendCastResult(GetPlayer(), spellInfo, spell.m_SpellVisual, spell.m_castId, result); + else + spell.SendPetCastResult(result); + + if (!pet.GetSpellHistory().HasCooldown(spellid)) + pet.GetSpellHistory().ResetCooldown(spellid, true); + + spell.Finish(false); + spell.Dispose(); + + // reset specific flags in case of spell fail. AI will reset other flags + if (pet.GetCharmInfo() != null) + pet.GetCharmInfo().SetIsCommandAttack(false); + } + break; + } default: Log.outError(LogFilter.Network, "WORLD: unknown PET flag Action {0} and spellid {1}.", flag, spellid); break; diff --git a/Source/Game/Maps/GridNotifiers.cs b/Source/Game/Maps/GridNotifiers.cs index 770abecd3..9171f2df3 100644 --- a/Source/Game/Maps/GridNotifiers.cs +++ b/Source/Game/Maps/GridNotifiers.cs @@ -45,11 +45,11 @@ namespace Game.Maps if (!c.HasUnitState(UnitState.Sightless)) { - if (c.IsAIEnabled && c.CanSeeOrDetect(u, false, true)) + if (c.IsAIEnabled() && c.CanSeeOrDetect(u, false, true)) c.GetAI().MoveInLineOfSight_Safe(u); else { - if (u.IsTypeId(TypeId.Player) && u.HasStealthAura() && c.IsAIEnabled && c.CanSeeOrDetect(u, false, true, true)) + if (u.IsTypeId(TypeId.Player) && u.HasStealthAura() && c.IsAIEnabled() && c.CanSeeOrDetect(u, false, true, true)) c.GetAI().TriggerAlert(u); } } @@ -939,8 +939,7 @@ namespace Game.Maps if (!u.IsWithinLOSInMap(i_enemy)) return; - if (u.GetAI() != null) - u.GetAI().AttackStart(i_enemy); + u.EngageWithTarget(i_enemy); } Unit i_funit; diff --git a/Source/Game/Maps/ObjectGridLoader.cs b/Source/Game/Maps/ObjectGridLoader.cs index 94f15f9aa..9be8aa3bb 100644 --- a/Source/Game/Maps/ObjectGridLoader.cs +++ b/Source/Game/Maps/ObjectGridLoader.cs @@ -217,7 +217,7 @@ namespace Game.Maps { creature.CombatStop(); creature.GetThreatManager().ClearAllThreat(); - if (creature.IsAIEnabled) + if (creature.IsAIEnabled()) creature.GetAI().EnterEvadeMode(); } } diff --git a/Source/Game/Movement/Generators/ChaseMovementGenerator.cs b/Source/Game/Movement/Generators/ChaseMovementGenerator.cs index eb68f44d3..02241c13e 100644 --- a/Source/Game/Movement/Generators/ChaseMovementGenerator.cs +++ b/Source/Game/Movement/Generators/ChaseMovementGenerator.cs @@ -255,9 +255,9 @@ namespace Game.Movement if (!owner.IsCreature()) return; - Creature creatureOwner = owner.ToCreature(); - if (creatureOwner.IsAIEnabled && creatureOwner.GetAI() != null) - creatureOwner.GetAI().MovementInform(MovementGeneratorType.Chase, (uint)target.GetGUID().GetCounter()); + CreatureAI ai = owner.ToCreature().GetAI(); + if (ai != null) + ai.MovementInform(MovementGeneratorType.Chase, (uint)target.GetGUID().GetCounter()); } public Unit GetTarget() diff --git a/Source/Game/Movement/Generators/FollowMovementGenerator.cs b/Source/Game/Movement/Generators/FollowMovementGenerator.cs index 6924f8e0c..264c887ab 100644 --- a/Source/Game/Movement/Generators/FollowMovementGenerator.cs +++ b/Source/Game/Movement/Generators/FollowMovementGenerator.cs @@ -217,9 +217,9 @@ namespace Game.Movement if (!owner.IsCreature()) return; - Creature creatureOwner = owner.ToCreature(); - if (creatureOwner.IsAIEnabled && creatureOwner.GetAI() != null) - creatureOwner.GetAI().MovementInform(MovementGeneratorType.Follow, (uint)target.GetGUID().GetCounter()); + CreatureAI ai = owner.ToCreature().GetAI(); + if (ai != null) + ai.MovementInform(MovementGeneratorType.Follow, (uint)target.GetGUID().GetCounter()); } } } diff --git a/Source/Game/Movement/Generators/SplineChainMovementGenerator.cs b/Source/Game/Movement/Generators/SplineChainMovementGenerator.cs index 318d4f38e..65c474412 100644 --- a/Source/Game/Movement/Generators/SplineChainMovementGenerator.cs +++ b/Source/Game/Movement/Generators/SplineChainMovementGenerator.cs @@ -17,6 +17,7 @@ using Framework.Constants; using Framework.GameMath; +using Game.AI; using Game.Entities; using System; using System.Collections.Generic; @@ -164,9 +165,9 @@ namespace Game.Movement if (movementInform && HasFlag(MovementGeneratorFlags.InformEnabled)) { - Creature ownerCreature = owner.ToCreature(); - if (ownerCreature != null && ownerCreature.IsAIEnabled) - ownerCreature.GetAI().MovementInform(MovementGeneratorType.SplineChain, _id); + CreatureAI ai = owner.ToCreature().GetAI(); + if (ai != null) + ai.MovementInform(MovementGeneratorType.SplineChain, _id); } } diff --git a/Source/Game/Movement/Generators/WaypointMovement.cs b/Source/Game/Movement/Generators/WaypointMovement.cs index 3a2ff6b24..e6a6b6e12 100644 --- a/Source/Game/Movement/Generators/WaypointMovement.cs +++ b/Source/Game/Movement/Generators/WaypointMovement.cs @@ -16,6 +16,7 @@ */ using Framework.Constants; +using Game.AI; using Game.Entities; using System.Collections.Generic; using System.Linq; @@ -116,8 +117,9 @@ namespace Game.Movement _nextMoveTime.Reset(1000); // inform AI - if (owner.IsAIEnabled) - owner.GetAI().WaypointPathStarted(_path.id); + CreatureAI ai = owner.GetAI(); + if (ai != null) + ai.WaypointPathStarted(_path.id); } public override void DoReset(Creature owner) @@ -230,8 +232,9 @@ namespace Game.Movement void MovementInform(Creature owner) { - if (owner.IsAIEnabled) - owner.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode); + CreatureAI ai = owner.GetAI(); + if (ai != null) + ai.MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode); } void OnArrived(Creature owner) @@ -255,10 +258,11 @@ namespace Game.Movement } // inform AI - if (owner.IsAIEnabled) + CreatureAI ai = owner.GetAI(); + if (ai != null) { - owner.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode); - owner.GetAI().WaypointReached(waypoint.id, _path.id); + ai.MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode); + ai.WaypointReached(waypoint.id, _path.id); } owner.UpdateCurrentWaypointInfo(waypoint.id, _path.id); @@ -284,8 +288,9 @@ namespace Game.Movement { Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator.StartMove: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})"); // inform AI - if (owner.IsAIEnabled) - owner.GetAI().WaypointStarted(_path.nodes[_currentNode].id, _path.id); + CreatureAI ai = owner.GetAI(); + if (ai != null) + ai.WaypointStarted(_path.nodes[_currentNode].id, _path.id); } else { @@ -314,8 +319,9 @@ namespace Game.Movement owner.UpdateCurrentWaypointInfo(0, 0); // inform AI - if (owner.IsAIEnabled) - owner.GetAI().WaypointPathEnded(currentWaypoint.id, _path.id); + CreatureAI ai = owner.GetAI(); + if (ai != null) + ai.WaypointPathEnded(currentWaypoint.id, _path.id); return; } } @@ -324,8 +330,9 @@ namespace Game.Movement AddFlag(MovementGeneratorFlags.Initialized); // inform AI - if (owner.IsAIEnabled) - owner.GetAI().WaypointStarted(_path.nodes[_currentNode].id, _path.id); + CreatureAI ai = owner.GetAI(); + if (ai != null) + ai.WaypointStarted(_path.nodes[_currentNode].id, _path.id); } Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator.StartMove: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})"); diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 65741da30..fc9c76494 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -17,6 +17,7 @@ using Framework.Constants; using Framework.Dynamic; +using Game.AI; using Game.BattleFields; using Game.BattleGrounds; using Game.Conditions; @@ -2507,8 +2508,11 @@ namespace Game.Spells { Creature cControlled = controlled.ToCreature(); if (cControlled != null) - if (cControlled.IsAIEnabled) - cControlled.GetAI().OwnerAttacked(target); + { + CreatureAI controlledAI = cControlled.GetAI(); + if (controlledAI != null) + controlledAI.OwnerAttacked(target); + } } } } @@ -2778,7 +2782,7 @@ namespace Game.Spells // Call CreatureAI hook OnSuccessfulSpellCast Creature caster = m_originalCaster.ToCreature(); if (caster) - if (caster.IsAIEnabled) + if (caster.IsAIEnabled()) caster.GetAI().OnSuccessfulSpellCast(GetSpellInfo()); } @@ -8005,18 +8009,20 @@ namespace Game.Spells if (_spellHitTarget) { //AI functions - if (_spellHitTarget.IsCreature()) + Creature cHitTarget = _spellHitTarget.ToCreature(); + if (cHitTarget != null) { - if (_spellHitTarget.ToCreature().IsAIEnabled) + CreatureAI hitTargetAI = cHitTarget.GetAI(); + if (hitTargetAI != null) { if (spell.GetCaster().IsGameObject()) - _spellHitTarget.ToCreature().GetAI().SpellHit(spell.GetCaster().ToGameObject(), spell.m_spellInfo); + hitTargetAI.SpellHit(spell.GetCaster().ToGameObject(), spell.m_spellInfo); else - _spellHitTarget.ToCreature().GetAI().SpellHit(spell.GetCaster().ToUnit(), spell.m_spellInfo); + hitTargetAI.SpellHit(spell.GetCaster().ToUnit(), spell.m_spellInfo); } } - if (spell.GetCaster().IsCreature() && spell.GetCaster().ToCreature().IsAIEnabled) + if (spell.GetCaster().IsCreature() && spell.GetCaster().ToCreature().IsAIEnabled()) spell.GetCaster().ToCreature().GetAI().SpellHitTarget(_spellHitTarget, spell.m_spellInfo); else if (spell.GetCaster().IsGameObject() && spell.GetCaster().ToGameObject().GetAI() != null) spell.GetCaster().ToGameObject().GetAI().SpellHitTarget(_spellHitTarget, spell.m_spellInfo); @@ -8072,7 +8078,7 @@ namespace Game.Spells go.GetAI().SpellHit(spell.GetCaster().ToUnit(), spell.m_spellInfo); } - if (spell.GetCaster().IsCreature() && spell.GetCaster().ToCreature().IsAIEnabled) + if (spell.GetCaster().IsCreature() && spell.GetCaster().ToCreature().IsAIEnabled()) spell.GetCaster().ToCreature().GetAI().SpellHitTarget(go, spell.m_spellInfo); else if (spell.GetCaster().IsGameObject() && spell.GetCaster().ToGameObject().GetAI() != null) spell.GetCaster().ToGameObject().GetAI().SpellHitTarget(go, spell.m_spellInfo); diff --git a/Source/Scripts/Spells/Generic.cs b/Source/Scripts/Spells/Generic.cs index b17ca6aa0..094053dd9 100644 --- a/Source/Scripts/Spells/Generic.cs +++ b/Source/Scripts/Spells/Generic.cs @@ -1549,7 +1549,7 @@ namespace Scripts.Spells.Generic GetUnitOwner().GetAllMinionsByEntry(minionList, CreatureIds.EtherealSoulTrader); foreach (Creature minion in minionList) { - if (minion.IsAIEnabled) + if (minion.IsAIEnabled()) { minion.GetAI().Talk(TextIds.SayStealEssence); minion.CastSpell(eventInfo.GetProcTarget(), SpellIds.StealEssenceVisual); diff --git a/Source/Scripts/Spells/Quest.cs b/Source/Scripts/Spells/Quest.cs index fdcba5d9c..ef08881ef 100644 --- a/Source/Scripts/Spells/Quest.cs +++ b/Source/Scripts/Spells/Quest.cs @@ -327,8 +327,8 @@ namespace Scripts.Spells.Quest if (!creatureTarget.IsPet() && creatureTarget.GetEntry() == _originalEntry) { creatureTarget.UpdateEntry(_newEntry); - if (_shouldAttack && creatureTarget.IsAIEnabled) - creatureTarget.GetAI().AttackStart(GetCaster()); + if (_shouldAttack) + creatureTarget.EngageWithTarget(GetCaster()); if (_despawnTime != 0) creatureTarget.DespawnOrUnsummon(_despawnTime);