From 1657f720dceb2379e911e2fe7b418b924a38962b Mon Sep 17 00:00:00 2001 From: hondacrx Date: Fri, 30 Aug 2019 12:00:42 -0400 Subject: [PATCH] Core/SmartAI: Various fixes and extensions for smart scripts: Port From (https://github.com/TrinityCore/TrinityCore/commit/a32d5cfa1762ae1158ee2f40b4c9b36e5b41913a) --- Source/Framework/Constants/SmartAIConst.cs | 63 ++- Source/Game/AI/CoreAI/CombatAI.cs | 31 +- Source/Game/AI/CoreAI/GameObjectAI.cs | 4 +- Source/Game/AI/CoreAI/UnitAI.cs | 70 +++ Source/Game/AI/SmartScripts/SmartAI.cs | 225 +++++--- Source/Game/AI/SmartScripts/SmartAIManager.cs | 182 ++++++- Source/Game/AI/SmartScripts/SmartScript.cs | 509 +++++++++++++----- Source/Game/Entities/Creature/Creature.cs | 8 - Source/Game/Entities/GameObject/GameObject.cs | 7 +- Source/Game/Entities/Object/WorldObject.cs | 8 + Source/Game/Entities/Unit/Unit.Combat.cs | 2 +- Source/Game/Handlers/SpellHandler.cs | 2 +- Source/Game/Spells/Spell.cs | 3 + Source/Game/Spells/SpellEffects.cs | 2 +- Source/Scripts/World/GameObjects.cs | 4 +- 15 files changed, 863 insertions(+), 257 deletions(-) diff --git a/Source/Framework/Constants/SmartAIConst.cs b/Source/Framework/Constants/SmartAIConst.cs index a74696c4f..b1c42836b 100644 --- a/Source/Framework/Constants/SmartAIConst.cs +++ b/Source/Framework/Constants/SmartAIConst.cs @@ -50,17 +50,42 @@ namespace Framework.Constants public enum SmartPhase { - ALWAYS = 0, + Always = 0, Phase1 = 1, Phase2 = 2, Phase3 = 3, Phase4 = 4, Phase5 = 5, Phase6 = 6, - Max = 7, - All = (Phase1 | Phase2 | Phase3 | Phase4 | Phase5 | Phase6), + Phase7 = 7, + Phase8 = 8, + Phase9 = 9, + Phase10 = 10, + Phase11 = 11, + Phase12 = 12, + Max = 13, - Count = 6 + Count = 12 + } + + public enum PhaseBits + { + PhaseAlwaysBit = 0, + Phase1Bit = 1, + Phase2Bit = 2, + Phase3Bit = 4, + Phase4Bit = 8, + Phase5Bit = 16, + Phase6Bit = 32, + Phase7Bit = 64, + Phase8Bit = 128, + Phase9Bit = 256, + Phase10Bit = 512, + Phase11Bit = 1024, + Phase12Bit = 2048, + All = Phase1Bit + Phase2Bit + Phase3Bit + Phase4Bit + Phase5Bit + + Phase6Bit + Phase7Bit + Phase8Bit + Phase9Bit + Phase10Bit + + Phase11Bit + Phase12Bit } public enum SmartEventFlags @@ -114,7 +139,7 @@ namespace Framework.Constants { UpdateIc = 0, // Initialmin, Initialmax, Repeatmin, Repeatmax UpdateOoc = 1, // Initialmin, Initialmax, Repeatmin, Repeatmax - HealtPct = 2, // Hpmin%, Hpmax%, Repeatmin, Repeatmax + HealthPct = 2, // Hpmin%, Hpmax%, Repeatmin, Repeatmax ManaPct = 3, // Manamin%, Manamax%, Repeatmin, Repeatmax Aggro = 4, // None Kill = 5, // Cooldownmin0, Cooldownmax1, Playeronly2, Else Creature Entry3 @@ -176,7 +201,7 @@ namespace Framework.Constants Link = 61, // Internal Usage, No Params, Used To Link Together Multiple Events, Does Not Use Any Extra Resources To Iterate Event Lists Needlessly GossipSelect = 62, // Menuid, Actionid JustCreated = 63, // None - GossipHello = 64, // None + GossipHello = 64, // noReportUse (for GOs) FollowCompleted = 65, // None DummyEffect = 66, // Spellid, Effectindex IsBehindTarget = 67, // Cooldownmin, Cooldownmax @@ -232,7 +257,7 @@ namespace Framework.Constants FleeForAssist = 25, // With Emote CallGroupeventhappens = 26, // Questid CombatStop = 27, // - Removeaurasfromspell = 28, // Spellid, 0 Removes All Auras + RemoveAurasFromSpell = 28, // Spellid, 0 Removes All Auras Follow = 29, // Distance (0 = Default), Angle (0 = Default), Endcreatureentry, Credit, Credittype (0monsterkill, 1event) RandomPhase = 30, // Phaseid1, Phaseid2, Phaseid3... RandomPhaseRange = 31, // Phasemin, Phasemax @@ -264,7 +289,7 @@ namespace Framework.Constants RemoveItem = 57, // Itemid, Count InstallAiTemplate = 58, // Aitemplateid SetRun = 59, // 0/1 - SetFly = 60, // 0/1 + SetDisableGravity = 60, // 0/1 SetSwim = 61, // 0/1 Teleport = 62, // Mapid, SetCounter = 63, // id, value, reset (0/1) @@ -323,7 +348,15 @@ namespace Framework.Constants SetCorpseDelay = 116, // timer DisableEvade = 117, // 0/1 (1 = disabled, 0 = enabled) GoSetGoState = 118, - // 119 - 127 : 3.3.5 reserved + SetCanFly = 119, // 0/1 + RemoveAurasByType = 120, // type + SetSightDist = 121, // sightDistance + Flee = 122, // fleeTime + AddThreat = 123, // +threat, -threat + LoadEquipment = 124, // id + TriggerRandomTimedEvent = 125, // id min range, id max range + RemoveAllGameobjects = 126, + StopMotion = 127, // stopMoving, movementExpired PlayAnimkit = 128, ScenePlay = 129, // sceneId SceneCancel = 130, // sceneId @@ -337,10 +370,10 @@ namespace Framework.Constants None = 0, // None, Defaulting To Invoket Self = 1, // Self Cast Victim = 2, // Our Current Target (Ie: Highest Aggro) - HostileSecondAggro = 3, // Second Highest Aggro - HostileLastAggro = 4, // Dead Last On Aggro - HostileRandom = 5, // Just Any Random Target On Our Threat List - HostileRandomNotTop = 6, // Any Random Target Except Top Threat + HostileSecondAggro = 3, // Second highest aggro, maxdist, playerOnly, powerType + 1 + HostileLastAggro = 4, // Dead last on aggro, maxdist, playerOnly, powerType + 1 + HostileRandom = 5, // Just any random target on our threat list, maxdist, playerOnly, powerType + 1 + HostileRandomNotTop = 6, // Any random target except top threat, maxdist, playerOnly, powerType + 1 ActionInvoker = 7, // Unit Who Caused This Event To Occur Position = 8, // Use Xyz From Event Params CreatureRange = 9, // Creatureentry(0any), Mindist, Maxdist @@ -357,8 +390,8 @@ namespace Framework.Constants ClosestGameobject = 20, // Entry(0any), Maxdist ClosestPlayer = 21, // Maxdist ActionInvokerVehicle = 22, // Unit'S Vehicle Who Caused This Event To Occur - OwnerOrSummoner = 23, // Unit'S Owner Or Summoner - ThreatList = 24, // All Units On Creature'S Threat List + OwnerOrSummoner = 23, // Unit's owner or summoner, Use Owner/Charmer of this unit + ThreatList = 24, // All units on creature's threat list, maxdist ClosestEnemy = 25, // maxDist, playerOnly ClosestFriendly = 26, // maxDist, playerOnly LootRecipients = 27, // all players that have tagged this creature (for kill credit) diff --git a/Source/Game/AI/CoreAI/CombatAI.cs b/Source/Game/AI/CoreAI/CombatAI.cs index 0c8d14506..f982d53f8 100644 --- a/Source/Game/AI/CoreAI/CombatAI.cs +++ b/Source/Game/AI/CoreAI/CombatAI.cs @@ -316,34 +316,35 @@ namespace Game.AI void CheckConditions(uint diff) { - if (m_ConditionsTimer < diff) + if (!m_HasConditions) + return; + + if (m_ConditionsTimer <= diff) { - if (m_HasConditions) + Vehicle vehicleKit = me.GetVehicleKit(); + if (vehicleKit) { - Vehicle vehicleKit = me.GetVehicleKit(); - if (vehicleKit) + foreach (var pair in vehicleKit.Seats) { - foreach (var pair in vehicleKit.Seats) + Unit passenger = Global.ObjAccessor.GetUnit(me, pair.Value.Passenger.Guid); + if (passenger) { - Unit passenger = Global.ObjAccessor.GetUnit(me, pair.Value.Passenger.Guid); - if (passenger) + Player player = passenger.ToPlayer(); + if (player) { - Player player = passenger.ToPlayer(); - if (player) + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.CreatureTemplateVehicle, me.GetEntry(), player, me)) { - if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.CreatureTemplateVehicle, me.GetEntry(), player, me)) - { - player.ExitVehicle(); - return;//check other pessanger in next tick - } + player.ExitVehicle(); + return;//check other pessanger in next tick } } } } } + m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME; } - else + else m_ConditionsTimer -= diff; } diff --git a/Source/Game/AI/CoreAI/GameObjectAI.cs b/Source/Game/AI/CoreAI/GameObjectAI.cs index bb4b3d22c..127af31ee 100644 --- a/Source/Game/AI/CoreAI/GameObjectAI.cs +++ b/Source/Game/AI/CoreAI/GameObjectAI.cs @@ -17,6 +17,7 @@ using Framework.Dynamic; using Game.Entities; +using Game.Spells; namespace Game.AI { @@ -40,7 +41,7 @@ namespace Game.AI public virtual void SetGUID(ulong guid, int id = 0) { } public virtual ulong GetGUID(int id = 0) { return 0; } - public virtual bool GossipHello(Player player, bool isUse) { return false; } + public virtual bool GossipHello(Player player, bool reportUse) { return false; } public virtual bool GossipSelect(Player player, uint sender, uint action) { return false; } public virtual bool GossipSelectCode(Player player, uint sender, uint action, string code) { return false; } public virtual bool QuestAccept(Player player, Quest quest) { return false; } @@ -55,6 +56,7 @@ namespace Game.AI public virtual void OnGameEvent(bool start, ushort eventId) { } public virtual void OnStateChanged(uint state, Unit unit) { } public virtual void EventInform(uint eventId) { } + public virtual void SpellHit(Unit unit, SpellInfo spellInfo) { } protected TaskScheduler _scheduler; protected EventMap _events; diff --git a/Source/Game/AI/CoreAI/UnitAI.cs b/Source/Game/AI/CoreAI/UnitAI.cs index e0064e5cc..0d647fc03 100644 --- a/Source/Game/AI/CoreAI/UnitAI.cs +++ b/Source/Game/AI/CoreAI/UnitAI.cs @@ -571,4 +571,74 @@ namespace Game.AI Unit _source; bool _playerOnly; } + + // Simple selector for units using mana + class PowerUsersSelector : ISelector + { + public PowerUsersSelector(Unit unit, PowerType power, float dist, bool playerOnly) + { + _me = unit; + _power = power; + _dist = dist; + _playerOnly = playerOnly; + } + + public bool Check(Unit target) + { + if (_me == null || target == null) + return false; + + if (target.GetPowerType() != _power) + return false; + + if (_playerOnly && target.GetTypeId() != TypeId.Player) + return false; + + if (_dist > 0.0f && !_me.IsWithinCombatRange(target, _dist)) + return false; + + if (_dist < 0.0f && _me.IsWithinCombatRange(target, -_dist)) + return false; + + return true; + } + + Unit _me; + PowerType _power; + float _dist; + bool _playerOnly; + } + + class FarthestTargetSelector : ISelector + { + public FarthestTargetSelector(Unit unit, float dist, bool playerOnly, bool inLos) + { + _me = unit; + _dist = dist; + _playerOnly = playerOnly; + _inLos = inLos; + } + + public bool Check(Unit target) + { + if (_me == null || target == null) + return false; + + if (_playerOnly && target.GetTypeId() != TypeId.Player) + return false; + + if (_dist > 0.0f && !_me.IsWithinCombatRange(target, _dist)) + return false; + + if (_inLos && !_me.IsWithinLOSInMap(target)) + return false; + + return true; + } + + Unit _me; + float _dist; + bool _playerOnly; + bool _inLos; + } } diff --git a/Source/Game/AI/SmartScripts/SmartAI.cs b/Source/Game/AI/SmartScripts/SmartAI.cs index 4278e2c22..cc2e463f5 100644 --- a/Source/Game/AI/SmartScripts/SmartAI.cs +++ b/Source/Game/AI/SmartScripts/SmartAI.cs @@ -30,20 +30,11 @@ namespace Game.AI { public class SmartAI : CreatureAI { - const int SMART_ESCORT_MAX_PLAYER_DIST = 50; + const int SMART_ESCORT_MAX_PLAYER_DIST = 60; const int SMART_MAX_AID_DIST = SMART_ESCORT_MAX_PLAYER_DIST / 2; public SmartAI(Creature creature) : base(creature) { - mIsCharmed = false; - // copy script to local (protection for table reload) - - mEscortState = SmartEscortState.None; - mCurrentWPID = 0;//first wp id is 1 !! - mWPReached = false; - mWPPauseTimer = 0; - mCanRepeatPath = false; - // Spawn in run mode me.SetWalk(false); mRun = true; @@ -53,21 +44,10 @@ namespace Game.AI mCanAutoAttack = true; mCanCombatMove = true; - mForcedPaused = false; - mLastWPIDReached = 0; - mEscortQuestID = 0; - - mDespawnTime = 0; - mDespawnState = 0; - mEscortInvokerCheckTimer = 1000; mFollowGuid = ObjectGuid.Empty; - mFollowDist = 0; - mFollowAngle = 0; - mFollowCredit = 0; - mFollowArrivedEntry = 0; - mFollowCreditType = 0; - mInvincibilityHpLevel = 0; + + mHasConditions = Global.ConditionMgr.HasConditionsForNotGroupedEntry(ConditionSourceType.CreatureTemplateVehicle, creature.GetEntry()); } bool IsAIControlled() @@ -133,20 +113,28 @@ namespace Game.AI StopPath(); if (path != 0) + { if (!LoadPath(path)) return; + } if (mWayPoints.Empty()) return; - AddEscortState(SmartEscortState.Escorting); - mCanRepeatPath = repeat; - - SetRun(run); - WayPoint wp = GetNextWayPoint(); if (wp != null) { + AddEscortState(SmartEscortState.Escorting); + mCanRepeatPath = repeat; + + SetRun(run); + + if (invoker != null && invoker.GetTypeId() == TypeId.Player) + { + mEscortNPCFlags = me.m_unitData.NpcFlags[0]; + me.SetNpcFlags(NPCFlags.None); + } + mLastOOCPos = me.GetPosition(); me.GetMotionMaster().MovePoint(wp.Id, wp.X, wp.Y, wp.Z); GetScript().ProcessEventsFor(SmartEvents.WaypointStart, null, wp.Id, GetScript().GetPathId()); @@ -221,6 +209,12 @@ namespace Game.AI mWPPauseTimer = 0; mLastWP = null; + if (mEscortNPCFlags != 0) + { + me.SetNpcFlags((NPCFlags)mEscortNPCFlags); + mEscortNPCFlags = 0; + } + if (mCanRepeatPath) { if (IsAIControlled()) @@ -301,9 +295,15 @@ namespace Game.AI if (mEscortInvokerCheckTimer < diff) { + // Escort failed, no players in range if (!IsEscortInvokerInRange()) { - StopPath(mDespawnTime, mEscortQuestID, true); + StopPath(0, mEscortQuestID, true); + + // allow to properly hook out of range despawn action, which in most cases should perform the same operation as dying + GetScript().ProcessEventsFor(SmartEvents.Death, me); + me.DespawnOrUnsummon(1); + return; } mEscortInvokerCheckTimer = 1000; } @@ -347,6 +347,7 @@ namespace Game.AI if ((!me.HasReactState(ReactStates.Passive) && me.IsInCombat()) || HasEscortState(SmartEscortState.Paused | SmartEscortState.Returning)) return; + // handle next wp if (mWPReached)//reached WP { @@ -369,6 +370,7 @@ namespace Game.AI public override void UpdateAI(uint diff) { + CheckConditions(diff); GetScript().OnUpdate(diff); UpdatePath(diff); UpdateDespawn(diff); @@ -390,10 +392,11 @@ namespace Game.AI var targets = GetScript().GetTargetList(SharedConst.SmartEscortTargets); if (targets != null) { + float checkDist = me.GetInstanceScript() != null ? SMART_ESCORT_MAX_PLAYER_DIST * 2 : SMART_ESCORT_MAX_PLAYER_DIST; if (targets.Count == 1 && GetScript().IsPlayer(targets.First())) { Player player = targets.First().ToPlayer(); - if (me.GetDistance(player) <= SMART_ESCORT_MAX_PLAYER_DIST) + if (me.GetDistance(player) <= checkDist) return true; Group group = player.GetGroup(); @@ -402,7 +405,7 @@ namespace Game.AI for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next()) { Player groupGuy = groupRef.GetSource(); - if (groupGuy.IsInMap(player) && me.GetDistance(groupGuy) <= SMART_ESCORT_MAX_PLAYER_DIST) + if (groupGuy.IsInMap(player) && me.GetDistance(groupGuy) <= checkDist) return true; } } @@ -413,13 +416,18 @@ namespace Game.AI { if (GetScript().IsPlayer(obj)) { - if (me.GetDistance(obj.ToPlayer()) <= SMART_ESCORT_MAX_PLAYER_DIST) + if (me.GetDistance(obj.ToPlayer()) <= checkDist) return true; } } } + + // no valid target found + return false; } - return true;//escort targets were not set, ignore range check + + // no player invoker was stored, just ignore range check + return true; } void MovepointReached(uint id) @@ -462,6 +470,12 @@ namespace Game.AI return; } + if (!IsAIControlled()) + { + me.AttackStop(); + return; + } + if (!_EnterEvadeMode()) return; @@ -538,7 +552,7 @@ namespace Game.AI return false; //never attack friendly - if (me.IsFriendlyTo(who)) + if (!me.IsValidAssistTarget(who.GetVictim())) return false; //too far away and no free sight? @@ -634,7 +648,15 @@ namespace Game.AI public override void AttackStart(Unit who) { - if (who && me.Attack(who, me.IsWithinMeleeRange(who))) + // dont allow charmed npcs to act on their own + if (!IsAIControlled()) + { + if (who != null && mCanAutoAttack) + me.Attack(who, true); + return; + } + + if (who != null && me.Attack(who, me.IsWithinMeleeRange(who))) { if (mCanCombatMove) me.GetMotionMaster().MoveChase(who); @@ -701,9 +723,11 @@ namespace Game.AI { mScript.OnInitialize(me); if (!me.IsDead()) + { mJustReset = true; - JustReachedHome(); - GetScript().ProcessEventsFor(SmartEvents.Respawn); + JustReachedHome(); + GetScript().ProcessEventsFor(SmartEvents.Respawn); + } } public override void OnCharmed(bool apply) @@ -759,9 +783,14 @@ namespace Game.AI mRun = run; } - public void SetFly(bool fly) + public void SetDisableGravity(bool disable = true) { - me.SetDisableGravity(fly); + me.SetDisableGravity(disable); + } + + public void SetCanFly(bool fly = true) + { + me.SetCanFly(fly); } public void SetSwim(bool swim) @@ -838,11 +867,14 @@ namespace Game.AI public void SetFollow(Unit target, float dist, float angle, uint credit, uint end, uint creditType) { if (target == null) + { + StopFollow(false); return; + } mFollowGuid = target.GetGUID(); - mFollowDist = dist >= 0.0f ? dist : SharedConst.PetFollowDist; - mFollowAngle = angle >= 0.0f ? angle : me.GetFollowAngle(); + mFollowDist = dist; + mFollowAngle = angle; mFollowArrivedTimer = 1000; mFollowCredit = credit; mFollowArrivedEntry = end; @@ -851,6 +883,35 @@ namespace Game.AI me.GetMotionMaster().MoveFollow(target, mFollowDist, mFollowAngle); } + public void StopFollow(bool complete) + { + mFollowGuid.Clear(); + mFollowDist = 0; + mFollowAngle = 0; + mFollowCredit = 0; + mFollowArrivedTimer = 1000; + mFollowArrivedEntry = 0; + mFollowCreditType = 0; + me.StopMoving(); + me.GetMotionMaster().MoveIdle(); + + if (!complete) + return; + + Player player = Global.ObjAccessor.GetPlayer(me, mFollowGuid); + if (player != null) + { + if (mFollowCreditType == 0) + player.RewardPlayerAndGroupAtEvent(mFollowCredit, me); + else + player.GroupEventHappens(mFollowCredit, me); + } + + SetDespawnTime(5000); + StartDespawn(); + GetScript().ProcessEventsFor(SmartEvents.FollowCompleted); + } + public void SetScript9(SmartScriptHolder e, uint entry, Unit invoker) { if (invoker != null) @@ -871,6 +932,40 @@ namespace Game.AI GetScript().ProcessEventsFor(SmartEvents.OnSpellclick, clicker); } + void CheckConditions(uint diff) + { + if (!mHasConditions) + return; + + if (mConditionsTimer <= diff) + { + Vehicle vehicleKit = me.GetVehicleKit(); + if (vehicleKit != null) + { + foreach (var pair in vehicleKit.Seats) + { + Unit passenger = Global.ObjAccessor.GetUnit(me, pair.Value.Passenger.Guid); + if (passenger != null) + { + Player player = passenger.ToPlayer(); + if (player != null) + { + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.CreatureTemplateVehicle, me.GetEntry(), player, me)) + { + player.ExitVehicle(); + return; // check other pessanger in next tick + } + } + } + } + } + + mConditionsTimer = 1000; + } + else + mConditionsTimer -= diff; + } + public void UpdateFollow(uint diff) { if (!mFollowGuid.IsEmpty()) @@ -879,31 +974,14 @@ namespace Game.AI { if (me.FindNearestCreature(mFollowArrivedEntry, SharedConst.InteractionDistance, true) != null) { - Player player = Global.ObjAccessor.GetPlayer(me, mFollowGuid); - if (player != null) - { - if (mFollowCreditType == 0) - player.RewardPlayerAndGroupAtEvent(mFollowCredit, me); - else - player.GroupEventHappens(mFollowCredit, me); - } - mFollowGuid.Clear(); - mFollowDist = 0; - mFollowAngle = 0; - mFollowCredit = 0; - mFollowArrivedTimer = 1000; - mFollowArrivedEntry = 0; - mFollowCreditType = 0; - SetDespawnTime(5000); - me.StopMoving(); - me.GetMotionMaster().MoveIdle(); - StartDespawn(); - GetScript().ProcessEventsFor(SmartEvents.FollowCompleted); + StopFollow(true); return; } + mFollowArrivedTimer = 1000; } - else mFollowArrivedTimer -= diff; + else + mFollowArrivedTimer -= diff; } } @@ -928,6 +1006,8 @@ namespace Game.AI uint GetWPCount() { return (uint)mWayPoints?.Count; } + public void SetWPPauseTimer(uint time) { mWPPauseTimer = time; } + bool mIsCharmed; uint mFollowCreditType; uint mFollowArrivedTimer; @@ -944,6 +1024,7 @@ namespace Game.AI uint mLastWPIDReached; bool mWPReached; uint mWPPauseTimer; + uint mEscortNPCFlags; WayPoint mLastWP; Position mLastOOCPos;//set on enter combat bool mCanRepeatPath; @@ -962,6 +1043,10 @@ namespace Game.AI uint mEscortInvokerCheckTimer; bool mJustReset; + + // Vehicle conditions + bool mHasConditions; + uint mConditionsTimer; } public class SmartGameObjectAI : GameObjectAI @@ -979,18 +1064,23 @@ namespace Game.AI public override void InitializeAI() { GetScript().OnInitialize(go); - GetScript().ProcessEventsFor(SmartEvents.Respawn); + // do not call respawn event if go is not spawned + if (go.isSpawned()) + GetScript().ProcessEventsFor(SmartEvents.Respawn); } public override void Reset() { + // call respawn event on reset + GetScript().ProcessEventsFor(SmartEvents.Respawn); + GetScript().OnReset(); } - public override bool GossipHello(Player player, bool isUse) + public override bool GossipHello(Player player, bool reportUse) { Log.outDebug(LogFilter.ScriptsAi, "SmartGameObjectAI.GossipHello"); - GetScript().ProcessEventsFor(SmartEvents.GossipHello, player, 0, 0, false, null, go); + GetScript().ProcessEventsFor(SmartEvents.GossipHello, player, reportUse ? 1 : 0u, 0, false, null, go); return false; } @@ -1054,6 +1144,11 @@ namespace Game.AI GetScript().ProcessEventsFor(SmartEvents.GoEventInform, null, eventId); } + public override void SpellHit(Unit unit, SpellInfo spellInfo) + { + GetScript().ProcessEventsFor(SmartEvents.SpellHit, unit, 0, 0, false, spellInfo); + } + public SmartScript GetScript() { return mScript; } SmartScript mScript; diff --git a/Source/Game/AI/SmartScripts/SmartAIManager.cs b/Source/Game/AI/SmartScripts/SmartAIManager.cs index 70766e634..195939af8 100644 --- a/Source/Game/AI/SmartScripts/SmartAIManager.cs +++ b/Source/Game/AI/SmartScripts/SmartAIManager.cs @@ -129,7 +129,7 @@ namespace Game.AI temp.event_id = result.Read(2); temp.link = result.Read(3); temp.Event.type = (SmartEvents)result.Read(4); - temp.Event.event_phase_mask = result.Read(5); + temp.Event.event_phase_mask = result.Read(5); temp.Event.event_chance = result.Read(6); temp.Event.event_flags = (SmartEventFlags)result.Read(7); @@ -165,6 +165,47 @@ namespace Game.AI if (!IsEventValid(temp)) continue; + // specific check for timed events + switch (temp.Event.type) + { + case SmartEvents.Update: + case SmartEvents.UpdateOoc: + case SmartEvents.UpdateIc: + case SmartEvents.HealthPct: + case SmartEvents.TargetHealthPct: + case SmartEvents.ManaPct: + case SmartEvents.TargetManaPct: + case SmartEvents.Range: + case SmartEvents.FriendlyHealth: + case SmartEvents.FriendlyHealthPCT: + case SmartEvents.FriendlyMissingBuff: + case SmartEvents.HasAura: + case SmartEvents.TargetBuffed: + if (temp.Event.minMaxRepeat.repeatMin == 0 && temp.Event.minMaxRepeat.repeatMax == 0 && !temp.Event.event_flags.HasAnyFlag(SmartEventFlags.NotRepeatable) && temp.source_type != SmartScriptType.TimedActionlist) + { + temp.Event.event_flags |= SmartEventFlags.NotRepeatable; + Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Entry {temp.entryOrGuid} SourceType {temp.GetScriptType()}, Event {temp.event_id}, Missing Repeat flag."); + } + break; + case SmartEvents.VictimCasting: + case SmartEvents.IsBehindTarget: + if (temp.Event.minMaxRepeat.min == 0 && temp.Event.minMaxRepeat.max == 0 && !temp.Event.event_flags.HasAnyFlag(SmartEventFlags.NotRepeatable) && temp.source_type != SmartScriptType.TimedActionlist) + { + temp.Event.event_flags |= SmartEventFlags.NotRepeatable; + Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Entry {temp.entryOrGuid} SourceType {temp.GetScriptType()}, Event {temp.event_id}, Missing Repeat flag."); + } + break; + case SmartEvents.FriendlyIsCc: + if (temp.Event.friendlyCC.repeatMin == 0 && temp.Event.friendlyCC.repeatMax == 0 && !temp.Event.event_flags.HasAnyFlag(SmartEventFlags.NotRepeatable) && temp.source_type != SmartScriptType.TimedActionlist) + { + temp.Event.event_flags |= SmartEventFlags.NotRepeatable; + Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Entry {temp.entryOrGuid} SourceType {temp.GetScriptType()}, Event {temp.event_id}, Missing Repeat flag."); + } + break; + default: + break; + } + // creature entry / guid not found in storage, create empty event list for it and increase counters if (!mEventMap[(int)source_type].ContainsKey(temp.entryOrGuid)) ++count; @@ -331,6 +372,7 @@ namespace Game.AI case SmartTargets.ClosestFriendly: case SmartTargets.Stored: case SmartTargets.LootRecipients: + case SmartTargets.Farthest: case SmartTargets.VehicleAccessory: case SmartTargets.SpellTarget: break; @@ -360,7 +402,7 @@ namespace Game.AI Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid action type ({2}), skipped.", e.entryOrGuid, e.event_id, e.GetActionType()); return false; } - if (e.Event.event_phase_mask > (uint)SmartPhase.All) + if (e.Event.event_phase_mask > (uint)PhaseBits.All) { Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: EntryOrGuid {0} using event({1}) has invalid phase mask ({2}), skipped.", e.entryOrGuid, e.event_id, e.Event.event_phase_mask); return false; @@ -391,7 +433,7 @@ namespace Game.AI case SmartEvents.Update: case SmartEvents.UpdateIc: case SmartEvents.UpdateOoc: - case SmartEvents.HealtPct: + case SmartEvents.HealthPct: case SmartEvents.ManaPct: case SmartEvents.TargetHealthPct: case SmartEvents.TargetManaPct: @@ -768,7 +810,7 @@ namespace Game.AI if (e.Action.animKit.type > 3) { - Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses invalid AnimKit type {4}, skipped.", + Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses invalid AnimKit type {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.animKit.type); return false; } @@ -830,6 +872,12 @@ namespace Game.AI } break; } + case SmartActions.CrossCast: + { + if (!IsSpellValid(e, e.Action.crossCast.spell)) + return false; + break; + } case SmartActions.AddAura: case SmartActions.InvokerCast: if (!IsSpellValid(e, e.Action.cast.spell)) @@ -871,7 +919,7 @@ namespace Game.AI return false; } break; - case SmartActions.Removeaurasfromspell: + case SmartActions.RemoveAurasFromSpell: if (e.Action.removeAura.spell != 0 && !IsSpellValid(e, e.Action.removeAura.spell)) return false; break; @@ -965,20 +1013,6 @@ namespace Game.AI Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses non-existent Map entry {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.teleport.mapID); return false; } - break; - case SmartActions.SetCounter: - if (e.Action.setCounter.counterId == 0) - { - Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses wrong counterId {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.setCounter.counterId); - return false; - } - - if (e.Action.setCounter.value == 0) - { - Log.outError(LogFilter.Sql, "SmartAIMgr: Entry {0} SourceType {1} Event {2} Action {3} uses wrong value {4}, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.Action.setCounter.value); - return false; - } - break; case SmartActions.InstallAiTemplate: if (e.Action.installTtemplate.id >= (uint)SmartAITemplate.End) @@ -1155,6 +1189,15 @@ namespace Game.AI return false; } + break; + } + case SmartActions.RemoveAurasByType: + { + if (e.Action.auraType.type >= (uint)AuraType.Total) + { + Log.outError(LogFilter.Sql, $"Entry {e.entryOrGuid} SourceType {e.GetScriptType()} Event {e.event_id} Action {e.GetActionType()} uses invalid data type {e.Action.auraType.type} (value range 0-TOTAL_AURAS), skipped."); + return false; + } break; } case SmartActions.StartClosestWaypoint: @@ -1181,7 +1224,8 @@ namespace Game.AI case SmartActions.SetData: case SmartActions.SetVisibility: case SmartActions.WpPause: - case SmartActions.SetFly: + case SmartActions.SetDisableGravity: + case SmartActions.SetCanFly: case SmartActions.SetRun: case SmartActions.SetSwim: case SmartActions.ForceDespawn: @@ -1202,7 +1246,6 @@ namespace Game.AI case SmartActions.SetNpcFlag: case SmartActions.AddNpcFlag: case SmartActions.RemoveNpcFlag: - case SmartActions.CrossCast: case SmartActions.CallRandomTimedActionlist: case SmartActions.RandomMove: case SmartActions.SetUnitFieldBytes1: @@ -1227,6 +1270,13 @@ namespace Game.AI case SmartActions.MoveOffset: case SmartActions.SetCorpseDelay: case SmartActions.DisableEvade: + case SmartActions.SetSightDist: + case SmartActions.Flee: + case SmartActions.AddThreat: + case SmartActions.LoadEquipment: + case SmartActions.TriggerRandomTimedEvent: + case SmartActions.SetCounter: + case SmartActions.RemoveAllGameobjects: break; default: Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled action_type({0}), event_type({1}), Entry {2} SourceType {3} Event {4}, skipped.", e.GetActionType(), e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id); @@ -1452,7 +1502,7 @@ namespace Game.AI { { SmartEvents.UpdateIc, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.TimedActionlist }, { SmartEvents.UpdateOoc, SmartScriptTypeMaskId.Creature + SmartScriptTypeMaskId.Gameobject + SmartScriptTypeMaskId.Instance }, - { SmartEvents.HealtPct, SmartScriptTypeMaskId.Creature }, + { SmartEvents.HealthPct, SmartScriptTypeMaskId.Creature }, { SmartEvents.ManaPct, SmartScriptTypeMaskId.Creature }, { SmartEvents.Aggro, SmartScriptTypeMaskId.Creature }, { SmartEvents.Kill, SmartScriptTypeMaskId.Creature }, @@ -1679,6 +1729,9 @@ namespace Game.AI [FieldOffset(16)] public TimedEvent timedEvent; + [FieldOffset(16)] + public GossipHello gossipHello; + [FieldOffset(16)] public Gossip gossip; @@ -1865,6 +1918,10 @@ namespace Game.AI { public uint id; } + public struct GossipHello + { + public uint noReportUse; + } public struct Gossip { public uint sender; @@ -2083,6 +2140,9 @@ namespace Game.AI [FieldOffset(4)] public SetRun setRun; + [FieldOffset(4)] + public SetDisableGravity setDisableGravity; + [FieldOffset(4)] public SetFly setFly; @@ -2137,6 +2197,9 @@ namespace Game.AI [FieldOffset(4)] public Jump jump; + [FieldOffset(4)] + public FleeAssist fleeAssist; + [FieldOffset(4)] public Flee flee; @@ -2194,6 +2257,21 @@ namespace Game.AI [FieldOffset(4)] public DisableEvade disableEvade; + [FieldOffset(4)] + public AuraType auraType; + + [FieldOffset(4)] + public SightDistance sightDistance; + + [FieldOffset(4)] + public LoadEquipment loadEquipment; + + [FieldOffset(4)] + public RandomTimedEvent randomTimedEvent; + + [FieldOffset(4)] + public StopMotion stopMotion; + [FieldOffset(4)] public AnimKit animKit; @@ -2255,6 +2333,7 @@ namespace Game.AI public uint spell; public uint castFlags; public uint triggerFlags; + public uint targetsLimit; } public struct CrossCast { @@ -2460,6 +2539,10 @@ namespace Game.AI { public uint run; } + public struct SetDisableGravity + { + public uint disable; + } public struct SetFly { public uint fly; @@ -2557,10 +2640,14 @@ namespace Game.AI public uint speedxy; public uint speedz; } - public struct Flee + public struct FleeAssist { public uint withEmote; } + public struct Flee + { + public uint fleeTime; + } public struct RespawnTarget { public uint goRespawnTime; @@ -2650,6 +2737,29 @@ namespace Game.AI { public uint disable; } + public struct AuraType + { + public uint type; + } + public struct SightDistance + { + public uint dist; + } + public struct LoadEquipment + { + public uint id; + public uint force; + } + public struct RandomTimedEvent + { + public uint minId; + public uint maxId; + } + public struct StopMotion + { + public uint stopMovement; + public uint movementExpired; + } public struct AnimKit { public uint animKit; @@ -2689,6 +2799,12 @@ namespace Game.AI [FieldOffset(16)] public float o; + [FieldOffset(20)] + public HostilRandom hostilRandom; + + [FieldOffset(20)] + public Farthest farthest; + [FieldOffset(20)] public UnitRange unitRange; @@ -2728,6 +2844,9 @@ namespace Game.AI [FieldOffset(20)] public ClosestFriendly closestFriendly; + [FieldOffset(20)] + public Owner owner; + [FieldOffset(20)] public Vehicle vehicle; @@ -2735,7 +2854,18 @@ namespace Game.AI public Raw raw; #region Structs - + public struct HostilRandom + { + public uint maxDist; + public uint playerOnly; + public uint powerType; + } + public struct Farthest + { + public uint maxDist; + public uint playerOnly; + public uint isInLos; + } public struct UnitRange { public uint creature; @@ -2799,6 +2929,10 @@ namespace Game.AI { public uint maxDist; } + public struct Owner + { + public uint useCharmerOrOwner; + } public struct Vehicle { public uint seat; diff --git a/Source/Game/AI/SmartScripts/SmartScript.cs b/Source/Game/AI/SmartScripts/SmartScript.cs index bf48c4d68..851592001 100644 --- a/Source/Game/AI/SmartScripts/SmartScript.cs +++ b/Source/Game/AI/SmartScripts/SmartScript.cs @@ -103,8 +103,7 @@ namespace Game.AI case SmartActions.Talk: { List targets = GetTargets(e, unit); - Creature talker = me; - Player targetPlayer = null; + Creature talker = e.Target.type == 0 ? me : null; Unit talkTarget = null; if (!targets.Empty()) @@ -114,31 +113,33 @@ namespace Game.AI if (IsCreature(obj) && !obj.ToCreature().IsPet()) // Prevented sending text to pets. { if (e.Action.talk.useTalkTarget != 0) + { + talker = me; talkTarget = obj.ToCreature(); + } else talker = obj.ToCreature(); break; } else if (IsPlayer(obj)) { - targetPlayer = obj.ToPlayer(); + talker = me; + talkTarget = obj.ToPlayer(); break; } } } - if (!talker) + if (talkTarget == null) + talkTarget = GetLastInvoker(); + + if (talker == null) break; mTalkerEntry = talker.GetEntry(); mLastTextID = e.Action.talk.textGroupId; mTextTimer = e.Action.talk.duration; - if (IsPlayer(GetLastInvoker())) // used for $vars in texts and whisper target - talkTarget = GetLastInvoker(); - else if (targetPlayer != null) - talkTarget = targetPlayer; - mUseTextTimer = true; Global.CreatureTextMgr.SendChat(talker, (byte)e.Action.talk.textGroupId, talkTarget); Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_TALK: talker: {0} (Guid: {1}), textGuid: {2}", @@ -464,8 +465,17 @@ namespace Game.AI if (targets.Empty()) break; + if (e.Action.cast.targetsLimit > 0 && targets.Count > e.Action.cast.targetsLimit) + targets.RandomResize(e.Action.cast.targetsLimit); + foreach (var obj in targets) { + if (go) + { + // may be nullptr + go.CastSpell(obj.ToUnit(), e.Action.cast.spell); + } + if (!IsUnit(obj)) continue; @@ -548,6 +558,9 @@ namespace Game.AI if (targets.Empty()) break; + if (e.Action.cast.targetsLimit > 0 && targets.Count > e.Action.cast.targetsLimit) + targets.RandomResize(e.Action.cast.targetsLimit); + foreach (var obj in targets) { if (!IsUnit(obj)) @@ -748,8 +761,8 @@ namespace Game.AI if (GetBaseObject() == null) break; - IncPhase((int)e.Action.incEventPhase.inc); - DecPhase((int)e.Action.incEventPhase.dec); + IncPhase(e.Action.incEventPhase.inc); + DecPhase(e.Action.incEventPhase.dec); Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction. SMART_ACTION_INC_EVENT_PHASE: Creature {0} inc event phase by {1}, " + "decrease by {2}", GetBaseObject().GetGUID().ToString(), e.Action.incEventPhase.inc, e.Action.incEventPhase.dec); break; @@ -769,7 +782,7 @@ namespace Game.AI break; me.DoFleeToGetAssistance(); - if (e.Action.flee.withEmote != 0) + if (e.Action.fleeAssist.withEmote != 0) { var builder = new BroadcastTextBuilder(me, ChatMsg.MonsterEmote, (uint)BroadcastTextIds.FleeForAssist, me.GetGender()); Global.CreatureTextMgr.SendChatPacket(me, builder, ChatMsg.Emote); @@ -782,9 +795,11 @@ namespace Game.AI if (unit == null) break; - if (IsPlayer(unit) && GetBaseObject() != null) + // If invoker was pet or charm + Player player = unit.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (player && GetBaseObject() != null) { - unit.ToPlayer().GroupEventHappens(e.Action.quest.questId, GetBaseObject()); + player.GroupEventHappens(e.Action.quest.questId, GetBaseObject()); Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_CALL_GROUPEVENTHAPPENS: Player {0}, group credit for quest {1}", unit.GetGUID().ToString(), e.Action.quest.questId); } @@ -795,9 +810,9 @@ namespace Game.AI { foreach (var seat in vehicle.Seats) { - Player player = Global.ObjAccessor.GetPlayer(unit, seat.Value.Passenger.Guid); - if (player != null) - player.GroupEventHappens(e.Action.quest.questId, GetBaseObject()); + Player player1 = Global.ObjAccessor.GetPlayer(unit, seat.Value.Passenger.Guid); + if (player1 != null) + player1.GroupEventHappens(e.Action.quest.questId, GetBaseObject()); } } break; @@ -811,7 +826,7 @@ namespace Game.AI Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_COMBAT_STOP: {0} CombatStop", me.GetGUID().ToString()); break; } - case SmartActions.Removeaurasfromspell: + case SmartActions.RemoveAurasFromSpell: { List targets = GetTargets(e, unit); if (targets.Empty()) @@ -841,13 +856,17 @@ namespace Game.AI List targets = GetTargets(e, unit); if (targets.Empty()) + { + ((SmartAI)me.GetAI()).StopFollow(false); break; + } foreach (var obj in targets) { if (IsUnit(obj)) { - ((SmartAI)me.GetAI()).SetFollow(obj.ToUnit(), e.Action.follow.dist, e.Action.follow.angle, e.Action.follow.credit, e.Action.follow.entry, e.Action.follow.creditType); + float angle = e.Action.follow.angle > 6 ? (e.Action.follow.angle * (float)Math.PI / 180.0f) : e.Action.follow.angle; + ((SmartAI)me.GetAI()).SetFollow(obj.ToUnit(), e.Action.follow.dist + 0.1f, angle, e.Action.follow.credit, e.Action.follow.entry, e.Action.follow.creditType); Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_FOLLOW: Creature {0} following target {1}", me.GetGUID().ToString(), obj.GetGUID().ToString()); break; @@ -1026,24 +1045,39 @@ namespace Game.AI } case SmartActions.SetInCombatWithZone: { - if (me != null) + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) { - me.SetInCombatWithZone(); - Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: Creature {0}", me.GetGUID().ToString()); + if (IsCreature(obj)) + { + me.SetInCombatWithZone(); + Log.outDebug(LogFilter.ScriptsAi, $"SmartScript.ProcessAction: SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: Creature: {me.GetGUID().ToString()}, Target: {obj.GetGUID().ToString()}"); + } } + break; } case SmartActions.CallForHelp: { - if (me != null) + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) { - me.CallForHelp(e.Action.callHelp.range); - if (e.Action.callHelp.withEmote != 0) + if (IsCreature(obj)) { - var builder = new BroadcastTextBuilder(me, ChatMsg.Emote, (uint)BroadcastTextIds.CallForHelp, me.GetGender()); - Global.CreatureTextMgr.SendChatPacket(me, builder, ChatMsg.MonsterEmote); + obj.ToCreature().CallForHelp(e.Action.callHelp.range); + if (e.Action.callHelp.withEmote != 0) + { + var builder = new BroadcastTextBuilder(me, ChatMsg.Emote, (uint)BroadcastTextIds.CallForHelp, me.GetGender()); + Global.CreatureTextMgr.SendChatPacket(me, builder, ChatMsg.MonsterEmote); + } + Log.outDebug(LogFilter.ScriptsAi, $"SmartScript.ProcessAction: SMART_ACTION_CALL_FOR_HELP: Creature: {me.GetGUID().ToString()}, Target: {obj.GetGUID().ToString()}"); } - Log.outDebug(LogFilter.ScriptsAi, "SmartScript.ProcessAction: SMART_ACTION_CALL_FOR_HELP: Creature {0}", me.GetGUID().ToString()); } break; } @@ -1156,18 +1190,25 @@ namespace Game.AI } case SmartActions.SetInvincibilityHpLevel: { - if (me == null) + List targets = GetTargets(e, unit); + if (targets.Empty()) break; - SmartAI ai = ((SmartAI)me.GetAI()); + foreach (var obj in targets) + { + if (IsCreature(obj)) + { + SmartAI ai = (SmartAI)me.GetAI(); + if (ai == null) + continue; - if (ai == null) - break; + if (e.Action.invincHP.percent != 0) + ai.SetInvincibilityHpLevel((uint)obj.ToCreature().CountPctFromMaxHealth((int)e.Action.invincHP.percent)); + else + ai.SetInvincibilityHpLevel(e.Action.invincHP.minHP); + } + } - if (e.Action.invincHP.percent != 0) - ai.SetInvincibilityHpLevel((uint)me.CountPctFromMaxHealth((int)e.Action.invincHP.percent)); - else - ai.SetInvincibilityHpLevel(e.Action.invincHP.minHP); break; } case SmartActions.SetData: @@ -1214,15 +1255,25 @@ namespace Game.AI } case SmartActions.SetVisibility: { - if (me != null) - me.SetVisible(Convert.ToBoolean(e.Action.visibility.state)); + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().SetVisible(e.Action.visibility.state != 0); + break; } case SmartActions.SetActive: { - WorldObject baseObj = GetBaseObject(); - if (baseObj != null) - baseObj.setActive(e.Action.active.state != 0); + List targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + obj.setActive(e.Action.active.state != 0); + break; } case SmartActions.AttackStart: @@ -1234,15 +1285,9 @@ namespace Game.AI if (targets.Empty()) break; - foreach (var obj in targets) - { - if (IsUnit(obj)) - { - me.GetAI().AttackStart(obj.ToUnit()); - break; - } - } - + Unit target = targets.SelectRandom().ToUnit(); + if (target != null) + me.GetAI().AttackStart(target); break; } @@ -1290,20 +1335,19 @@ namespace Game.AI { foreach (var obj in targets) { - if (!IsUnit(obj)) - continue; + // allow gameobjects to summon gameobjects + //if (!IsUnit(*itr)) + // continue; Position pos = obj.GetPositionWithOffset(new Position(e.Target.x, e.Target.y, e.Target.z, e.Target.o)); - Quaternion rot = Quaternion.fromEulerAnglesZYX(pos.GetOrientation(), 0.0f, 0.0f); - summoner.SummonGameObject(e.Action.summonGO.entry, pos, rot, e.Action.summonGO.despawnTime); + summoner.SummonGameObject(e.Action.summonGO.entry, pos, Quaternion.fromEulerAnglesZYX(pos.GetOrientation(), 0.0f, 0.0f), e.Action.summonGO.despawnTime); } } if (e.GetTargetType() != SmartTargets.Position) break; - Quaternion rot1 = Quaternion.fromEulerAnglesZYX(e.Target.o, 0.0f, 0.0f); - summoner.SummonGameObject(e.Action.summonGO.entry, new Position(e.Target.x, e.Target.y, e.Target.z, e.Target.o), rot1, e.Action.summonGO.despawnTime); + summoner.SummonGameObject(e.Action.summonGO.entry, new Position(e.Target.x, e.Target.y, e.Target.z, e.Target.o), Quaternion.fromEulerAnglesZYX(e.Target.o, 0.0f, 0.0f), e.Action.summonGO.despawnTime); break; } case SmartActions.KillUnit: @@ -1379,12 +1423,20 @@ namespace Game.AI break; } - case SmartActions.SetFly: + case SmartActions.SetDisableGravity: { if (!IsSmart()) break; - ((SmartAI)me.GetAI()).SetFly(e.Action.setFly.fly != 0 ? true : false); + ((SmartAI)me.GetAI()).SetDisableGravity(e.Action.setDisableGravity.disable != 0); + break; + } + case SmartActions.SetCanFly: + { + if (!IsSmart()) + break; + + ((SmartAI)me.GetAI()).SetCanFly(e.Action.setFly.fly != 0); break; } case SmartActions.SetRun: @@ -1438,11 +1490,24 @@ namespace Game.AI if (!IsSmart()) break; - bool run = e.Action.wpStart.run != 0 ? true : false; + bool run = e.Action.wpStart.run != 0; uint entry = e.Action.wpStart.pathID; - bool repeat = e.Action.wpStart.repeat != 0 ? true : false; + bool repeat = e.Action.wpStart.repeat != 0; + + // ensure that SMART_ESCORT_TARGETS contains at least one player reference List targets = GetTargets(e, unit); - StoreTargetList(targets, SharedConst.SmartEscortTargets); + if (!targets.Empty()) + { + foreach (var obj in targets) + { + if (IsPlayer(obj)) + { + StoreTargetList(targets, SharedConst.SmartEscortTargets); + break; + } + } + } + me.SetReactState((ReactStates)e.Action.wpStart.reactState); ((SmartAI)me.GetAI()).StartPath(run, entry, repeat, unit); @@ -1477,7 +1542,7 @@ namespace Game.AI if (!IsSmart()) break; - ((SmartAI)me.GetAI()).ResumePath(); + ((SmartAI)me.GetAI()).SetWPPauseTimer(0); break; } case SmartActions.SetOrientation: @@ -1520,34 +1585,32 @@ namespace Game.AI WorldObject target = null; - if (e.GetTargetType() == SmartTargets.CreatureRange || e.GetTargetType() == SmartTargets.CreatureGuid || + /*if (e.GetTargetType() == SmartTargets.CreatureRange || e.GetTargetType() == SmartTargets.CreatureGuid || e.GetTargetType() == SmartTargets.CreatureDistance || e.GetTargetType() == SmartTargets.GameobjectRange || 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)*/ { List targets = GetTargets(e, unit); if (targets.Empty()) break; - target = targets.First(); + // we want to move to random element + target = targets.SelectRandom(); } if (!target) { - float x = e.Target.x; - float y = e.Target.y; - float z = e.Target.z; - float o = 0; + Position dest = new Position(e.Target.x, e.Target.y, e.Target.z); if (e.Action.moveToPos.transport != 0) { ITransport trans = me.GetDirectTransport(); if (trans != null) - trans.CalculatePassengerPosition(ref x, ref y, ref z, ref o); + trans.CalculatePassengerPosition(ref dest.posX, ref dest.posY, ref dest.posZ, ref dest.Orientation); } - me.GetMotionMaster().MovePoint(e.Action.moveToPos.pointId, x, y, z, e.Action.moveToPos.disablePathfinding == 0); + me.GetMotionMaster().MovePoint(e.Action.moveToPos.pointId, dest, e.Action.moveToPos.disablePathfinding == 0); } else { @@ -1555,7 +1618,7 @@ namespace Game.AI target.GetPosition(out x, out y, out z); if (e.Action.moveToPos.contactDistance > 0) target.GetContactPoint(me, out x, out y, out z, e.Action.moveToPos.contactDistance); - me.GetMotionMaster().MovePoint(e.Action.moveToPos.pointId, x, y, z, e.Action.moveToPos.disablePathfinding == 0); + me.GetMotionMaster().MovePoint(e.Action.moveToPos.pointId, x + e.Target.x, y + e.Target.y, z + e.Target.z, e.Action.moveToPos.disablePathfinding == 0); } break; } @@ -1570,10 +1633,15 @@ namespace Game.AI if (IsCreature(obj)) obj.ToCreature().Respawn(); else if (IsGameObject(obj)) - obj.ToGameObject().SetRespawnTime((int)e.Action.respawnTarget.goRespawnTime); + { + // do not modify respawndelay of already spawned gameobjects + if (obj.ToGameObject().isSpawnedByDefault()) + obj.ToGameObject().Respawn(); + else + obj.ToGameObject().SetRespawnTime((int)e.Action.respawnTarget.goRespawnTime); + } } - break; } case SmartActions.CloseGossip: @@ -1664,6 +1732,10 @@ namespace Game.AI } case SmartActions.TriggerTimedEvent: ProcessEventsFor(SmartEvents.TimedEventTriggered, null, e.Action.timeEvent.id); + + // remove this event if not repeatable + if (e.Event.event_flags.HasAnyFlag(SmartEventFlags.NotRepeatable)) + mRemIDs.Add(e.Action.timeEvent.id); break; case SmartActions.RemoveTimedEvent: mRemIDs.Add(e.Action.timeEvent.id); @@ -2306,7 +2378,7 @@ namespace Game.AI WayPoint closestWp = null; var targets = GetTargets(e, unit); - if (targets != null) + if (!targets.Empty()) { foreach (var obj in targets) { @@ -2357,7 +2429,7 @@ namespace Game.AI bool onlySelf = e.Action.randomSound.onlySelf != 0; var targets = GetTargets(e, unit); - if (targets != null) + if (!targets.Empty()) { foreach (var obj in targets) { @@ -2375,7 +2447,7 @@ namespace Game.AI case SmartActions.SetCorpseDelay: { var targets = GetTargets(e, unit); - if (targets == null) + if (targets.Empty()) break; foreach (var obj in targets) @@ -2392,6 +2464,104 @@ namespace Game.AI break; ((SmartAI)me.GetAI()).SetEvadeDisabled(e.Action.disableEvade.disable != 0); + break; + } + case SmartActions.RemoveAurasByType: // can be used to exit vehicle for example + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().RemoveAurasByType((AuraType)e.Action.auraType.type); + + break; + } + case SmartActions.SetSightDist: + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsCreature(obj)) + obj.ToCreature().m_SightDistance = e.Action.sightDistance.dist; + + break; + } + case SmartActions.Flee: + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsCreature(obj)) + obj.ToCreature().GetMotionMaster().MoveFleeing(me, e.Action.flee.fleeTime); + + break; + } + case SmartActions.AddThreat: + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + me.AddThreat(obj.ToUnit(), (float)e.Action.threatPCT.threatINC - (float)e.Action.threatPCT.threatDEC); + + break; + } + case SmartActions.LoadEquipment: + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsCreature(obj)) + obj.ToCreature().LoadEquipment((int)e.Action.loadEquipment.id, e.Action.loadEquipment.force != 0); + + break; + } + case SmartActions.TriggerRandomTimedEvent: + { + uint eventId = RandomHelper.URand(e.Action.randomTimedEvent.minId, e.Action.randomTimedEvent.maxId); + ProcessEventsFor(SmartEvents.TimedEventTriggered, null, eventId); + break; + } + + case SmartActions.RemoveAllGameobjects: + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + if (IsUnit(obj)) + obj.ToUnit().RemoveAllGameObjects(); + + break; + } + case SmartActions.StopMotion: + { + var targets = GetTargets(e, unit); + if (targets.Empty()) + break; + + foreach (var obj in targets) + { + if (IsUnit(obj)) + { + if (e.Action.stopMotion.stopMovement != 0) + obj.ToUnit().StopMoving(); + if (e.Action.stopMotion.movementExpired != 0) + obj.ToUnit().GetMotionMaster().MovementExpired(); + } + } + break; } case SmartActions.PlayAnimkit: @@ -2471,10 +2641,15 @@ namespace Game.AI void ProcessTimedAction(SmartScriptHolder e, uint min, uint max, Unit unit = null, uint var0 = 0, uint var1 = 0, bool bvar = false, SpellInfo spell = null, GameObject gob = null, string varString = "") { + // We may want to execute action rarely and because of this if condition is not fulfilled the action will be rechecked in a long time if (Global.ConditionMgr.IsObjectMeetingSmartEventConditions(e.entryOrGuid, e.event_id, e.source_type, unit, GetBaseObject())) + { ProcessAction(e, unit, var0, var1, bvar, spell, gob, varString); - RecalcTimer(e, min, max); + RecalcTimer(e, min, max); + } + else + RecalcTimer(e, Math.Min(min, 5000), Math.Min(min, 5000)); } void InstallTemplate(SmartScriptHolder e) @@ -2615,36 +2790,79 @@ namespace Game.AI case SmartTargets.HostileSecondAggro: if (me != null) { - Unit u = me.GetAI().SelectTarget(SelectAggroTarget.TopAggro, 1); - if (u != null) - l.Add(u); + if (e.Target.hostilRandom.powerType != 0) + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.TopAggro, 1, new PowerUsersSelector(me, (PowerType)(e.Target.hostilRandom.powerType - 1), (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0)); + if (u != null) + l.Add(u); + } + else + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.TopAggro, 1, (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0); + if (u != null) + l.Add(u); + } } break; case SmartTargets.HostileLastAggro: if (me != null) { - Unit u = me.GetAI().SelectTarget(SelectAggroTarget.BottomAggro, 0); - if (u != null) - l.Add(u); + if (e.Target.hostilRandom.powerType != 0) + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.BottomAggro, 1, new PowerUsersSelector(me, (PowerType)(e.Target.hostilRandom.powerType - 1), (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0)); + if (u != null) + l.Add(u); + } + else + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.BottomAggro, 1, (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0); + if (u != null) + l.Add(u); + } } break; case SmartTargets.HostileRandom: if (me != null) { - Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Random, 0); - if (u != null) - l.Add(u); + if (e.Target.hostilRandom.powerType != 0) + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Random, 1, new PowerUsersSelector(me, (PowerType)(e.Target.hostilRandom.powerType - 1), (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0)); + if (u != null) + l.Add(u); + } + else + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Random, 1, (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0); + if (u != null) + l.Add(u); + } } break; case SmartTargets.HostileRandomNotTop: if (me != null) { - Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Random, 1); + if (e.Target.hostilRandom.powerType != 0) + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Random, 1, new PowerUsersSelector(me, (PowerType)(e.Target.hostilRandom.powerType - 1), (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0)); + if (u != null) + l.Add(u); + } + else + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Random, 1, (float)e.Target.hostilRandom.maxDist, e.Target.hostilRandom.playerOnly != 0); + if (u != null) + l.Add(u); + } + } + break; + case SmartTargets.Farthest: + if (me) + { + Unit u = me.GetAI().SelectTarget(SelectAggroTarget.Farthest, 0, new FarthestTargetSelector(me, (float)e.Target.farthest.maxDist, e.Target.farthest.playerOnly != 0, e.Target.farthest.isInLos != 0)); if (u != null) l.Add(u); } break; - case SmartTargets.None: case SmartTargets.ActionInvoker: if (scriptTrigger != null) l.Add(scriptTrigger); @@ -2794,7 +3012,7 @@ namespace Game.AI if (!list.Empty()) l.AddRange(list); - return l; + break; } case SmartTargets.ClosestCreature: { @@ -2812,9 +3030,10 @@ namespace Game.AI } case SmartTargets.ClosestPlayer: { - if (me) + WorldObject obj = GetBaseObject(); + if (obj != null) { - Player target = me.SelectNearestPlayer(e.Target.playerDistance.dist); + Player target = obj.SelectNearestPlayer(e.Target.playerDistance.dist); if (target) l.Add(target); } @@ -2843,6 +3062,23 @@ namespace Game.AI if (owner != null) l.Add(owner); } + else if (go != null) + { + Unit owner = Global.ObjAccessor.GetUnit(go, go.GetOwnerGUID()); + if (owner) + l.Add(owner); + } + + // Get owner of owner + if (e.Target.owner.useCharmerOrOwner != 0 && !l.Empty()) + { + Unit owner = l.First().ToUnit(); + l.Clear(); + + Unit unitBase = Global.ObjAccessor.GetUnit(owner, owner.GetCharmerOrOwnerGUID()); + if (unitBase != null) + l.Add(unitBase); + } break; } case SmartTargets.ThreatList: @@ -2854,7 +3090,8 @@ namespace Game.AI { Unit temp = Global.ObjAccessor.GetUnit(me, i.getUnitGuid()); if (temp != null) - l.Add(temp); + if (e.Target.hostilRandom.maxDist == 0 || me.IsWithinCombatRange(temp, (float)e.Target.hostilRandom.maxDist)) + l.Add(temp); } } break; @@ -2971,7 +3208,7 @@ namespace Game.AI return; ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax); break; - case SmartEvents.HealtPct: + case SmartEvents.HealthPct: { if (me == null || !me.IsInCombat() || me.GetMaxHealth() == 0) return; @@ -3018,6 +3255,8 @@ namespace Game.AI if (me.IsInRange(me.GetVictim(), e.Event.minMaxRepeat.min, e.Event.minMaxRepeat.max)) ProcessTimedAction(e, e.Event.minMaxRepeat.repeatMin, e.Event.minMaxRepeat.repeatMax, me.GetVictim()); + else // make it predictable + RecalcTimer(e, 500, 500); break; } case SmartEvents.VictimCasting: @@ -3048,7 +3287,12 @@ namespace Game.AI Unit target = DoSelectLowestHpFriendly(e.Event.friendlyHealth.radius, e.Event.friendlyHealth.hpDeficit); if (target == null || !target.IsInCombat()) + { + // if there are at least two same npcs, they will perform the same action immediately even if this is useless... + RecalcTimer(e, 1000, 3000); return; + } + ProcessTimedAction(e, e.Event.friendlyHealth.repeatMin, e.Event.friendlyHealth.repeatMax, target); break; } @@ -3060,7 +3304,12 @@ namespace Game.AI List pList = new List(); DoFindFriendlyCC(pList, e.Event.friendlyCC.radius); if (pList.Empty()) + { + // if there are at least two same npcs, they will perform the same action immediately even if this is useless... + RecalcTimer(e, 1000, 3000); return; + } + ProcessTimedAction(e, e.Event.friendlyCC.repeatMin, e.Event.friendlyCC.repeatMax, pList.First()); break; } @@ -3072,7 +3321,7 @@ namespace Game.AI if (pList.Empty()) return; - ProcessTimedAction(e, e.Event.missingBuff.repeatMin, e.Event.missingBuff.repeatMax, pList.First()); + ProcessTimedAction(e, e.Event.missingBuff.repeatMin, e.Event.missingBuff.repeatMax, pList.SelectRandom()); break; } case SmartEvents.HasAura: @@ -3118,11 +3367,17 @@ namespace Game.AI case SmartEvents.JustSummoned: case SmartEvents.Reset: case SmartEvents.JustCreated: - case SmartEvents.GossipHello: case SmartEvents.FollowCompleted: case SmartEvents.OnSpellclick: ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; + case SmartEvents.GossipHello: + { + if (e.Event.gossipHello.noReportUse != 0 && var0 != 0) + return; + ProcessAction(e, unit, var0, var1, bvar, spell, gob); + break; + } case SmartEvents.IsBehindTarget: { if (me == null) @@ -3266,6 +3521,13 @@ namespace Game.AI break; } case SmartEvents.SummonDespawned: + { + if (e.Event.summoned.creature != 0 && e.Event.summoned.creature != var0) + return; + ProcessAction(e, unit, var0); + RecalcTimer(e, e.Event.summoned.cooldownMin, e.Event.summoned.cooldownMax); + break; + } case SmartEvents.InstancePlayerEnter: { if (e.Event.instancePlayerEnter.team != 0 && var0 != e.Event.instancePlayerEnter.team) @@ -3480,8 +3742,10 @@ namespace Game.AI break; } case SmartEvents.CounterSet: - if (GetCounterId(e.Event.counter.id) != 0 && GetCounterValue(e.Event.counter.id) == e.Event.counter.value) - ProcessTimedAction(e, e.Event.counter.cooldownMin, e.Event.counter.cooldownMax); + if (e.Event.counter.id != var0 || GetCounterValue(e.Event.counter.id) != e.Event.counter.value) + return; + + ProcessTimedAction(e, e.Event.counter.cooldownMin, e.Event.counter.cooldownMax); break; case SmartEvents.SceneStart: case SmartEvents.SceneCancel: @@ -3570,7 +3834,7 @@ namespace Game.AI // Delay flee for assist event if stunned or rooted if (e.GetActionType() == SmartActions.FleeForAssist) { - if (me && me.HasUnitState(UnitState.Root | UnitState.Stunned)) + if (me && me.HasUnitState(UnitState.Root | UnitState.LostControl)) { e.timer = 1; return; @@ -3583,7 +3847,7 @@ namespace Game.AI case SmartEvents.Update: case SmartEvents.UpdateIc: case SmartEvents.UpdateOoc: - case SmartEvents.HealtPct: + case SmartEvents.HealthPct: case SmartEvents.TargetHealthPct: case SmartEvents.ManaPct: case SmartEvents.TargetManaPct: @@ -3676,9 +3940,9 @@ namespace Game.AI if (!mRemIDs.Empty()) { foreach (var id in mRemIDs) - { RemoveStoredEvent(id); - } + + mRemIDs.Clear(); } if (mUseTextTimer && me != null) { @@ -4078,19 +4342,14 @@ namespace Game.AI if (mCounterList.ContainsKey(id)) { if (reset == 0) - value += GetCounterValue(id); - mCounterList.Remove(id); + mCounterList[id] += value; + else + mCounterList[id] = value; } + else + mCounterList.Add(id, value); - mCounterList.Add(id, value); - ProcessEventsFor(SmartEvents.CounterSet); - } - - uint GetCounterId(uint id) - { - if (mCounterList.ContainsKey(id)) - return id; - return 0; + ProcessEventsFor(SmartEvents.CounterSet, null, id); } uint GetCounterValue(uint id) @@ -4152,22 +4411,26 @@ namespace Game.AI meOrigGUID.Clear(); } - void IncPhase(int p = 1) + void IncPhase(uint p) { - if (p >= 0) - mEventPhase += (uint)p; - else - DecPhase(-p); + // protect phase from overflowing + mEventPhase = Math.Min((uint)SmartPhase.Phase12, mEventPhase + p); } - void DecPhase(int p = 1) + void DecPhase(uint p) { - if (mEventPhase > p) - mEventPhase -= (uint)p; - else + if (p >= mEventPhase) mEventPhase = 0; + else + mEventPhase -= p; + } + bool IsInPhase(uint p) + { + if (mEventPhase == 0) + return false; + + return ((1 << (int)(mEventPhase - 1)) & p) != 0; } - bool IsInPhase(uint p) { return Convert.ToBoolean((1 << (int)(mEventPhase - 1)) & p); } void SetPhase(uint p = 0) { mEventPhase = p; } void RemoveStoredEvent(uint id) diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 327ef2ed9..02fae2aa3 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -1909,14 +1909,6 @@ namespace Game.Entities return searcher.GetTarget(); } - public Player SelectNearestPlayer(float distance) - { - var checker = new NearestPlayerInObjectRangeCheck(this, distance); - var searcher = new PlayerLastSearcher(this, checker); - Cell.VisitAllObjects(this, searcher, distance); - return searcher.GetTarget(); - } - public void SendAIReaction(AiReaction reactionType) { AIReaction packet = new AIReaction(); diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index b68cc5b7c..c229fcc77 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -569,6 +569,11 @@ namespace Game.Entities SetLootState(LootState.JustDeactivated); return; } + + // Call AI Reset (required for example in SmartAI to clear one time events) + if (GetAI() != null) + GetAI().Reset(); + // respawn timer uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool(GetSpawnId()) : 0; if (poolid != 0) @@ -1305,7 +1310,7 @@ namespace Game.Entities if (Global.ScriptMgr.OnGossipHello(playerUser, this)) return; - if (GetAI().GossipHello(playerUser, true)) + if (GetAI().GossipHello(playerUser, false)) return; } diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index 6b7736677..08a9c53ed 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -1544,6 +1544,14 @@ namespace Game.Entities return searcher.GetTarget(); } + public Player SelectNearestPlayer(float distance) + { + var checker = new NearestPlayerInObjectRangeCheck(this, distance); + var searcher = new PlayerLastSearcher(this, checker); + Cell.VisitAllObjects(this, searcher, distance); + return searcher.GetTarget(); + } + public void GetGameObjectListWithEntryInGrid(List gameobjectList, uint entry = 0, float maxSearchRange = 250.0f) { var check = new AllGameObjectsWithEntryInRange(this, entry, maxSearchRange); diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index f1aab4574..6aaa60181 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -3147,7 +3147,7 @@ namespace Game.Entities return true; } - bool IsValidAssistTarget(Unit target) + public bool IsValidAssistTarget(Unit target) { return _IsValidAssistTarget(target, null); } diff --git a/Source/Game/Handlers/SpellHandler.cs b/Source/Game/Handlers/SpellHandler.cs index 2350e349f..43fa4ebea 100644 --- a/Source/Game/Handlers/SpellHandler.cs +++ b/Source/Game/Handlers/SpellHandler.cs @@ -258,7 +258,7 @@ namespace Game GameObject go = GetPlayer().GetGameObjectIfCanInteractWith(packet.Guid); if (go) { - if (go.GetAI().GossipHello(GetPlayer(), false)) + if (go.GetAI().GossipHello(GetPlayer(), true)) return; GetPlayer().UpdateCriteria(CriteriaTypes.UseGameobject, go.GetEntry()); diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index c6f9438cc..fc7cc3756 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -2232,6 +2232,9 @@ namespace Game.Spells if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex))) HandleEffects(null, null, go, effect.EffectIndex, SpellEffectHandleMode.HitTarget); + if (go.GetAI() != null) + go.GetAI().SpellHit(m_caster, m_spellInfo); + CallScriptOnHitHandlers(); CallScriptAfterHitHandlers(); } diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 4ad42b800..1ba499a52 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -1350,7 +1350,7 @@ namespace Game.Spells if (Global.ScriptMgr.OnGossipHello(player, gameObjTarget)) return; - if (gameObjTarget.GetAI().GossipHello(player, true)) + if (gameObjTarget.GetAI().GossipHello(player, false)) return; switch (gameObjTarget.GetGoType()) diff --git a/Source/Scripts/World/GameObjects.cs b/Source/Scripts/World/GameObjects.cs index 8946e564a..478bc7718 100644 --- a/Source/Scripts/World/GameObjects.cs +++ b/Source/Scripts/World/GameObjects.cs @@ -760,9 +760,9 @@ namespace Scripts.World { public go_soulwellAI(GameObject go) : base(go) { } - public override bool GossipHello(Player player, bool isUse) + public override bool GossipHello(Player player, bool reportUse) { - if (!isUse) + if (!reportUse) return true; Unit owner = go.GetOwner();