From 6c0e21b4de4fa4ced47341fbc18d5e55c317bff4 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Thu, 27 Jan 2022 12:36:56 -0500 Subject: [PATCH] Misc updates/fixes --- Source/Framework/Constants/AccountConst.cs | 4 +- Source/Framework/Constants/Language.cs | 3 +- .../Framework/Constants/Spells/SpellConst.cs | 27 +- Source/Framework/Framework.csproj | 2 +- Source/Game/AI/CoreAI/CombatAI.cs | 48 ++-- Source/Game/AI/CoreAI/CreatureAI.cs | 26 +- Source/Game/AI/CoreAI/GameObjectAI.cs | 4 +- Source/Game/AI/CoreAI/GuardAI.cs | 2 +- Source/Game/AI/CoreAI/PassiveAI.cs | 10 +- Source/Game/AI/CoreAI/PetAI.cs | 60 ++-- Source/Game/AI/CoreAI/TotemAI.cs | 4 +- Source/Game/AI/PlayerAI/PlayerAI.cs | 4 +- Source/Game/AI/ScriptedAI/ScriptedAI.cs | 24 +- Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs | 30 +- .../Game/AI/ScriptedAI/ScriptedFollowerAI.cs | 4 +- Source/Game/AI/SmartScripts/SmartAI.cs | 8 +- Source/Game/Accounts/AccountManager.cs | 2 +- Source/Game/Chat/Commands/CastCommands.cs | 30 +- .../Game/Chat/Commands/CharacterCommands.cs | 4 +- Source/Game/Chat/Commands/DebugCommands.cs | 35 +++ Source/Game/Chat/Commands/GoCommands.cs | 2 +- Source/Game/Chat/Commands/MiscCommands.cs | 2 +- Source/Game/Chat/Hyperlink.cs | 44 +++ Source/Game/Handlers/CharacterHandler.cs | 15 +- Source/Game/Handlers/MailHandler.cs | 3 + Source/Game/Handlers/MovementHandler.cs | 4 +- .../Generators/FlightPathMovementGenerator.cs | 18 +- Source/Game/Movement/MotionMaster.cs | 9 +- Source/Game/Movement/MoveSpline.cs | 8 +- Source/Game/Movement/MoveSplineInit.cs | 12 +- Source/Game/Movement/MoveSplineInitArgs.cs | 10 +- Source/Game/Server/WorldManager.cs | 17 +- Source/Game/Server/WorldSession.cs | 20 +- Source/Game/Warden/Warden.cs | 4 +- Source/Scripts/World/NpcSpecial.cs | 263 +++++++----------- Source/WorldServer/WorldServer.conf.dist | 7 +- 36 files changed, 415 insertions(+), 354 deletions(-) diff --git a/Source/Framework/Constants/AccountConst.cs b/Source/Framework/Constants/AccountConst.cs index 4963efcd0..fe20643fd 100644 --- a/Source/Framework/Constants/AccountConst.cs +++ b/Source/Framework/Constants/AccountConst.cs @@ -789,8 +789,8 @@ namespace Framework.Constants CommandLookupMapId = 875, CommandLookupItemId = 876, CommandLookupQuestId = 877, - // 878 previously used, do not reuse - CommandDebugPoolstatus = 879, + CommandDebugQuestreset = 878, + // 879 previously used, do not reuse CommandPdumpCopy = 880, CommandReloadVehicleTemplate = 881, // Custom Permissions 1000+ diff --git a/Source/Framework/Constants/Language.cs b/Source/Framework/Constants/Language.cs index bd8670209..9ace89ad7 100644 --- a/Source/Framework/Constants/Language.cs +++ b/Source/Framework/Constants/Language.cs @@ -1097,8 +1097,9 @@ namespace Framework.Constants NpcinfoUnitFieldFlags3 = 5085, NpcinfoNpcFlags = 5086, NpcinfoPhaseIds = 5087, + Scenario = 5088, - // Room For More Trinity Strings 5088-6603 + // Room For More Trinity Strings 5089-6603 // Level Requirement Notifications SayReq = 6604, diff --git a/Source/Framework/Constants/Spells/SpellConst.cs b/Source/Framework/Constants/Spells/SpellConst.cs index 6c642a793..8b5d9d2d9 100644 --- a/Source/Framework/Constants/Spells/SpellConst.cs +++ b/Source/Framework/Constants/Spells/SpellConst.cs @@ -61,26 +61,23 @@ namespace Framework.Constants //Spell targets used by SelectSpell public enum SelectTargetType { - DontCare = 0, //All target types allowed - - Self, //Only Self casting - - SingleEnemy, //Only Single Enemy - AoeEnemy, //Only AoE Enemy - AnyEnemy, //AoE or Single Enemy - - SingleFriend, //Only Single Friend - AoeFriend, //Only AoE Friend - AnyFriend //AoE or Single Friend + DontCare = 0, //All target types allowed + Self, //Only Self casting + SingleEnemy, //Only Single Enemy + AoeEnemy, //Only AoE Enemy + AnyEnemy, //AoE or Single Enemy + SingleFriend, //Only Single Friend + AoeFriend, //Only AoE Friend + AnyFriend //AoE or Single Friend } //Spell Effects used by SelectSpell public enum SelectEffect { - DontCare = 0, //All spell effects allowed - Damage, //Spell does damage - Healing, //Spell does healing - Aura //Spell applies an aura + DontCare = 0, //All spell effects allowed + Damage, //Spell does damage + Healing, //Spell does healing + Aura //Spell applies an aura } public enum SpellCastSource diff --git a/Source/Framework/Framework.csproj b/Source/Framework/Framework.csproj index 4ee1138ec..acc84561d 100644 --- a/Source/Framework/Framework.csproj +++ b/Source/Framework/Framework.csproj @@ -6,7 +6,7 @@ - + diff --git a/Source/Game/AI/CoreAI/CombatAI.cs b/Source/Game/AI/CoreAI/CombatAI.cs index 82a8eaa16..8a065c9a7 100644 --- a/Source/Game/AI/CoreAI/CombatAI.cs +++ b/Source/Game/AI/CoreAI/CombatAI.cs @@ -109,33 +109,32 @@ namespace Game.AI public class CasterAI : CombatAI { - float _attackDist; + float _attackDistance; - public CasterAI(Creature c) - : base(c) + public CasterAI(Creature creature) : base(creature) { - _attackDist = SharedConst.MeleeRange; + _attackDistance = SharedConst.MeleeRange; } public override void InitializeAI() { base.InitializeAI(); - _attackDist = 30.0f; + _attackDistance = 30.0f; foreach (var id in Spells) { AISpellInfoType info = GetAISpellInfo(id, me.GetMap().GetDifficultyID()); - if (info != null && info.condition == AICondition.Combat && _attackDist > info.maxRange) - _attackDist = info.maxRange; + if (info != null && info.condition == AICondition.Combat && _attackDistance > info.maxRange) + _attackDistance = info.maxRange; } - if (_attackDist == 30.0f) - _attackDist = SharedConst.MeleeRange; + if (_attackDistance == 30.0f) + _attackDistance = SharedConst.MeleeRange; } public override void AttackStart(Unit victim) { - AttackStartCaster(victim, _attackDist); + AttackStartCaster(victim, _attackDistance); } public override void JustEngagedWith(Unit victim) @@ -198,18 +197,19 @@ namespace Game.AI { float _minRange; - public ArcherAI(Creature c) : base(c) + public ArcherAI(Creature creature) : base(creature) { - if (me.m_spells[0] == 0) - Log.outError(LogFilter.ScriptsAi, "ArcherAI set for creature (entry = {0}) with spell1=0. AI will do nothing", me.GetEntry()); + if (creature.m_spells[0] == 0) + Log.outError(LogFilter.ScriptsAi, $"ArcherAI set for creature with spell1=0. AI will do nothing ({me.GetGUID()})"); - var spellInfo = Global.SpellMgr.GetSpellInfo(me.m_spells[0], me.GetMap().GetDifficultyID()); + var spellInfo = Global.SpellMgr.GetSpellInfo(creature.m_spells[0], creature.GetMap().GetDifficultyID()); _minRange = spellInfo != null ? spellInfo.GetMinRange(false) : 0; if (_minRange == 0) _minRange = SharedConst.MeleeRange; - me.m_CombatDistance = spellInfo != null ? spellInfo.GetMaxRange(false) : 0; - me.m_SightDistance = me.m_CombatDistance; + + creature.m_CombatDistance = spellInfo != null ? spellInfo.GetMaxRange(false) : 0; + creature.m_SightDistance = creature.m_CombatDistance; } public override void AttackStart(Unit who) @@ -248,23 +248,21 @@ namespace Game.AI { float _minRange; - public TurretAI(Creature c) - : base(c) + public TurretAI(Creature creature) : base(creature) { - if (me.m_spells[0] == 0) - Log.outError(LogFilter.Server, "TurretAI set for creature (entry = {0}) with spell1=0. AI will do nothing", me.GetEntry()); + if (creature.m_spells[0] == 0) + Log.outError(LogFilter.Server, $"TurretAI set for creature with spell1=0. AI will do nothing ({creature.GetGUID()})"); - var spellInfo = Global.SpellMgr.GetSpellInfo(me.m_spells[0], me.GetMap().GetDifficultyID()); + var spellInfo = Global.SpellMgr.GetSpellInfo(creature.m_spells[0], creature.GetMap().GetDifficultyID()); _minRange = spellInfo != null ? spellInfo.GetMinRange(false) : 0; - me.m_CombatDistance = spellInfo != null ? spellInfo.GetMaxRange(false) : 0; - me.m_SightDistance = me.m_CombatDistance; + creature.m_CombatDistance = spellInfo != null ? spellInfo.GetMaxRange(false) : 0; + creature.m_SightDistance = creature.m_CombatDistance; } public override bool CanAIAttack(Unit victim) { // todo use one function to replace it - if (!me.IsWithinCombatRange(victim, me.m_CombatDistance) - || (_minRange != 0 && me.IsWithinCombatRange(victim, _minRange))) + if (!me.IsWithinCombatRange(victim, me.m_CombatDistance) || (_minRange != 0 && me.IsWithinCombatRange(victim, _minRange))) return false; return true; } diff --git a/Source/Game/AI/CoreAI/CreatureAI.cs b/Source/Game/AI/CoreAI/CreatureAI.cs index df63c570c..c69b51e95 100644 --- a/Source/Game/AI/CoreAI/CreatureAI.cs +++ b/Source/Game/AI/CoreAI/CreatureAI.cs @@ -46,6 +46,11 @@ namespace Game.AI _moveInLOSLocked = false; } + public void Talk(uint id, WorldObject whisperTarget = null) + { + Global.CreatureTextMgr.SendChat(me, (byte)id, whisperTarget); + } + public override void OnCharmed(bool isNew) { if (isNew && !me.IsCharmed() && !me.LastCharmerGUID.IsEmpty()) @@ -56,27 +61,20 @@ namespace Game.AI if (lastCharmer != null) me.EngageWithTarget(lastCharmer); } - me.LastCharmerGUID.Clear(); - if (!me.IsInCombat()) - EnterEvadeMode(EvadeReason.NoHostiles); + me.LastCharmerGUID.Clear(); } base.OnCharmed(isNew); } - public void Talk(uint id, WorldObject whisperTarget = null) - { - Global.CreatureTextMgr.SendChat(me, (byte)id, whisperTarget); - } - public void DoZoneInCombat(Creature creature = null) { if (!creature) creature = me; Map map = creature.GetMap(); - if (!map.IsDungeon()) //use IsDungeon instead of Instanceable, in case Battlegrounds will be instantiated + if (!map.IsDungeon()) // use IsDungeon instead of Instanceable, in case Battlegrounds will be instantiated { Log.outError(LogFilter.Server, "DoZoneInCombat call for map that isn't an instance (creature entry = {0})", creature.IsTypeId(TypeId.Unit) ? creature.ToCreature().GetEntry() : 0); return; @@ -219,7 +217,7 @@ namespace Game.AI if (!_EnterEvadeMode(why)) return; - Log.outDebug(LogFilter.Unit, "Creature {0} enters evade mode.", me.GetEntry()); + Log.outDebug(LogFilter.Unit, $"CreatureAI::EnterEvadeMode: entering evade mode (why: {why}) ({me.GetGUID()})"); if (me.GetVehicle() == null) // otherwise me will be in evade mode forever { @@ -374,12 +372,10 @@ namespace Game.AI } alreadyChecked.Add(next); } - else - { - if (outOfBounds.Contains(next)) - hasOutOfBoundsNeighbor = true; - } + else if (outOfBounds.Contains(next)) + hasOutOfBoundsNeighbor = true; } + if (fill || hasOutOfBoundsNeighbor) { var pos = new Position(startPosition.GetPositionX() + front.Key * SharedConst.BoundaryVisualizeStepSize, startPosition.GetPositionY() + front.Value * SharedConst.BoundaryVisualizeStepSize, spawnZ); diff --git a/Source/Game/AI/CoreAI/GameObjectAI.cs b/Source/Game/AI/CoreAI/GameObjectAI.cs index 26f36a327..8f1a09582 100644 --- a/Source/Game/AI/CoreAI/GameObjectAI.cs +++ b/Source/Game/AI/CoreAI/GameObjectAI.cs @@ -29,9 +29,9 @@ namespace Game.AI public GameObject me; - public GameObjectAI(GameObject gameObject) + public GameObjectAI(GameObject go) { - me = gameObject; + me = go; _scheduler = new TaskScheduler(); _events = new EventMap(); } diff --git a/Source/Game/AI/CoreAI/GuardAI.cs b/Source/Game/AI/CoreAI/GuardAI.cs index 31c9a70f8..8b619e4c8 100644 --- a/Source/Game/AI/CoreAI/GuardAI.cs +++ b/Source/Game/AI/CoreAI/GuardAI.cs @@ -52,7 +52,7 @@ namespace Game.AI return; } - Log.outDebug(LogFilter.Unit, "Guard entry: {0} enters evade mode.", me.GetEntry()); + Log.outTrace(LogFilter.ScriptsAi, $"GuardAI::EnterEvadeMode: {me.GetGUID()} enters evade mode."); me.RemoveAllAuras(); me.CombatStop(true); diff --git a/Source/Game/AI/CoreAI/PassiveAI.cs b/Source/Game/AI/CoreAI/PassiveAI.cs index 113221d71..3466d7d79 100644 --- a/Source/Game/AI/CoreAI/PassiveAI.cs +++ b/Source/Game/AI/CoreAI/PassiveAI.cs @@ -23,9 +23,9 @@ namespace Game.AI { public class PassiveAI : CreatureAI { - public PassiveAI(Creature c) : base(c) + public PassiveAI(Creature creature) : base(creature) { - me.SetReactState(ReactStates.Passive); + creature.SetReactState(ReactStates.Passive); } public override void UpdateAI(uint diff) @@ -41,9 +41,9 @@ namespace Game.AI public class PossessedAI : CreatureAI { - public PossessedAI(Creature c) : base(c) + public PossessedAI(Creature creature) : base(creature) { - me.SetReactState(ReactStates.Passive); + creature.SetReactState(ReactStates.Passive); } public override void AttackStart(Unit target) @@ -99,7 +99,7 @@ namespace Game.AI { public NullCreatureAI(Creature creature) : base(creature) { - me.SetReactState(ReactStates.Passive); + creature.SetReactState(ReactStates.Passive); } public override void MoveInLineOfSight(Unit unit) { } diff --git a/Source/Game/AI/CoreAI/PetAI.cs b/Source/Game/AI/CoreAI/PetAI.cs index b7705b5cd..e3c488129 100644 --- a/Source/Game/AI/CoreAI/PetAI.cs +++ b/Source/Game/AI/CoreAI/PetAI.cs @@ -30,7 +30,7 @@ namespace Game.AI List _allySet = new(); uint _updateAlliesTimer; - public PetAI(Creature c) : base(c) + public PetAI(Creature creature) : base(creature) { UpdateAllies(); } @@ -60,7 +60,7 @@ namespace Game.AI if (NeedToStop()) { - Log.outDebug(LogFilter.Server, "Pet AI stopped attacking [{0}]", me.GetGUID().ToString()); + Log.outTrace(LogFilter.ScriptsAi, $"PetAI::UpdateAI: AI stopped attacking {me.GetGUID()}"); StopAttack(); return; } @@ -528,26 +528,28 @@ namespace Game.AI public override void ReceiveEmote(Player player, TextEmotes emoteId) { - if (!me.GetOwnerGUID().IsEmpty() && me.GetOwnerGUID() == player.GetGUID()) - switch (emoteId) - { - case TextEmotes.Cower: - if (me.IsPet() && me.ToPet().IsPetGhoul()) - me.HandleEmoteCommand(Emote.OneshotOmnicastGhoul); - break; - case TextEmotes.Angry: - if (me.IsPet() && me.ToPet().IsPetGhoul()) - me.HandleEmoteCommand(Emote.StateStun); - break; - case TextEmotes.Glare: - if (me.IsPet() && me.ToPet().IsPetGhoul()) - me.HandleEmoteCommand(Emote.StateStun); - break; - case TextEmotes.Soothe: - if (me.IsPet() && me.ToPet().IsPetGhoul()) - me.HandleEmoteCommand(Emote.OneshotOmnicastGhoul); - break; - } + if (me.GetOwnerGUID() != player.GetGUID()) + return; + + switch (emoteId) + { + case TextEmotes.Cower: + if (me.IsPet() && me.ToPet().IsPetGhoul()) + me.HandleEmoteCommand(Emote.OneshotOmnicastGhoul); + break; + case TextEmotes.Angry: + if (me.IsPet() && me.ToPet().IsPetGhoul()) + me.HandleEmoteCommand(Emote.StateStun); + break; + case TextEmotes.Glare: + if (me.IsPet() && me.ToPet().IsPetGhoul()) + me.HandleEmoteCommand(Emote.StateStun); + break; + case TextEmotes.Soothe: + if (me.IsPet() && me.ToPet().IsPetGhoul()) + me.HandleEmoteCommand(Emote.OneshotOmnicastGhoul); + break; + } } bool NeedToStop() @@ -595,17 +597,17 @@ namespace Game.AI if (player) group = player.GetGroup(); - //only pet and owner/not in group.ok + // only pet and owner/not in group.ok if (_allySet.Count == 2 && !group) return; - //owner is in group; group members filled in already (no raid . subgroupcount = whole count) + // owner is in group; group members filled in already (no raid . subgroupcount = whole count) if (group && !group.IsRaidGroup() && _allySet.Count == (group.GetMembersCount() + 2)) return; _allySet.Clear(); _allySet.Add(me.GetGUID()); - if (group) //add group + if (group) // add group { for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.Next()) { @@ -619,7 +621,7 @@ namespace Game.AI _allySet.Add(target.GetGUID()); } } - else //remove group + else // remove group _allySet.Add(owner.GetGUID()); } @@ -631,12 +633,12 @@ namespace Game.AI base.OnCharmed(isNew); } + /// + /// Quick access to set all flags to FALSE + /// void ClearCharmInfoFlags() { - // Quick access to set all flags to FALSE - CharmInfo ci = me.GetCharmInfo(); - if (ci != null) { ci.SetIsAtStay(false); diff --git a/Source/Game/AI/CoreAI/TotemAI.cs b/Source/Game/AI/CoreAI/TotemAI.cs index 21239d154..e31b767af 100644 --- a/Source/Game/AI/CoreAI/TotemAI.cs +++ b/Source/Game/AI/CoreAI/TotemAI.cs @@ -27,6 +27,7 @@ namespace Game.AI public TotemAI(Creature creature) : base(creature) { + Cypher.Assert(creature.IsTotem(), $"TotemAI: AI assigned to a no-totem creature ({creature.GetGUID()})!"); _victimGuid = ObjectGuid.Empty; } @@ -51,8 +52,7 @@ namespace Game.AI Unit victim = !_victimGuid.IsEmpty() ? Global.ObjAccessor.GetUnit(me, _victimGuid) : null; // Search victim if no, not attackable, or out of range, or friendly (possible in case duel end) - if (victim == null || !victim.IsTargetableForAttack() || !me.IsWithinDistInMap(victim, max_range) || - me.IsFriendlyTo(victim) || !me.CanSeeOrDetect(victim)) + if (victim == null || !victim.IsTargetableForAttack() || !me.IsWithinDistInMap(victim, max_range) || me.IsFriendlyTo(victim) || !me.CanSeeOrDetect(victim)) { var u_check = new NearestAttackableUnitInObjectRangeCheck(me, me.GetCharmerOrOwnerOrSelf(), max_range); var checker = new UnitLastSearcher(me, u_check); diff --git a/Source/Game/AI/PlayerAI/PlayerAI.cs b/Source/Game/AI/PlayerAI/PlayerAI.cs index 961c9cc18..6c1613386 100644 --- a/Source/Game/AI/PlayerAI/PlayerAI.cs +++ b/Source/Game/AI/PlayerAI/PlayerAI.cs @@ -1317,8 +1317,8 @@ namespace Game.AI _castCheckTimer = 0; else { - if (IsRangedAttacker()) - { // chase to zero if the target isn't in line of sight + if (IsRangedAttacker()) // chase to zero if the target isn't in line of sight + { bool inLOS = me.IsWithinLOSInMap(target); if (_chaseCloser != !inLOS) { diff --git a/Source/Game/AI/ScriptedAI/ScriptedAI.cs b/Source/Game/AI/ScriptedAI/ScriptedAI.cs index ce75fa0b0..5a3c69caf 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedAI.cs @@ -108,7 +108,7 @@ namespace Game.AI if (!CliDB.SoundKitStorage.ContainsKey(soundId)) { - Log.outError(LogFilter.Scripts, "Invalid soundId {0} used in DoPlaySoundToSet (Source: TypeId {1}, GUID {2})", soundId, source.GetTypeId(), source.GetGUID().ToString()); + Log.outError(LogFilter.ScriptsAi, $"ScriptedAI::DoPlaySoundToSet: Invalid soundId {soundId} used in DoPlaySoundToSet (Source: {source.GetGUID()})"); return; } @@ -307,9 +307,24 @@ namespace Game.AI if (mechanic != 0 && tempSpell.Mechanic != mechanic) continue; + // Continue if we don't have the mana to actually cast this spell + bool hasPower = true; + foreach (SpellPowerCost cost in tempSpell.CalcPowerCost(me, tempSpell.GetSchoolMask())) + { + if (cost.Amount > me.GetPower(cost.Power)) + { + hasPower = false; + break; + } + } + + if (!hasPower) + continue; + //Check if the spell meets our range requirements if (rangeMin != 0 && me.GetSpellMinRangeForTarget(target, tempSpell) < rangeMin) continue; + if (rangeMax != 0 && me.GetSpellMaxRangeForTarget(target, tempSpell) > rangeMax) continue; @@ -350,8 +365,7 @@ namespace Game.AI if (player != null) player.TeleportTo(unit.GetMapId(), x, y, z, o, TeleportToOptions.NotLeaveCombat); else - Log.outError(LogFilter.Scripts, "Creature {0} (Entry: {1}) Tried to teleport non-player unit (Type: {2} GUID: {3}) to X: {4} Y: {5} Z: {6} O: {7}. Aborted.", - me.GetGUID(), me.GetEntry(), unit.GetTypeId(), unit.GetGUID(), x, y, z, o); + Log.outError(LogFilter.ScriptsAi, $"ScriptedAI::DoTeleportPlayer: Creature {me.GetGUID()} Tried to teleport non-player unit ({unit.GetGUID()}) to X: {x} Y: {y} Z: {z} O: {o}. Aborted."); } public void DoTeleportAll(float x, float y, float z, float o) @@ -631,7 +645,7 @@ namespace Game.AI { if (delayToRespawn < TimeSpan.FromSeconds(2)) { - Log.outError(LogFilter.Scripts, "_DespawnAtEvade called with delay of {0} seconds, defaulting to 2.", delayToRespawn); + Log.outError(LogFilter.ScriptsAi, $"BossAI::_DespawnAtEvade: called with delay of {delayToRespawn} seconds, defaulting to 2 (me: {me.GetGUID()})"); delayToRespawn = TimeSpan.FromSeconds(2); } @@ -641,7 +655,7 @@ namespace Game.AI TempSummon whoSummon = who.ToTempSummon(); if (whoSummon) { - Log.outWarn(LogFilter.ScriptsAi, "_DespawnAtEvade called on a temporary summon."); + Log.outWarn(LogFilter.ScriptsAi, $"BossAI::_DespawnAtEvade: called on a temporary summon (who: {who.GetGUID()})"); whoSummon.UnSummon(); return; } diff --git a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs index 438267232..b2db047d3 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs @@ -143,7 +143,7 @@ namespace Game.AI { AddEscortState(EscortState.Returning); ReturnToLastPoint(); - Log.outDebug(LogFilter.Scripts, "EscortAI.EnterEvadeMode has left combat and is now returning to last point"); + Log.outDebug(LogFilter.ScriptsAi, $"EscortAI.EnterEvadeMode has left combat and is now returning to last point {me.GetGUID()}"); } else { @@ -195,7 +195,7 @@ namespace Game.AI if (_despawnAtEnd) { - Log.outDebug(LogFilter.Scripts, "EscortAI.UpdateAI: reached end of waypoints, despawning at end"); + Log.outDebug(LogFilter.ScriptsAi, $"EscortAI::UpdateAI: reached end of waypoints, despawning at end ({me.GetGUID()})"); if (_returnToStart) { Position respawnPosition = new(); @@ -203,7 +203,7 @@ namespace Game.AI me.GetRespawnPosition(out respawnPosition.posX, out respawnPosition.posY, out respawnPosition.posZ, out orientation); respawnPosition.SetOrientation(orientation); me.GetMotionMaster().MovePoint(EscortPointIds.Home, respawnPosition); - Log.outDebug(LogFilter.Scripts, $"EscortAI.UpdateAI: returning to spawn location: {respawnPosition}"); + Log.outDebug(LogFilter.ScriptsAi, $"EscortAI::UpdateAI: returning to spawn location: {respawnPosition} ({me.GetGUID()})"); } else if (_instantRespawn) me.Respawn(); @@ -211,7 +211,7 @@ namespace Game.AI me.DespawnOrUnsummon(); } - Log.outDebug(LogFilter.Scripts, "EscortAI.UpdateAI: reached end of waypoints"); + Log.outDebug(LogFilter.ScriptsAi, $"EscortAI::UpdateAI: reached end of waypoints ({me.GetGUID()})"); RemoveEscortState(EscortState.Escorting); return; } @@ -242,7 +242,7 @@ namespace Game.AI { if (!IsPlayerOrGroupInRange()) { - Log.outDebug(LogFilter.Scripts, "EscortAI failed because player/group was to far away or not found"); + Log.outDebug(LogFilter.ScriptsAi, $"EscortAI::UpdateAI: failed because player/group was to far away or not found ({me.GetGUID()})"); bool isEscort = false; CreatureData creatureData = me.GetCreatureData(); @@ -293,7 +293,7 @@ namespace Game.AI if (Id == EscortPointIds.LastPoint) { - Log.outDebug(LogFilter.Scripts, "EscortAI.MovementInform has returned to original position before combat"); + Log.outDebug(LogFilter.ScriptsAi, $"EscortAI::MovementInform has returned to original position before combat ({me.GetGUID()})"); me.SetWalk(!_running); RemoveEscortState(EscortState.Returning); @@ -301,16 +301,16 @@ namespace Game.AI } else if (Id == EscortPointIds.Home) { - Log.outDebug(LogFilter.Scripts, "EscortAI.MovementInform: returned to home location and restarting waypoint path"); + Log.outDebug(LogFilter.ScriptsAi, $"EscortAI::MovementInform: returned to home location and restarting waypoint path ({me.GetGUID()})"); _started = false; } } else if (moveType == MovementGeneratorType.Waypoint) { - Cypher.Assert(Id < _path.nodes.Count, $"EscortAI.MovementInform: referenced movement id ({Id}) points to non-existing node in loaded path"); + Cypher.Assert(Id < _path.nodes.Count, $"EscortAI::MovementInform: referenced movement id ({Id}) points to non-existing node in loaded path ({me.GetGUID()})"); WaypointNode waypoint = _path.nodes[(int)Id]; - Log.outDebug(LogFilter.Scripts, $"EscortAI.MovementInform: waypoint node {waypoint.id} reached"); + Log.outDebug(LogFilter.ScriptsAi, $"EscortAI::MovementInform: waypoint node {waypoint.id} reached ({me.GetGUID()})"); // last point if (Id == _path.nodes.Count - 1) @@ -387,15 +387,15 @@ namespace Game.AI } } - if (me.GetVictim()) + if (me.IsEngaged()) { - Log.outError(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) attempts to Start while in combat"); + Log.outError(LogFilter.ScriptsAi, $"EscortAI::Start: (script: {me.GetScriptName()} attempts to Start while in combat ({me.GetGUID()})"); return; } if (HasEscortState(EscortState.Escorting)) { - Log.outError(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) attempts to Start while already escorting"); + Log.outError(LogFilter.ScriptsAi, $"EscortAI::Start: (script: {me.GetScriptName()} attempts to Start while already escorting ({me.GetGUID()})"); return; } @@ -406,7 +406,7 @@ namespace Game.AI if (_path.nodes.Empty()) { - Log.outError(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: {(quest != null ? quest.Id : 0)})."); + Log.outError(LogFilter.ScriptsAi, $"EscortAI::Start: (script: {me.GetScriptName()} starts with 0 waypoints (possible missing entry in script_waypoint. Quest: {(quest != null ? quest.Id : 0)} ({me.GetGUID()})"); return; } @@ -418,7 +418,7 @@ namespace Game.AI _returnToStart = canLoopPath; if (_returnToStart && _instantRespawn) - Log.outError(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn."); + Log.outError(LogFilter.ScriptsAi, $"EscortAI::Start: (script: {me.GetScriptName()} is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn ({me.GetGUID()})"); me.GetMotionMaster().MoveIdle(); me.GetMotionMaster().Clear(MovementGeneratorPriority.Normal); @@ -432,7 +432,7 @@ namespace Game.AI me.SetImmuneToNPC(false); } - Log.outDebug(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) started with {_path.nodes.Count} waypoints. ActiveAttacker = {_activeAttacker}, Run = {_running}, Player = {_playerGUID}"); + Log.outDebug(LogFilter.ScriptsAi, $"EscortAI::Start: (script: {me.GetScriptName()}, started with {_path.nodes.Count} waypoints. ActiveAttacker = {_activeAttacker}, Run = {_running}, Player = {_playerGUID} ({me.GetGUID()})"); // set initial speed me.SetWalk(!_running); diff --git a/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs b/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs index 30212edbc..cf7e0ec19 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs @@ -109,7 +109,7 @@ namespace Game.AI { if (HasFollowState(FollowState.Complete) && !HasFollowState(FollowState.PostEvent)) { - Log.outDebug(LogFilter.Scripts, "FollowerAI is set completed, despawns."); + Log.outDebug(LogFilter.ScriptsAi, $"FollowerAI::UpdateAI: is set completed, despawns. ({me.GetGUID()})"); me.DespawnOrUnsummon(); return; } @@ -156,7 +156,7 @@ namespace Game.AI if (maxRangeExceeded || questAbandoned) { - Log.outDebug(LogFilter.Scripts, $"FollowerAI::UpdateAI: failed because player/group was to far away or not found ({me.GetGUID()})"); + Log.outDebug(LogFilter.ScriptsAi, $"FollowerAI::UpdateAI: failed because player/group was to far away or not found ({me.GetGUID()})"); me.DespawnOrUnsummon(); return; } diff --git a/Source/Game/AI/SmartScripts/SmartAI.cs b/Source/Game/AI/SmartScripts/SmartAI.cs index d70a1c956..7fab892b8 100644 --- a/Source/Game/AI/SmartScripts/SmartAI.cs +++ b/Source/Game/AI/SmartScripts/SmartAI.cs @@ -1079,9 +1079,9 @@ namespace Game.AI GetScript().OnReset(); } - public bool HasEscortState(SmartEscortState uiEscortState) { return (_escortState & uiEscortState) != 0; } - public void AddEscortState(SmartEscortState uiEscortState) { _escortState |= uiEscortState; } - public void RemoveEscortState(SmartEscortState uiEscortState) { _escortState &= ~uiEscortState; } + public bool HasEscortState(SmartEscortState escortState) { return (_escortState & escortState) != 0; } + public void AddEscortState(SmartEscortState escortState) { _escortState |= escortState; } + public void RemoveEscortState(SmartEscortState escortState) { _escortState &= ~escortState; } public void SetAutoAttack(bool on) { _canAutoAttack = on; } public bool CanCombatMove() { return _canCombatMove; } @@ -1110,7 +1110,7 @@ namespace Game.AI // Gossip bool _gossipReturn; - public SmartGameObjectAI(GameObject g) : base(g) { } + public SmartGameObjectAI(GameObject go) : base(go) { } public override void UpdateAI(uint diff) { diff --git a/Source/Game/Accounts/AccountManager.cs b/Source/Game/Accounts/AccountManager.cs index 7b60cfb79..76a3c21aa 100644 --- a/Source/Game/Accounts/AccountManager.cs +++ b/Source/Game/Accounts/AccountManager.cs @@ -98,7 +98,7 @@ namespace Game if (p) { WorldSession s = p.GetSession(); - s.KickPlayer(); // mark session to remove at next session list update + s.KickPlayer("AccountMgr::DeleteAccount Deleting the account"); // mark session to remove at next session list update s.LogoutPlayer(false); // logout player without waiting next session list update } diff --git a/Source/Game/Chat/Commands/CastCommands.cs b/Source/Game/Chat/Commands/CastCommands.cs index e0854f395..e6b0741ab 100644 --- a/Source/Game/Chat/Commands/CastCommands.cs +++ b/Source/Game/Chat/Commands/CastCommands.cs @@ -27,11 +27,8 @@ namespace Game.Chat class CastCommands { [Command("", RBACPermissions.CommandCast)] - static bool HandleCastCommand(CommandHandler handler, StringArguments args) + static bool HandleCastCommand(CommandHandler handler, SpellInfo spell, string triggeredStr) { - if (args.Empty()) - return false; - Unit target = handler.GetSelectedUnit(); if (!target) { @@ -39,22 +36,19 @@ namespace Game.Chat return false; } - // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form - uint spellId = handler.ExtractSpellIdFromLink(args); - if (spellId == 0) + if (!CheckSpellExistsAndIsValid(handler, spell)) return false; - if (!CheckSpellExistsAndIsValid(handler, spellId)) - return false; - - string triggeredStr = args.NextString(); - if (!string.IsNullOrEmpty(triggeredStr)) + TriggerCastFlags triggerFlags = TriggerCastFlags.None; + if (!triggeredStr.IsEmpty()) { - if (triggeredStr != "triggered") + if ("triggered".Contains(triggeredStr)) // check if "triggered" starts with *triggeredStr (e.g. "trig", "trigger", etc.) + triggerFlags = TriggerCastFlags.FullDebugMask; + else return false; } - handler.GetSession().GetPlayer().CastSpell(target, spellId, !triggeredStr.IsEmpty()); + handler.GetSession().GetPlayer().CastSpell(target, spell.Id, new CastSpellExtraArgs(triggerFlags)); return true; } @@ -228,7 +222,11 @@ namespace Game.Chat static bool CheckSpellExistsAndIsValid(CommandHandler handler, uint spellId) { - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId, Difficulty.None); + return CheckSpellExistsAndIsValid(handler, Global.SpellMgr.GetSpellInfo(spellId, Difficulty.None)); + } + + static bool CheckSpellExistsAndIsValid(CommandHandler handler, SpellInfo spellInfo) + { if (spellInfo == null) { handler.SendSysMessage(CypherStrings.CommandNospellfound); @@ -237,7 +235,7 @@ namespace Game.Chat if (!Global.SpellMgr.IsSpellValid(spellInfo, handler.GetPlayer())) { - handler.SendSysMessage(CypherStrings.CommandSpellBroken, spellId); + handler.SendSysMessage(CypherStrings.CommandSpellBroken, spellInfo.Id); return false; } return true; diff --git a/Source/Game/Chat/Commands/CharacterCommands.cs b/Source/Game/Chat/Commands/CharacterCommands.cs index 6316560af..f170f7a63 100644 --- a/Source/Game/Chat/Commands/CharacterCommands.cs +++ b/Source/Game/Chat/Commands/CharacterCommands.cs @@ -144,7 +144,7 @@ namespace Game.Chat target.SetName(newName); session = target.GetSession(); if (session != null) - session.KickPlayer(); + session.KickPlayer("HandleCharacterRenameCommand GM Command renaming character"); } else { @@ -463,7 +463,7 @@ namespace Game.Chat { characterGuid = player.GetGUID(); accountId = player.GetSession().GetAccountId(); - player.GetSession().KickPlayer(); + player.GetSession().KickPlayer("HandleCharacterEraseCommand GM Command deleting character"); } else { diff --git a/Source/Game/Chat/Commands/DebugCommands.cs b/Source/Game/Chat/Commands/DebugCommands.cs index 212342bed..d42dd2e94 100644 --- a/Source/Game/Chat/Commands/DebugCommands.cs +++ b/Source/Game/Chat/Commands/DebugCommands.cs @@ -1331,6 +1331,41 @@ namespace Game.Chat } } + [CommandGroup("pvp", RBACPermissions.CommandDebug)] + class PvpCommands + { + [Command("warmode", RBACPermissions.CommandDebug)] + static bool HandleDebugWarModeFactionBalanceCommand(CommandHandler handler, string command, int rewardValue = 0) + { + // USAGE: .debug pvp fb [pct] + // neutral Sets faction balance off. + // alliance Set faction balance to alliance. + // horde Set faction balance to horde. + // off Reset the faction balance and use the calculated value of it + switch (command.ToLower()) + { + default: // workaround for Variant of only ExactSequences not being supported + handler.SendSysMessage(CypherStrings.BadValue); + return false; + case "alliance": + Global.WorldMgr.SetForcedWarModeFactionBalanceState(TeamId.Alliance, rewardValue); + break; + case "horde": + Global.WorldMgr.SetForcedWarModeFactionBalanceState(TeamId.Horde, rewardValue); + break; + case "neutral": + Global.WorldMgr.SetForcedWarModeFactionBalanceState(TeamId.Neutral); + break; + case "off": + Global.WorldMgr.DisableForcedWarModeFactionBalanceState(); + break; + } + + return true; + } + } + + [CommandGroup("send", RBACPermissions.CommandDebugSend)] class SendCommands { diff --git a/Source/Game/Chat/Commands/GoCommands.cs b/Source/Game/Chat/Commands/GoCommands.cs index 2a1a6a00c..2fc0804b7 100644 --- a/Source/Game/Chat/Commands/GoCommands.cs +++ b/Source/Game/Chat/Commands/GoCommands.cs @@ -42,7 +42,7 @@ namespace Game.Chat.Commands return DoTeleport(handler, new Position(at.Pos.X, at.Pos.Y, at.Pos.Z), at.ContinentID); } - [Command("areatrigger", RBACPermissions.CommandGo)] + [Command("boss", RBACPermissions.CommandGo)] static bool HandleGoBossCommand(CommandHandler handler, string[] needles) { if (needles.Empty()) diff --git a/Source/Game/Chat/Commands/MiscCommands.cs b/Source/Game/Chat/Commands/MiscCommands.cs index 52a0e120e..f36b1eb35 100644 --- a/Source/Game/Chat/Commands/MiscCommands.cs +++ b/Source/Game/Chat/Commands/MiscCommands.cs @@ -1115,7 +1115,7 @@ namespace Game.Chat else handler.SendSysMessage(CypherStrings.CommandKickmessage, playerName); - target.GetSession().KickPlayer(); + target.GetSession().KickPlayer("HandleKickPlayerCommand GM Command"); return true; } diff --git a/Source/Game/Chat/Hyperlink.cs b/Source/Game/Chat/Hyperlink.cs index 102521b5b..760d8e0d9 100644 --- a/Source/Game/Chat/Hyperlink.cs +++ b/Source/Game/Chat/Hyperlink.cs @@ -114,6 +114,50 @@ namespace Game.Chat return false; } + public static bool CheckAllLinks(string str) + { + // Step 1: Disallow all control sequences except ||, |H, |h, |c and |r + { + int pos = 0; + while ((pos = str.IndexOf('|', pos)) != -1) + { + char next = str[pos + 1]; + if (next == 'H' || next == 'h' || next == 'c' || next == 'r' || next == '|') + pos += 2; + else + return false; + } + } + + // Step 2: Parse all link sequences + // They look like this: |c|H:|h[]|h|r + // - is 8 hex characters AARRGGBB + // - is arbitrary length [a-z_] + // - is arbitrary length, no | contained + // - is printable + { + int pos = 0; + while ((pos = str.IndexOf('|', pos)) != -1) + { + if (str[pos + 1] == '|') // this is an escaped pipe character (||) + { + pos += 2; + continue; + } + + HyperlinkInfo info = ParseHyperlink(str.Substring(pos)); + if (info == null)// todo fix me || !ValidateLinkInfo(info)) + return false; + + // tag is fine, find the next one + pos = str.Length - info.next.Length; + } + } + + // all tags are valid + return true; + } + static byte toHex(char c) { return (byte)((c >= '0' && c <= '9') ? c - '0' + 0x10 : (c >= 'a' && c <= 'f') ? c - 'a' + 0x1a : 0x00); } //|color|Henchant:recipe_spell_id|h[prof_name: recipe_name]|h|r diff --git a/Source/Game/Handlers/CharacterHandler.cs b/Source/Game/Handlers/CharacterHandler.cs index 299e33aea..2cd4193c1 100644 --- a/Source/Game/Handlers/CharacterHandler.cs +++ b/Source/Game/Handlers/CharacterHandler.cs @@ -686,6 +686,7 @@ namespace Game if (PlayerLoading() || GetPlayer() != null) { Log.outError(LogFilter.Network, "Player tries to login again, AccountId = {0}", GetAccountId()); + KickPlayer("WorldSession::HandlePlayerLoginOpcode Another client logging in"); return; } @@ -695,7 +696,7 @@ namespace Game if (!_legitCharacters.Contains(playerLogin.Guid)) { Log.outError(LogFilter.Network, "Account ({0}) can't login with that character ({1}).", GetAccountId(), playerLogin.Guid.ToString()); - KickPlayer(); + KickPlayer("WorldSession::HandlePlayerLoginOpcode Trying to login with a character of another account"); return; } @@ -706,7 +707,7 @@ namespace Game { if (!PlayerLoading() || GetPlayer()) { - KickPlayer(); + KickPlayer("WorldSession::HandleContinuePlayerLogin incorrect player state when logging in"); return; } @@ -726,7 +727,7 @@ namespace Game if (!pCurrChar.LoadFromDB(playerGuid, holder)) { SetPlayer(null); - KickPlayer(); + KickPlayer("WorldSession::HandlePlayerLogin Player::LoadFromDB failed"); m_playerLoading.Clear(); return; } @@ -1061,7 +1062,7 @@ namespace Game { if (!PlayerLoading() || GetPlayer()) { - KickPlayer(); + KickPlayer("WorldSession::AbortLogin incorrect player state when logging in"); return; } @@ -1220,7 +1221,7 @@ namespace Game { Log.outError(LogFilter.Network, "Account {0}, IP: {1} tried to rename character {2}, but it does not belong to their account!", GetAccountId(), GetRemoteAddress(), request.RenameInfo.Guid.ToString()); - KickPlayer(); + KickPlayer("WorldSession::HandleCharRenameOpcode rename character from a different account"); return; } @@ -1402,7 +1403,7 @@ namespace Game { Log.outError(LogFilter.Network, "Account {0}, IP: {1} tried to customise {2}, but it does not belong to their account!", GetAccountId(), GetRemoteAddress(), packet.CustomizeInfo.CharGUID.ToString()); - KickPlayer(); + KickPlayer("WorldSession::HandleCharCustomize Trying to customise character of another account"); return; } @@ -1673,7 +1674,7 @@ namespace Game { Log.outError(LogFilter.Network, "Account {0}, IP: {1} tried to factionchange character {2}, but it does not belong to their account!", GetAccountId(), GetRemoteAddress(), packet.RaceOrFactionChangeInfo.Guid.ToString()); - KickPlayer(); + KickPlayer("WorldSession::HandleCharFactionOrRaceChange Trying to change faction of character of another account"); return; } diff --git a/Source/Game/Handlers/MailHandler.cs b/Source/Game/Handlers/MailHandler.cs index e7e4bef85..87fc95324 100644 --- a/Source/Game/Handlers/MailHandler.cs +++ b/Source/Game/Handlers/MailHandler.cs @@ -73,6 +73,9 @@ namespace Game if (string.IsNullOrEmpty(packet.Info.Target)) return; + if (!ValidateHyperlinksAndMaybeKick(packet.Info.Subject) || !ValidateHyperlinksAndMaybeKick(packet.Info.Body)) + return; + Player player = GetPlayer(); if (player.GetLevel() < WorldConfig.GetIntValue(WorldCfg.MailLevelReq)) { diff --git a/Source/Game/Handlers/MovementHandler.cs b/Source/Game/Handlers/MovementHandler.cs index 2d660d6ff..38c9ccd12 100644 --- a/Source/Game/Handlers/MovementHandler.cs +++ b/Source/Game/Handlers/MovementHandler.cs @@ -541,7 +541,7 @@ namespace Game { Log.outDebug(LogFilter.Server, "Player {0} from account id {1} kicked for incorrect speed (must be {2} instead {3})", GetPlayer().GetName(), GetPlayer().GetSession().GetAccountId(), GetPlayer().GetSpeed(move_type), packet.Speed); - GetPlayer().GetSession().KickPlayer(); + GetPlayer().GetSession().KickPlayer("WorldSession::HandleForceSpeedChangeAck Incorrect speed"); } } } @@ -675,7 +675,7 @@ namespace Game if (Math.Abs(expectedModMagnitude - setModMovementForceMagnitudeAck.Speed) > 0.01f) { Log.outDebug(LogFilter.Misc, $"Player {_player.GetName()} from account id {_player.GetSession().GetAccountId()} kicked for incorrect movement force magnitude (must be {expectedModMagnitude} instead {setModMovementForceMagnitudeAck.Speed})"); - _player.GetSession().KickPlayer(); + _player.GetSession().KickPlayer("WorldSession::HandleMoveSetModMovementForceMagnitudeAck Incorrect magnitude"); return; } } diff --git a/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs b/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs index 14d13e6b6..151084e63 100644 --- a/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs +++ b/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs @@ -52,14 +52,24 @@ namespace Game.Movement owner.CombatStopWithPets(); owner.AddUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); - MoveSplineInit init = new(owner); uint end = GetPathAtMapEnd(); - init.args.path = new Vector3[end]; - for (int i = (int)GetCurrentNode(); i != end; ++i) + uint currentNodeId = GetCurrentNode(); + + if (currentNodeId == end) + { + Log.outDebug(LogFilter.Movement, $"FlightPathMovementGenerator::DoReset: trying to start a flypath from the end point. {owner.GetDebugInfo()}"); + return; + } + + MoveSplineInit init = new(owner); + // Providing a starting vertex since the taxi paths do not provide such + init.Path().Add(new Vector3(owner.GetPositionX(), owner.GetPositionY(), owner.GetPositionZ())); + for (int i = (int)currentNodeId; i != (uint)end; ++i) { Vector3 vertice = new(_path[i].Loc.X, _path[i].Loc.Y, _path[i].Loc.Z); - init.args.path[i] = vertice; + init.Path().Add(vertice); } + init.SetFirstPointId((int)GetCurrentNode()); init.SetFly(); init.SetSmooth(); diff --git a/Source/Game/Movement/MotionMaster.cs b/Source/Game/Movement/MotionMaster.cs index c29db25c8..e0d6f8c78 100644 --- a/Source/Game/Movement/MotionMaster.cs +++ b/Source/Game/Movement/MotionMaster.cs @@ -798,11 +798,10 @@ namespace Game.Movement MoveSplineInit init = new(_owner); - init.args.path = new Vector3[stepCount + 1]; - // add the owner's current position as starting point as it gets removed after entering the cycle - init.args.path[0] = new Vector3(_owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ()); - for (byte i = 1; i < stepCount; angle += step, ++i) + init.Path().Add(new Vector3(_owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ())); + + for (byte i = 0; i < stepCount; angle += step, ++i) { Vector3 point = new(); point.X = (float)(x + radius * Math.Cos(angle)); @@ -813,7 +812,7 @@ namespace Game.Movement else point.Z = _owner.GetMapHeight(point.X, point.Y, z) + _owner.GetHoverOffset(); - init.args.path[i] = point; + init.Path().Add(point); } if (_owner.IsFlying()) diff --git a/Source/Game/Movement/MoveSpline.cs b/Source/Game/Movement/MoveSpline.cs index a8e3496b6..d24fe7704 100644 --- a/Source/Game/Movement/MoveSpline.cs +++ b/Source/Game/Movement/MoveSpline.cs @@ -52,7 +52,7 @@ namespace Game.Movement effect_start_time = 0; spell_effect_extra = args.spellEffectExtra; anim_tier = args.animTier; - splineIsFacingOnly = args.path.Length == 2 && args.facing.type != MonsterMoveType.Normal && ((args.path[1] - args.path[0]).Length() < 0.1f); + splineIsFacingOnly = args.path.Count == 2 && args.facing.type != MonsterMoveType.Normal && ((args.path[1] - args.path[0]).Length() < 0.1f); // Check if its a stop spline if (args.flags.HasFlag(SplineFlag.Done)) @@ -91,11 +91,11 @@ namespace Game.Movement int cyclic_point = 0; if (splineflags.HasFlag(SplineFlag.EnterCycle)) cyclic_point = 1; // shouldn't be modified, came from client - spline.InitCyclicSpline(args.path, args.path.Length, modes[Convert.ToInt32(args.flags.IsSmooth())], cyclic_point, args.initialOrientation); + spline.InitCyclicSpline(args.path.ToArray(), args.path.Count, modes[Convert.ToInt32(args.flags.IsSmooth())], cyclic_point, args.initialOrientation); } else { - spline.InitSpline(args.path, args.path.Length, modes[Convert.ToInt32(args.flags.IsSmooth())], args.initialOrientation); + spline.InitSpline(args.path.ToArray(), args.path.Count, modes[Convert.ToInt32(args.flags.IsSmooth())], args.initialOrientation); } // init spline timestamps @@ -298,7 +298,7 @@ namespace Game.Movement splineflags.SetUnsetFlag(SplineFlag.EnterCycle, false); MoveSplineInitArgs args = new(spline.GetPointCount()); - args.path = spline.GetPoints().AsSpan().Slice(spline.First() + 1, spline.Last()).ToArray(); + args.path.AddRange(spline.GetPoints().AsSpan().Slice(spline.First() + 1, spline.Last()).ToArray()); args.facing = facing; args.flags = splineflags; args.path_Idx_offset = point_Idx_offset; diff --git a/Source/Game/Movement/MoveSplineInit.cs b/Source/Game/Movement/MoveSplineInit.cs index 24acd6b21..32748592a 100644 --- a/Source/Game/Movement/MoveSplineInit.cs +++ b/Source/Game/Movement/MoveSplineInit.cs @@ -19,6 +19,7 @@ using Framework.Constants; using Game.Entities; using Game.Networking.Packets; using System; +using System.Collections.Generic; using System.Numerics; namespace Game.Movement @@ -94,7 +95,7 @@ namespace Game.Movement } // should i do the things that user should do? - no. - if (args.path.Length == 0) + if (args.path.Count == 0) return 0; // correct first vertex @@ -246,9 +247,9 @@ namespace Game.Movement } args.path_Idx_offset = 0; - args.path = new Vector3[2]; + args.path.Add(default); TransportPathTransform transform = new(unit, args.TransformForTransport); - args.path[1] = transform.Calc(dest); + args.path.Add(transform.Calc(dest)); } public void SetFall() @@ -282,10 +283,9 @@ namespace Game.Movement public void MovebyPath(Vector3[] controls, int path_offset = 0) { args.path_Idx_offset = path_offset; - args.path = new Vector3[controls.Length]; TransportPathTransform transform = new(unit, args.TransformForTransport); for (var i = 0; i < controls.Length; i++) - args.path[i] = transform.Calc(controls[i]); + args.path.Add(transform.Calc(controls[i])); } @@ -333,7 +333,7 @@ namespace Game.Movement args.spellEffectExtra.Set(spellEffectExtraData); } - public Vector3[] Path() { return args.path; } + public List Path() { return args.path; } public MoveSplineInitArgs args = new(); Unit unit; diff --git a/Source/Game/Movement/MoveSplineInitArgs.cs b/Source/Game/Movement/MoveSplineInitArgs.cs index fadaa2a67..5e956f265 100644 --- a/Source/Game/Movement/MoveSplineInitArgs.cs +++ b/Source/Game/Movement/MoveSplineInitArgs.cs @@ -17,6 +17,7 @@ using Framework.Dynamic; using Game.Entities; +using System.Collections.Generic; using System.Numerics; namespace Game.Movement @@ -33,10 +34,9 @@ namespace Game.Movement initialOrientation = 0.0f; HasVelocity = false; TransformForTransport = true; - path = new Vector3[path_capacity]; } - public Vector3[] path; + public List path = new(); public FacingInfo facing = new(); public MoveSplineFlag flags = new(); public int path_Idx_offset; @@ -65,7 +65,7 @@ namespace Game.Movement return true; } - if (!CHECK(path.Length > 1)) + if (!CHECK(path.Count > 1)) return false; if (!CHECK(velocity >= 0.01f)) return false; @@ -78,8 +78,8 @@ namespace Game.Movement bool _checkPathLengths() { - if (path.Length > 2 || facing.type == Framework.Constants.MonsterMoveType.Normal) - for (uint i = 0; i < path.Length - 1; ++i) + if (path.Count > 2 || facing.type == Framework.Constants.MonsterMoveType.Normal) + for (int i = 0; i < path.Count - 1; ++i) if ((path[i + 1] - path[i]).Length() < 0.1f) return false; return true; diff --git a/Source/Game/Server/WorldManager.cs b/Source/Game/Server/WorldManager.cs index d6d34ae39..9c080e1e8 100644 --- a/Source/Game/Server/WorldManager.cs +++ b/Source/Game/Server/WorldManager.cs @@ -182,7 +182,7 @@ namespace Game if (session.PlayerLoading()) return false; - session.KickPlayer(); + session.KickPlayer("World::RemoveSession"); } return true; @@ -208,7 +208,7 @@ namespace Game // if player is in loading and want to load again, return if (!RemoveSession(s.GetAccountId())) { - s.KickPlayer(); + s.KickPlayer("World::AddSession_ Couldn't remove the other session while on loading screen"); return; } @@ -1243,18 +1243,17 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, @"VMap data directory is: {0}\vmaps", GetDataPath()); } - void SetForcedWarModeFactionBalanceState(int team, int reward) + public void SetForcedWarModeFactionBalanceState(int team, int reward = 0) { _warModeDominantFaction = team; _warModeOutnumberedFactionReward = reward; } - void DisableForcedWarModeFactionBalanceState() + public void DisableForcedWarModeFactionBalanceState() { UpdateWarModeRewardValues(); } - public void LoadAutobroadcasts() { uint oldMSTime = Time.GetMSTime(); @@ -1597,7 +1596,7 @@ namespace Game // session not removed at kick and will removed in next update tick foreach (var session in m_sessions.Values) - session.KickPlayer(); + session.KickPlayer("World::KickAll"); } void KickAllLess(AccountTypes sec) @@ -1605,7 +1604,7 @@ namespace Game // session not removed at kick and will removed in next update tick foreach (var session in m_sessions.Values) if (session.GetSecurity() < sec) - session.KickPlayer(); + session.KickPlayer("World::KickAllLess"); } /// Ban an account or ban an IP address, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban @@ -1689,7 +1688,7 @@ namespace Game if (sess) { if (sess.GetPlayerName() != author) - sess.KickPlayer(); + sess.KickPlayer("World::BanAccount Banning account"); } } while (resultAccounts.NextRow()); @@ -1762,7 +1761,7 @@ namespace Game DB.Characters.CommitTransaction(trans); if (pBanned) - pBanned.GetSession().KickPlayer(); + pBanned.GetSession().KickPlayer("World::BanCharacter Banning character"); return BanReturn.Success; } diff --git a/Source/Game/Server/WorldSession.cs b/Source/Game/Server/WorldSession.cs index 98ff0bde6..72754b1ba 100644 --- a/Source/Game/Server/WorldSession.cs +++ b/Source/Game/Server/WorldSession.cs @@ -34,6 +34,7 @@ using System.IO; using System.Numerics; using System.Text; using System.Threading.Tasks; +using Game.Chat; namespace Game { @@ -419,8 +420,10 @@ namespace Game public void AddInstanceConnection(WorldSocket sock) { m_Socket[(int)ConnectionType.Instance] = sock; } - public void KickPlayer() + public void KickPlayer(string reason) { + Log.outInfo(LogFilter.Network, $"Account: {GetAccountId()} Character: '{(_player ? _player.GetName() : "")}' {(_player ? _player.GetGUID() : "")} kicked with reason: {reason}"); + for (byte i = 0; i < 2; ++i) { if (m_Socket[i] != null) @@ -588,6 +591,19 @@ namespace Game return m_muteTime <= GameTime.GetGameTime(); } + bool ValidateHyperlinksAndMaybeKick(string str) + { + if (Hyperlink.CheckAllLinks(str)) + return true; + + Log.outError(LogFilter.Network, $"Player {GetPlayer().GetName()} {GetPlayer().GetGUID()} sent a message with an invalid link:\n{str}"); + + if (WorldConfig.GetIntValue(WorldCfg.ChatStrictLinkCheckingKick) != 0) + KickPlayer("WorldSession::ValidateHyperlinksAndMaybeKick Invalid chat link"); + + return false; + } + public bool DisallowHyperlinksAndMaybeKick(string str) { if (!str.Contains('|')) @@ -596,7 +612,7 @@ namespace Game Log.outError(LogFilter.Network, $"Player {GetPlayer().GetName()} ({GetPlayer().GetGUID()}) sent a message which illegally contained a hyperlink:\n{str}"); if (WorldConfig.GetIntValue(WorldCfg.ChatStrictLinkCheckingKick) != 0) - KickPlayer(); + KickPlayer("WorldSession::DisallowHyperlinksAndMaybeKick Illegal chat link"); return false; } diff --git a/Source/Game/Warden/Warden.cs b/Source/Game/Warden/Warden.cs index 6cb7a3dfa..fa4fdfe1e 100644 --- a/Source/Game/Warden/Warden.cs +++ b/Source/Game/Warden/Warden.cs @@ -101,7 +101,7 @@ namespace Game { Log.outWarn(LogFilter.Warden, "{0} (latency: {1}, IP: {2}) exceeded Warden module response delay for more than {3} - disconnecting client", _session.GetPlayerInfo(), _session.GetLatency(), _session.GetRemoteAddress(), Time.secsToTimeString(maxClientResponseDelay, TimeFormat.ShortText)); - _session.KickPlayer(); + _session.KickPlayer("Warden::Update Warden module response delay exceeded"); } else _clientResponseTimer += diff; @@ -172,7 +172,7 @@ namespace Game case WardenActions.Log: return "None"; case WardenActions.Kick: - _session.KickPlayer(); + _session.KickPlayer("Warden::Penalty"); return "Kick"; case WardenActions.Ban: { diff --git a/Source/Scripts/World/NpcSpecial.cs b/Source/Scripts/World/NpcSpecial.cs index 960031721..07dec2065 100644 --- a/Source/Scripts/World/NpcSpecial.cs +++ b/Source/Scripts/World/NpcSpecial.cs @@ -33,21 +33,21 @@ namespace Scripts.World.NpcSpecial { enum SpawnType { - TripwireRooftop, // no warning, summon Creature at smaller range + Tripwire, // no warning, summon Creature at smaller range AlarmBot, // cast guards mark and summon npc - if player shows up with that buff duration < 5 seconds attack } - class SpawnAssociation + class AirForceSpawn { - public SpawnAssociation(uint _thisCreatureEntry, uint _spawnedCreatureEntry, SpawnType _spawnType) + public AirForceSpawn(uint _myEntry, uint _otherEntry, SpawnType _spawnType) { - thisCreatureEntry = _thisCreatureEntry; - spawnedCreatureEntry = _spawnedCreatureEntry; + myEntry = _myEntry; + otherEntry = _otherEntry; spawnType = _spawnType; } - public uint thisCreatureEntry; - public uint spawnedCreatureEntry; + public uint myEntry; + public uint otherEntry; public SpawnType spawnType; } @@ -364,39 +364,39 @@ namespace Scripts.World.NpcSpecial struct Misc { - public static SpawnAssociation[] spawnAssociations = + public static AirForceSpawn[] AirforceSpawns = { - new SpawnAssociation(2614, 15241, SpawnType.AlarmBot), //Air Force Alarm Bot (Alliance) - new SpawnAssociation(2615, 15242, SpawnType.AlarmBot), //Air Force Alarm Bot (Horde) - new SpawnAssociation(21974, 21976, SpawnType.AlarmBot), //Air Force Alarm Bot (Area 52) - new SpawnAssociation(21993, 15242, SpawnType.AlarmBot), //Air Force Guard Post (Horde - Bat Rider) - new SpawnAssociation(21996, 15241, SpawnType.AlarmBot), //Air Force Guard Post (Alliance - Gryphon) - new SpawnAssociation(21997, 21976, SpawnType.AlarmBot), //Air Force Guard Post (Goblin - Area 52 - Zeppelin) - new SpawnAssociation(21999, 15241, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Alliance) - new SpawnAssociation(22001, 15242, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Horde) - new SpawnAssociation(22002, 15242, SpawnType.TripwireRooftop), //Air Force Trip Wire - Ground (Horde) - new SpawnAssociation(22003, 15241, SpawnType.TripwireRooftop), //Air Force Trip Wire - Ground (Alliance) - new SpawnAssociation(22063, 21976, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Goblin - Area 52) - new SpawnAssociation(22065, 22064, SpawnType.AlarmBot), //Air Force Guard Post (Ethereal - Stormspire) - new SpawnAssociation(22066, 22067, SpawnType.AlarmBot), //Air Force Guard Post (Scryer - Dragonhawk) - new SpawnAssociation(22068, 22064, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Ethereal - Stormspire) - new SpawnAssociation(22069, 22064, SpawnType.AlarmBot), //Air Force Alarm Bot (Stormspire) - new SpawnAssociation(22070, 22067, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Scryer) - new SpawnAssociation(22071, 22067, SpawnType.AlarmBot), //Air Force Alarm Bot (Scryer) - new SpawnAssociation(22078, 22077, SpawnType.AlarmBot), //Air Force Alarm Bot (Aldor) - new SpawnAssociation(22079, 22077, SpawnType.AlarmBot), //Air Force Guard Post (Aldor - Gryphon) - new SpawnAssociation(22080, 22077, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Aldor) - new SpawnAssociation(22086, 22085, SpawnType.AlarmBot), //Air Force Alarm Bot (Sporeggar) - new SpawnAssociation(22087, 22085, SpawnType.AlarmBot), //Air Force Guard Post (Sporeggar - Spore Bat) - new SpawnAssociation(22088, 22085, SpawnType.TripwireRooftop), //Air Force Trip Wire - Rooftop (Sporeggar) - new SpawnAssociation(22090, 22089, SpawnType.AlarmBot), //Air Force Guard Post (Toshley's Station - Flying Machine) - new SpawnAssociation(22124, 22122, SpawnType.AlarmBot), //Air Force Alarm Bot (Cenarion) - new SpawnAssociation(22125, 22122, SpawnType.AlarmBot), //Air Force Guard Post (Cenarion - Stormcrow) - new SpawnAssociation(22126, 22122, SpawnType.AlarmBot) //Air Force Trip Wire - Rooftop (Cenarion Expedition) + new AirForceSpawn(2614, 15241, SpawnType.AlarmBot), //Air Force Alarm Bot (Alliance) + new AirForceSpawn(2615, 15242, SpawnType.AlarmBot), //Air Force Alarm Bot (Horde) + new AirForceSpawn(21974, 21976, SpawnType.AlarmBot), //Air Force Alarm Bot (Area 52) + new AirForceSpawn(21993, 15242, SpawnType.AlarmBot), //Air Force Guard Post (Horde - Bat Rider) + new AirForceSpawn(21996, 15241, SpawnType.AlarmBot), //Air Force Guard Post (Alliance - Gryphon) + new AirForceSpawn(21997, 21976, SpawnType.AlarmBot), //Air Force Guard Post (Goblin - Area 52 - Zeppelin) + new AirForceSpawn(21999, 15241, SpawnType.Tripwire), //Air Force Trip Wire - Rooftop (Alliance) + new AirForceSpawn(22001, 15242, SpawnType.Tripwire), //Air Force Trip Wire - Rooftop (Horde) + new AirForceSpawn(22002, 15242, SpawnType.Tripwire), //Air Force Trip Wire - Ground (Horde) + new AirForceSpawn(22003, 15241, SpawnType.Tripwire), //Air Force Trip Wire - Ground (Alliance) + new AirForceSpawn(22063, 21976, SpawnType.Tripwire), //Air Force Trip Wire - Rooftop (Goblin - Area 52) + new AirForceSpawn(22065, 22064, SpawnType.AlarmBot), //Air Force Guard Post (Ethereal - Stormspire) + new AirForceSpawn(22066, 22067, SpawnType.AlarmBot), //Air Force Guard Post (Scryer - Dragonhawk) + new AirForceSpawn(22068, 22064, SpawnType.Tripwire), //Air Force Trip Wire - Rooftop (Ethereal - Stormspire) + new AirForceSpawn(22069, 22064, SpawnType.AlarmBot), //Air Force Alarm Bot (Stormspire) + new AirForceSpawn(22070, 22067, SpawnType.Tripwire), //Air Force Trip Wire - Rooftop (Scryer) + new AirForceSpawn(22071, 22067, SpawnType.AlarmBot), //Air Force Alarm Bot (Scryer) + new AirForceSpawn(22078, 22077, SpawnType.AlarmBot), //Air Force Alarm Bot (Aldor) + new AirForceSpawn(22079, 22077, SpawnType.AlarmBot), //Air Force Guard Post (Aldor - Gryphon) + new AirForceSpawn(22080, 22077, SpawnType.Tripwire), //Air Force Trip Wire - Rooftop (Aldor) + new AirForceSpawn(22086, 22085, SpawnType.AlarmBot), //Air Force Alarm Bot (Sporeggar) + new AirForceSpawn(22087, 22085, SpawnType.AlarmBot), //Air Force Guard Post (Sporeggar - Spore Bat) + new AirForceSpawn(22088, 22085, SpawnType.Tripwire), //Air Force Trip Wire - Rooftop (Sporeggar) + new AirForceSpawn(22090, 22089, SpawnType.AlarmBot), //Air Force Guard Post (Toshley's Station - Flying Machine) + new AirForceSpawn(22124, 22122, SpawnType.AlarmBot), //Air Force Alarm Bot (Cenarion) + new AirForceSpawn(22125, 22122, SpawnType.AlarmBot), //Air Force Guard Post (Cenarion - Stormcrow) + new AirForceSpawn(22126, 22122, SpawnType.AlarmBot) //Air Force Trip Wire - Rooftop (Cenarion Expedition) }; public const float RangeTripwire = 15.0f; - public const float RangeGuardsMark = 100.0f; + public const float RangeAlarmbot = 100.0f; //ChickenCluck public const uint FactionFriendly = 35; @@ -467,153 +467,100 @@ namespace Scripts.World.NpcSpecial } [Script] - class npc_air_force_bots : ScriptedAI + class npc_air_force_bots : NullCreatureAI { - SpawnAssociation SpawnAssoc; - ObjectGuid SpawnedGUID; - List inLineOfSightSinceLastUpdate = new(); + AirForceSpawn _spawn; + ObjectGuid _myGuard; + List _toAttack = new(); - public npc_air_force_bots(Creature creature) : base(creature) + static AirForceSpawn FindSpawnFor(uint entry) { - SpawnAssoc = null; - SpawnedGUID.Clear(); - - // find the correct spawnhandling - foreach (var association in Misc.spawnAssociations) + foreach (AirForceSpawn spawn in Misc.AirforceSpawns) { - if (association.thisCreatureEntry == creature.GetEntry()) + if (spawn.myEntry == entry) { - SpawnAssoc = association; - break; + Cypher.Assert(Global.ObjectMgr.GetCreatureTemplate(spawn.otherEntry) != null, $"Invalid creature entry {spawn.otherEntry} in 'npc_air_force_bots' script"); + return spawn; } } - - if (SpawnAssoc == null) - Log.outError(LogFilter.Sql, "TCSR: Creature template entry {0} has ScriptName npc_air_force_bots, but it's not handled by that script", creature.GetEntry()); - else - { - CreatureTemplate spawnedTemplate = Global.ObjectMgr.GetCreatureTemplate(SpawnAssoc.spawnedCreatureEntry); - if (spawnedTemplate == null) - { - Log.outError(LogFilter.Sql, "TCSR: Creature template entry {0} does not exist in DB, which is required by npc_air_force_bots", SpawnAssoc.spawnedCreatureEntry); - SpawnAssoc = null; - return; - } - } - } - - public override void Reset() { } - - Creature SummonGuard() - { - Creature summoned = me.SummonCreature(SpawnAssoc.spawnedCreatureEntry, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOutOfCombat, 300000); - - if (summoned) - SpawnedGUID = summoned.GetGUID(); - else - { - Log.outError(LogFilter.Sql, "npc_air_force_bots: wasn't able to spawn Creature {0}", SpawnAssoc.spawnedCreatureEntry); - SpawnAssoc = null; - } - - return summoned; - } - - Creature GetSummonedGuard() - { - Creature creature = ObjectAccessor.GetCreature(me, SpawnedGUID); - if (creature && creature.IsAlive()) - return creature; + Cypher.Assert(false, $"Unhandled creature with entry {entry} is assigned 'npc_air_force_bots' script"); return null; } + public npc_air_force_bots(Creature creature) : base(creature) + { + _spawn = FindSpawnFor(creature.GetEntry()); + } + + Creature GetOrSummonGuard() + { + Creature guard = ObjectAccessor.GetCreature(me, _myGuard); + + if (guard == null && (guard = me.SummonCreature(_spawn.otherEntry, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOutOfCombat, 300000))) + _myGuard = guard.GetGUID(); + + return guard; + } + public override void UpdateAI(uint diff) { - base.UpdateAI(diff); + if (_toAttack.Empty()) + return; - inLineOfSightSinceLastUpdate.Clear(); + Creature guard = GetOrSummonGuard(); + if (guard == null) + return; + + // Keep the list of targets for later on when the guards will be alive + if (!guard.IsAlive()) + return; + + for (var i = 0; i < _toAttack.Count; ++i) + { + ObjectGuid guid = _toAttack[i]; + + Unit target = Global.ObjAccessor.GetUnit(me, guid); + if (!target) + continue; + + if (guard.IsEngagedBy(target)) + continue; + + guard.EngageWithTarget(target); + if (_spawn.spawnType == SpawnType.AlarmBot) + guard.CastSpell(target, SpellIds.GuardsMark, true); + } + + _toAttack.Clear(); } public override void MoveInLineOfSight(Unit who) { - if (SpawnAssoc == null) + // guards are only spawned against players + if (!who.IsPlayer()) return; - if (me.IsValidAttackTarget(who)) - { - Player playerTarget = who.ToPlayer(); + // we're already scheduled to attack this player on our next tick, don't bother checking + if (_toAttack.Contains(who.GetGUID())) + return; - // airforce guards only spawn for players - if (!playerTarget) - return; + // check if they're in range + if (!who.IsWithinDistInMap(me, (_spawn.spawnType == SpawnType.AlarmBot) ? Misc.RangeAlarmbot : Misc.RangeTripwire)) + return; - Creature lastSpawnedGuard = SpawnedGUID.IsEmpty() ? null : GetSummonedGuard(); + // check if they're hostile + if (!(me.IsHostileTo(who) || who.IsHostileTo(me))) + return; - // prevent calling Unit::GetUnit at next MoveInLineOfSight call - speedup - if (!lastSpawnedGuard) - SpawnedGUID.Clear(); + // check if they're a valid attack target + if (!me.IsValidAttackTarget(who)) + return; - switch (SpawnAssoc.spawnType) - { - case SpawnType.AlarmBot: - { - // handle only 1 change for world update for each target - if (inLineOfSightSinceLastUpdate.Contains(who.GetGUID())) - return; + if ((_spawn.spawnType == SpawnType.Tripwire) && who.IsFlying()) + return; - inLineOfSightSinceLastUpdate.Add(who.GetGUID()); - - if (!who.IsWithinDistInMap(me, Misc.RangeGuardsMark)) - return; - - Aura markAura = who.GetAura(SpellIds.GuardsMark); - if (markAura != null) - { - // the target wasn't able to move out of our range within 25 seconds - if (!lastSpawnedGuard) - { - lastSpawnedGuard = SummonGuard(); - - if (!lastSpawnedGuard) - return; - } - - if (markAura.GetDuration() < Misc.AuraDurationTimeLeft) - if (!lastSpawnedGuard.GetVictim()) - lastSpawnedGuard.GetAI().AttackStart(who); - } - else - { - if (!lastSpawnedGuard) - lastSpawnedGuard = SummonGuard(); - - if (!lastSpawnedGuard) - return; - - lastSpawnedGuard.CastSpell(who, SpellIds.GuardsMark, true); - } - break; - } - case SpawnType.TripwireRooftop: - { - if (!who.IsWithinDistInMap(me, Misc.RangeTripwire)) - return; - - if (!lastSpawnedGuard) - lastSpawnedGuard = SummonGuard(); - - if (!lastSpawnedGuard) - return; - - // ROOFTOP only triggers if the player is on the ground - if (!playerTarget.IsFlying() && !lastSpawnedGuard.GetVictim()) - lastSpawnedGuard.GetAI().AttackStart(who); - - break; - } - } - } + _toAttack.Add(who.GetGUID()); } } diff --git a/Source/WorldServer/WorldServer.conf.dist b/Source/WorldServer/WorldServer.conf.dist index 5d832ec42..6dbacdc67 100644 --- a/Source/WorldServer/WorldServer.conf.dist +++ b/Source/WorldServer/WorldServer.conf.dist @@ -1831,9 +1831,10 @@ Respawn.DynamicMinimumGameObject = 10 # CHAT SETTINGS # # ChatFakeMessagePreventing -# Description: Chat protection from creating fake messages using a lot spaces or other -# invisible symbols. Not applied to the addon language, but may break old -# addons that use normal languages for sending data to other clients. +# Description: Additional protection from creating fake chat messages using spaces. +# Collapses multiple subsequent whitespaces into a single whitespace. +# Not applied to the addon language, but may break old addons that use +# "normal" chat messages for sending data to other clients. # Default: 1 - (Enabled, Blizzlike) # 0 - (Disabled)