diff --git a/Source/Framework/Constants/SmartAIConst.cs b/Source/Framework/Constants/SmartAIConst.cs index 3fa44b7b0..d662b19a2 100644 --- a/Source/Framework/Constants/SmartAIConst.cs +++ b/Source/Framework/Constants/SmartAIConst.cs @@ -435,7 +435,7 @@ namespace Framework.Constants LootRecipients = 27, // all players that have tagged this creature (for kill credit) Farthest = 28, // maxDist, playerOnly, isInLos VehiclePassenger = 29, // seatMask (0 - all seats) - SpellTarget = 30, + ClosestUnspawnedGameobject = 30, End = 31 } diff --git a/Source/Game/AI/CoreAI/CreatureAI.cs b/Source/Game/AI/CoreAI/CreatureAI.cs index c69b51e95..2d2b74a42 100644 --- a/Source/Game/AI/CoreAI/CreatureAI.cs +++ b/Source/Game/AI/CoreAI/CreatureAI.cs @@ -493,17 +493,17 @@ namespace Game.AI public virtual void SpellHitTarget(Unit target, SpellInfo spellInfo) { } public virtual void SpellHitTargetGameObject(GameObject target, SpellInfo spellInfo) { } - // Should return true if the NPC is currently being escorted - public virtual bool IsEscorted() { return false; } - - public virtual void MovementInform(MovementGeneratorType type, uint id) { } - // Called when a spell cast gets interrupted public virtual void OnSpellCastInterrupt(SpellInfo spell) { } // Called when a spell cast has been successfully finished public virtual void OnSuccessfulSpellCast(SpellInfo spell) { } + // Should return true if the NPC is currently being escorted + public virtual bool IsEscorted() { return false; } + + public virtual void MovementInform(MovementGeneratorType type, uint id) { } + // Called at reaching home after evade public virtual void JustReachedHome() { } diff --git a/Source/Game/AI/SmartScripts/SmartAIManager.cs b/Source/Game/AI/SmartScripts/SmartAIManager.cs index d4214d101..63a0a690a 100644 --- a/Source/Game/AI/SmartScripts/SmartAIManager.cs +++ b/Source/Game/AI/SmartScripts/SmartAIManager.cs @@ -513,7 +513,7 @@ namespace Game.AI case SmartTargets.LootRecipients: case SmartTargets.Farthest: case SmartTargets.VehiclePassenger: - case SmartTargets.SpellTarget: + case SmartTargets.ClosestUnspawnedGameobject: break; default: Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled target_type({0}), Entry {1} SourceType {2} Event {3} Action {4}, skipped.", e.GetTargetType(), e.EntryOrGuid, e.GetScriptType(), e.EventId, e.GetActionType()); diff --git a/Source/Game/AI/SmartScripts/SmartScript.cs b/Source/Game/AI/SmartScripts/SmartScript.cs index 6d6c3c2b6..c0eba4ef2 100644 --- a/Source/Game/AI/SmartScripts/SmartScript.cs +++ b/Source/Game/AI/SmartScripts/SmartScript.cs @@ -2006,7 +2006,8 @@ namespace Game.AI e.GetTargetType() == SmartTargets.GameobjectGuid || e.GetTargetType() == SmartTargets.GameobjectDistance || e.GetTargetType() == SmartTargets.ClosestCreature || e.GetTargetType() == SmartTargets.ClosestGameobject || e.GetTargetType() == SmartTargets.OwnerOrSummoner || e.GetTargetType() == SmartTargets.ActionInvoker || - e.GetTargetType() == SmartTargets.ClosestEnemy || e.GetTargetType() == SmartTargets.ClosestFriendly) + e.GetTargetType() == SmartTargets.ClosestEnemy || e.GetTargetType() == SmartTargets.ClosestFriendly || + e.GetTargetType() == SmartTargets.ClosestUnspawnedGameobject) { target.ToCreature().SetHomePosition(target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), target.GetOrientation()); } @@ -3089,6 +3090,13 @@ namespace Game.AI } break; } + case SmartTargets.ClosestUnspawnedGameobject: + { + GameObject target = baseObject.FindNearestUnspawnedGameObject(e.Target.closest.entry, (float)(e.Target.closest.dist != 0 ? e.Target.closest.dist : 100)); + if (target != null) + targets.Add(target); + break; + } case SmartTargets.Position: default: break; diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index 408b988e4..ab8ee6caa 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -1540,6 +1540,15 @@ namespace Game.Entities return searcher.GetTarget(); } + public GameObject FindNearestUnspawnedGameObject(uint entry, float range) + { + NearestUnspawnedGameObjectEntryInObjectRangeCheck checker = new(this, entry, range); + GameObjectLastSearcher searcher = new(this, checker); + + Cell.VisitGridObjects(this, searcher, range); + return searcher.GetTarget(); + } + public GameObject FindNearestGameObjectOfType(GameObjectTypes type, float range) { var checker = new NearestGameObjectTypeInObjectRangeCheck(this, type, range); diff --git a/Source/Game/Maps/GridNotifiers.cs b/Source/Game/Maps/GridNotifiers.cs index 5706841cb..a35bd8ad4 100644 --- a/Source/Game/Maps/GridNotifiers.cs +++ b/Source/Game/Maps/GridNotifiers.cs @@ -2772,6 +2772,31 @@ namespace Game.Maps float i_range; } + // Success at unit in range, range update for next check (this can be use with GameobjectLastSearcher to find nearest unspawned GO) + class NearestUnspawnedGameObjectEntryInObjectRangeCheck : ICheck + { + WorldObject i_obj; + uint i_entry; + float i_range; + + public NearestUnspawnedGameObjectEntryInObjectRangeCheck(WorldObject obj, uint entry, float range) + { + i_obj = obj; + i_entry = entry; + i_range = range; + } + + public bool Invoke(GameObject go) + { + if (!go.IsSpawned() && go.GetEntry() == i_entry && go.GetGUID() != i_obj.GetGUID() && i_obj.IsWithinDistInMap(go, i_range)) + { + i_range = i_obj.GetDistance(go); // use found GO range as new range limit for next check + return true; + } + return false; + } + } + // Success at unit in range, range update for next check (this can be use with GameobjectLastSearcher to find nearest GO with a certain type) class NearestGameObjectTypeInObjectRangeCheck : ICheck {