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);