diff --git a/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs b/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs index 85662a887..c89dd6e98 100644 --- a/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs +++ b/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs @@ -398,17 +398,14 @@ namespace Server.Factions else*/ if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"My move is blocked, so I am going to attack {Mobile.FocusMob.Name}"); - } + this.DebugSayFormatted($"My move is blocked, so I am going to attack {Mobile.FocusMob.Name}"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; } - else if (Mobile.Debug) + else { - Mobile.DebugSay("I am stuck"); + DebugSay("I am stuck"); } } diff --git a/Projects/UOContent/Mobiles/AI/AnimalAI.cs b/Projects/UOContent/Mobiles/AI/AnimalAI.cs index 481b22747..9e33414c7 100644 --- a/Projects/UOContent/Mobiles/AI/AnimalAI.cs +++ b/Projects/UOContent/Mobiles/AI/AnimalAI.cs @@ -23,19 +23,13 @@ public class AnimalAI : BaseAI if (!Mobile.Summoned && !Mobile.Controlled && hitPercent < 0.1 && Mobile.CanFlee) // Less than 10% health { - if (Mobile.Debug) - { - Mobile.DebugSay("I am low on health!"); - } + DebugSay("I am low on health!"); Action = ActionType.Flee; } else if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have detected {Mobile.FocusMob.Name}, attacking"); - } + this.DebugSayFormatted($"I have detected {Mobile.FocusMob.Name}, attacking"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; @@ -55,10 +49,7 @@ public class AnimalAI : BaseAI if (combatant == null || combatant.Deleted || combatant.Map != Mobile.Map || !combatant.Alive || combatant.IsDeadBondedPet) { - if (Mobile.Debug) - { - Mobile.DebugSay("My combatant is gone!"); - } + DebugSay("My combatant is gone!"); Action = ActionType.Wander; return true; @@ -68,19 +59,13 @@ public class AnimalAI : BaseAI { if (Mobile.GetDistanceToSqrt(combatant) > Mobile.RangePerception + 1) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I cannot find {combatant.Name}"); - } + this.DebugSayFormatted($"I cannot find {combatant.Name}"); Action = ActionType.Wander; return true; } - if (Mobile.Debug) - { - Mobile.DebugSay($"I should be closer to {combatant.Name}"); - } + this.DebugSayFormatted($"I should be closer to {combatant.Name}"); } else if (Core.TickCount - Mobile.LastMoveTime > 400) { @@ -93,10 +78,7 @@ public class AnimalAI : BaseAI if (hitPercent <= 0.1) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am low on health!"); - } + DebugSay("I am low on health!"); Action = ActionType.Flee; return true; @@ -105,10 +87,7 @@ public class AnimalAI : BaseAI if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I used my abilities on {combatant.Name}!"); - } + this.DebugSayFormatted($"I used my abilities on {combatant.Name}!"); } return true; @@ -126,20 +105,14 @@ public class AnimalAI : BaseAI { if (WalkMobileRange(Mobile.FocusMob, 1, false, Mobile.RangePerception, Mobile.RangePerception * 2)) { - if (Mobile.Debug) - { - Mobile.DebugSay("Well, here I am safe"); - } + DebugSay("Well, here I am safe"); Action = ActionType.Wander; } } else { - if (Mobile.Debug) - { - Mobile.DebugSay("I have lost my focus, lets relax"); - } + DebugSay("I have lost my focus, lets relax"); Action = ActionType.Wander; } diff --git a/Projects/UOContent/Mobiles/AI/ArcherAI.cs b/Projects/UOContent/Mobiles/AI/ArcherAI.cs index 660ca702f..6491c42d5 100644 --- a/Projects/UOContent/Mobiles/AI/ArcherAI.cs +++ b/Projects/UOContent/Mobiles/AI/ArcherAI.cs @@ -11,17 +11,11 @@ public class ArcherAI : BaseAI public override bool DoActionWander() { - if (Mobile.Debug) - { - Mobile.DebugSay("I have no combatant"); - } + DebugSay("I have no combatant"); if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have detected {Mobile.FocusMob.Name} and I will attack"); - } + this.DebugSayFormatted($"I have detected {Mobile.FocusMob.Name} and I will attack"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; @@ -41,10 +35,7 @@ public class ArcherAI : BaseAI if (combatant == null || combatant.Deleted || combatant.Map != Mobile.Map || !combatant.Alive || combatant.IsDeadBondedPet) { - if (Mobile.Debug) - { - Mobile.DebugSay("My combatant is gone, so my guard is up"); - } + DebugSay("My combatant is gone, so my guard is up"); Action = ActionType.Guard; return true; @@ -58,17 +49,11 @@ public class ArcherAI : BaseAI Mobile.Weapon.MaxRange )) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am still not in range of {combatant.Name}"); - } + this.DebugSayFormatted($"I am still not in range of {combatant.Name}"); if ((int)Mobile.GetDistanceToSqrt(combatant) > Mobile.RangePerception + 1) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have lost {combatant.Name}"); - } + this.DebugSayFormatted($"I have lost {combatant.Name}"); Mobile.Combatant = null; Action = ActionType.Guard; @@ -82,10 +67,7 @@ public class ArcherAI : BaseAI if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I used my abilities on {combatant.Name}!"); - } + this.DebugSayFormatted($"I used my abilities on {combatant.Name}!"); return true; } @@ -118,10 +100,7 @@ public class ArcherAI : BaseAI { if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have detected {Mobile.FocusMob.Name}, attacking"); - } + this.DebugSayFormatted($"I have detected {Mobile.FocusMob.Name}, attacking"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index 8ddf00d32..cc8ee14c3 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -102,6 +102,7 @@ public abstract class BaseAI private long _nextDetectHidden; private long _nextStopGuard; + public long NextDebugMessage { get; set; } protected PathFollower _path; public Timer _timer; @@ -463,20 +464,14 @@ public abstract class BaseAI if (Mobile.Controlled && Mobile.Commandable) { - if (Mobile.Debug) - { - Mobile.DebugSay("Listening..."); - } + DebugSay("Listening..."); var isOwner = e.Mobile == Mobile.ControlMaster; var isFriend = !isOwner && Mobile.IsPetFriend(e.Mobile); if (e.Mobile.Alive && (isOwner || isFriend)) { - if (Mobile.Debug) - { - Mobile.DebugSay("It's from my master"); - } + DebugSay("It's from my master"); var keywords = e.Keywords; var speech = e.Speech; @@ -774,10 +769,7 @@ public abstract class BaseAI { if (e.Mobile.AccessLevel >= AccessLevel.GameMaster) { - if (Mobile.Debug) - { - Mobile.DebugSay("It's from a GM"); - } + DebugSay("It's from a GM"); if (Mobile.FindMyName(e.Speech, true)) { @@ -805,6 +797,15 @@ public abstract class BaseAI } } + public void DebugSay(string message, int cooldownMs = 5000) + { + if (Mobile.Debug && Core.TickCount >= NextDebugMessage) + { + Mobile.PublicOverheadMessage(MessageType.Regular, 41, false, message); + NextDebugMessage = Core.TickCount + cooldownMs; + } + } + public virtual bool Think() { if (Mobile.Deleted) @@ -923,10 +924,7 @@ public abstract class BaseAI { if (CheckHerding()) { - if (Mobile.Debug) - { - Mobile.DebugSay("Praise the shepherd!"); - } + DebugSay("Praise the shepherd!"); } else if (Mobile.CurrentWayPoint != null) { @@ -934,19 +932,13 @@ public abstract class BaseAI if ((point.X != Mobile.Location.X || point.Y != Mobile.Location.Y) && point.Map == Mobile.Map && point.Parent == null && !point.Deleted) { - if (Mobile.Debug) - { - Mobile.DebugSay("I will move towards my waypoint."); - } + DebugSay("I will move towards my waypoint."); DoMove(Mobile.GetDirectionTo(Mobile.CurrentWayPoint)); } else if (OnAtWayPoint()) { - if (Mobile.Debug) - { - Mobile.DebugSay("I will go to the next waypoint"); - } + DebugSay("I will go to the next waypoint"); Mobile.CurrentWayPoint = point.NextPoint; if (point.NextPoint?.Deleted == true) @@ -987,10 +979,7 @@ public abstract class BaseAI { if (Core.AOS && CheckHerding()) { - if (Mobile.Debug) - { - Mobile.DebugSay("Praise the shepherd!"); - } + DebugSay("Praise the shepherd!"); return true; } @@ -1000,10 +989,7 @@ public abstract class BaseAI if (combatant == null || combatant.Deleted || combatant.Map != Mobile.Map || !combatant.Alive || combatant.IsDeadBondedPet) { - if (Mobile.Debug) - { - Mobile.DebugSay("My combatant is gone!"); - } + DebugSay("My combatant is gone!"); Action = ActionType.Wander; return true; @@ -1012,10 +998,7 @@ public abstract class BaseAI Mobile.Direction = Mobile.GetDirectionTo(combatant); if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I used my abilities on {combatant.Name}!"); - } + this.DebugSayFormatted($"I used my abilities on {combatant.Name}!"); } return true; @@ -1025,25 +1008,16 @@ public abstract class BaseAI { if (Core.AOS && CheckHerding()) { - if (Mobile.Debug) - { - Mobile.DebugSay("Praise the shepherd!"); - } + DebugSay("Praise the shepherd!"); } else if (Core.TickCount - _nextStopGuard < 0) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am on guard"); - } + DebugSay("I am on guard"); // m_Mobile.Turn( Utility.Random(0, 2) - 1 ); } else { - if (Mobile.Debug) - { - Mobile.DebugSay("I stopped being on guard"); - } + DebugSay("I stopped being on guard"); Action = ActionType.Wander; } @@ -1057,10 +1031,7 @@ public abstract class BaseAI if (from?.Deleted != false || from.Map != Mobile.Map) { - if (Mobile.Debug) - { - Mobile.DebugSay("I have lost him"); - } + DebugSay("I have lost him"); Action = ActionType.Guard; return true; @@ -1068,19 +1039,13 @@ public abstract class BaseAI if (WalkMobileRange(from, 1, true, Mobile.RangePerception * 2, Mobile.RangePerception * 3)) { - if (Mobile.Debug) - { - Mobile.DebugSay("I have fled"); - } + DebugSay("I have fled"); Action = ActionType.Guard; return true; } - if (Mobile.Debug) - { - Mobile.DebugSay("I am fleeing!"); - } + DebugSay("I am fleeing!"); return true; } @@ -1244,10 +1209,7 @@ public abstract class BaseAI public virtual bool DoOrderNone() { - if (Mobile.Debug) - { - Mobile.DebugSay("I have no order"); - } + DebugSay("I have no order"); WalkRandomInHome(3, 2, 1); @@ -1276,20 +1238,14 @@ public abstract class BaseAI if (iCurrDist > Mobile.RangePerception) { - if (Mobile.Debug) - { - Mobile.DebugSay("I have lost my master. I stay here"); - } + DebugSay("I have lost my master. I stay here"); Mobile.ControlTarget = null; Mobile.ControlOrder = OrderType.None; } else { - if (Mobile.Debug) - { - Mobile.DebugSay("My master told me come"); - } + DebugSay("My master told me come"); // Not exactly OSI style, but better than nothing. var bRun = iCurrDist > 5; @@ -1319,10 +1275,7 @@ public abstract class BaseAI return true; } - if (Mobile.Debug) - { - Mobile.DebugSay("I drop my stuff for my master"); - } + DebugSay("I drop my stuff for my master"); var pack = Mobile.Backpack; @@ -1389,10 +1342,7 @@ public abstract class BaseAI { if (CheckHerding()) { - if (Mobile.Debug) - { - Mobile.DebugSay("Praise the shepherd!"); - } + DebugSay("Praise the shepherd!"); } else if (Mobile.ControlTarget?.Deleted == false && Mobile.ControlTarget != Mobile) { @@ -1400,10 +1350,7 @@ public abstract class BaseAI if (iCurrDist > Mobile.RangePerception) { - if (Mobile.Debug) - { - Mobile.DebugSay("I have lost the one to follow. I stay here"); - } + DebugSay("I have lost the one to follow. I stay here"); if (Mobile.Combatant?.Deleted == false && Mobile.Combatant.Alive && !Mobile.Combatant.IsDeadBondedPet) @@ -1418,10 +1365,7 @@ public abstract class BaseAI } else { - if (Mobile.Debug) - { - Mobile.DebugSay($"My master told me to follow: {Mobile.ControlTarget.Name}"); - } + this.DebugSayFormatted($"My master told me to follow: {Mobile.ControlTarget.Name}"); // Not exactly OSI style, but better than nothing. var bRun = iCurrDist > 5; @@ -1447,10 +1391,7 @@ public abstract class BaseAI } else { - if (Mobile.Debug) - { - Mobile.DebugSay("I have nobody to follow"); - } + DebugSay("I have nobody to follow"); Mobile.ControlTarget = null; Mobile.ControlOrder = OrderType.None; @@ -1600,9 +1541,9 @@ public abstract class BaseAI } } - if (combatant != null && Mobile.Debug) + if (combatant != null) { - Mobile.DebugSay("Crap, my master has been attacked! I will attack one of those bastards!"); + DebugSay("Crap, my master has been attacked! I will attack one of those bastards!"); } } @@ -1610,10 +1551,7 @@ public abstract class BaseAI combatant.Alive && !combatant.IsDeadBondedPet && Mobile.CanSee(combatant) && Mobile.CanBeHarmful(combatant, false) && combatant.Map == Mobile.Map) { - if (Mobile.Debug) - { - Mobile.DebugSay("Guarding from target..."); - } + DebugSay("Guarding from target..."); Mobile.Combatant = combatant; Mobile.FocusMob = combatant; @@ -1627,10 +1565,7 @@ public abstract class BaseAI } else { - if (Mobile.Debug) - { - Mobile.DebugSay("Nothing to guard from"); - } + DebugSay("Nothing to guard from"); Mobile.Warmode = false; if (Core.AOS) @@ -1654,12 +1589,7 @@ public abstract class BaseAI if (Mobile.ControlTarget?.Deleted != false || Mobile.ControlTarget.Map != Mobile.Map || !Mobile.ControlTarget.Alive || Mobile.ControlTarget.IsDeadBondedPet) { - if (Mobile.Debug) - { - Mobile.DebugSay( - "I think he might be dead. He's not anywhere around here at least. That's cool. I'm glad he's dead." - ); - } + DebugSay("I think he might be dead. He's not anywhere around here at least. That's cool. I'm glad he's dead."); if (Core.AOS) { @@ -1703,10 +1633,7 @@ public abstract class BaseAI Mobile.ControlTarget = newCombatant; Mobile.ControlOrder = OrderType.Attack; Mobile.Combatant = newCombatant; - if (Mobile.Debug) - { - Mobile.DebugSay("But -that- is not dead. Here we go again..."); - } + DebugSay("But -that- is not dead. Here we go again..."); Think(); } @@ -1714,10 +1641,7 @@ public abstract class BaseAI } else { - if (Mobile.Debug) - { - Mobile.DebugSay("Attacking target..."); - } + DebugSay("Attacking target..."); Think(); } @@ -1727,20 +1651,14 @@ public abstract class BaseAI public virtual bool DoOrderPatrol() { - if (Mobile.Debug) - { - Mobile.DebugSay("This order is not yet coded"); - } + DebugSay("This order is not yet coded"); return true; } public virtual bool DoOrderRelease() { - if (Mobile.Debug) - { - Mobile.DebugSay("I have been released"); - } + DebugSay("I have been released"); Mobile.PlaySound(Mobile.GetAngerSound()); @@ -1776,17 +1694,7 @@ public abstract class BaseAI public virtual bool DoOrderStay() { - if (Mobile.Debug) - { - if (CheckHerding()) - { - Mobile.DebugSay("Praise the shepherd!"); - } - else - { - Mobile.DebugSay("My master told me to stay"); - } - } + DebugSay(CheckHerding() ? "Praise the shepherd!" : "My master told me to stay"); return true; } @@ -1798,10 +1706,7 @@ public abstract class BaseAI return true; } - if (Mobile.Debug) - { - Mobile.DebugSay("My master told me to stop."); - } + DebugSay("My master told me to stop."); Mobile.Direction = Mobile.GetDirectionTo(Mobile.ControlMaster); Mobile.Home = Mobile.Location; @@ -1832,10 +1737,7 @@ public abstract class BaseAI if (from?.Deleted == false && to?.Deleted == false && from != to && to.Player) { - if (Mobile.Debug) - { - Mobile.DebugSay($"Begin transfer with {to.Name}"); - } + this.DebugSayFormatted($"Begin transfer with {to.Name}"); var youngFrom = from is PlayerMobile mobile && mobile.Young; var youngTo = to is PlayerMobile playerMobile && playerMobile.Young; @@ -1911,20 +1813,14 @@ public abstract class BaseAI { if (Core.Now < Mobile.BardEndTime) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am pacified, I wait"); - } + DebugSay("I am pacified, I wait"); Mobile.Combatant = null; Mobile.Warmode = false; } else { - if (Mobile.Debug) - { - Mobile.DebugSay("I'm not pacified any longer"); - } + DebugSay("I'm not pacified any longer"); Mobile.BardPacified = false; } @@ -1939,10 +1835,7 @@ public abstract class BaseAI Mobile.BardMaster.Map != Mobile.Map || Mobile.GetDistanceToSqrt(Mobile.BardMaster) > Mobile.RangePerception)) { - if (Mobile.Debug) - { - Mobile.DebugSay("I have lost my provoker"); - } + DebugSay("I have lost my provoker"); Mobile.BardProvoked = false; Mobile.BardMaster = null; @@ -1954,10 +1847,7 @@ public abstract class BaseAI else if (Mobile.BardTarget?.Deleted != false || Mobile.BardTarget.Map != Mobile.Map || Mobile.GetDistanceToSqrt(Mobile.BardTarget) > Mobile.RangePerception) { - if (Mobile.Debug) - { - Mobile.DebugSay("I have lost my provoke target"); - } + DebugSay("I have lost my provoke target"); Mobile.BardProvoked = false; Mobile.BardMaster = null; @@ -2172,10 +2062,7 @@ public abstract class BaseAI if (canOpenDoors || canDestroyObstacles) { - if (Mobile.Debug) - { - Mobile.DebugSay("My movement was blocked, I will try to clear some obstacles."); - } + DebugSay("My movement was blocked, I will try to clear some obstacles."); var map = Mobile.Map; @@ -2236,28 +2123,15 @@ public abstract class BaseAI if (item is BaseDoor door) { - if (Mobile.Debug) - { - Mobile.DebugSay( - "Little do they expect, I've learned how to open doors. Didn't they read the script??" - ); - } + DebugSay("Little do they expect, I've learned how to open doors. Didn't they read the script??"); - if (Mobile.Debug) - { - Mobile.DebugSay("*twist*"); - } + DebugSay("*twist*"); door.Use(Mobile); } else { - if (Mobile.Debug) - { - Mobile.DebugSay( - $"Ugabooga. I'm so big and tough I can destroy it: {item.GetType().Name}" - ); - } + this.DebugSayFormatted($"Ugabooga. I'm so big and tough I can destroy it: {item.GetType().Name}"); if (item is Container cont) { @@ -2410,10 +2284,7 @@ public abstract class BaseAI { if (_path != null) { - if (Mobile.Debug) - { - Mobile.DebugSay("Teleported; repathing"); - } + DebugSay("Teleported; repathing"); _path.ForceRepath(); } @@ -2578,10 +2449,7 @@ public abstract class BaseAI if (Mobile.ConstantFocus != null) { - if (Mobile.Debug) - { - Mobile.DebugSay("Acquired my constant focus"); - } + DebugSay("Acquired my constant focus"); Mobile.FocusMob = Mobile.ConstantFocus; return true; @@ -2608,10 +2476,7 @@ public abstract class BaseAI Mobile.NextReacquireTime = Core.TickCount + (int)Mobile.ReacquireDelay.TotalMilliseconds; - if (Mobile.Debug) - { - Mobile.DebugSay("Acquiring..."); - } + DebugSay("Acquiring..."); var map = Mobile.Map; @@ -2806,10 +2671,7 @@ public abstract class BaseAI return; } - if (Mobile.Debug) - { - Mobile.DebugSay("Checking for hidden players"); - } + DebugSay("Checking for hidden players"); var srcSkill = Mobile.Skills.DetectHidden.Value; @@ -2823,10 +2685,7 @@ public abstract class BaseAI if (trg != Mobile && trg.Player && trg.Alive && trg.Hidden && trg.AccessLevel == AccessLevel.Player && Mobile.InLOS(trg)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"Trying to detect {trg.Name}"); - } + this.DebugSayFormatted($"Trying to detect {trg.Name}"); var trgHiding = trg.Skills.Hiding.Value / 2.9; var trgStealth = trg.Skills.Stealth.Value / 1.8; diff --git a/Projects/UOContent/Mobiles/AI/BaseAI/BaseAIExtensions.cs b/Projects/UOContent/Mobiles/AI/BaseAI/BaseAIExtensions.cs new file mode 100644 index 000000000..e46181c00 --- /dev/null +++ b/Projects/UOContent/Mobiles/AI/BaseAI/BaseAIExtensions.cs @@ -0,0 +1,20 @@ +using System.Runtime.CompilerServices; +using Server.Mobiles.AI.BaseAI; + +namespace Server.Mobiles; + +public static class BaseAIExtensions +{ + public static void DebugSayFormatted(this BaseAI ai, + [InterpolatedStringHandlerArgument("ai")] ref DebugInterpolatedStringHandler handler, int cooldownMs = 5000) + { + var message = handler.Text; + if (message.Length > 0) + { + ai.Mobile.PublicOverheadMessage(MessageType.Regular, 41, false, message.ToString()); + ai.NextDebugMessage = Core.TickCount + cooldownMs; + } + + handler.Clear(); + } +} diff --git a/Projects/UOContent/Mobiles/AI/BaseAI/DebugInterpolatedStringHandler.cs b/Projects/UOContent/Mobiles/AI/BaseAI/DebugInterpolatedStringHandler.cs new file mode 100644 index 000000000..c2d5dcf15 --- /dev/null +++ b/Projects/UOContent/Mobiles/AI/BaseAI/DebugInterpolatedStringHandler.cs @@ -0,0 +1,662 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.CompilerServices; +using Server.Buffers; + +namespace Server.Mobiles.AI.BaseAI; + +[InterpolatedStringHandler] +public ref struct DebugInterpolatedStringHandler +{ + // Implementation note: + // As this type lives in CompilerServices and is only intended to be targeted by the compiler, + // public APIs eschew argument validation logic in a variety of places, e.g. allowing a null input + // when one isn't expected to produce a NullReferenceException rather than an ArgumentNullException. + + /// Expected average length of formatted data used for an individual interpolation expression result. + /// + /// This is inherited from string.Format, and could be changed based on further data. + /// string.Format actually uses `format.Length + args.Length * 8`, but format.Length + /// includes the format items themselves, e.g. "{0}", and since it's rare to have double-digit + /// numbers of items, we bump the 8 up to 11 to account for the three extra characters in "{d}", + /// since the compiler-provided base length won't include the equivalent character count. + /// + private const int GuessedLengthPerHole = 11; + /// Minimum size array to rent from the pool. + /// Same as stack-allocation size used today by string.Format. + private const int MinimumArrayPoolLength = 256; + + /// Optional provider to pass to IFormattable.ToString or ISpanFormattable.TryFormat calls. + private readonly IFormatProvider? _provider; + /// Array rented from the array pool and used to back . + private char[]? _arrayToReturnToPool; + /// The span to write into. + private Span _chars; + /// Position at which to write the next character. + private int _pos; + /// Whether provides an ICustomFormatter. + /// + /// Custom formatters are very rare. We want to support them, but it's ok if we make them more expensive + /// in order to make them as pay-for-play as possible. So, we avoid adding another reference type field + /// to reduce the size of the handler and to reduce required zero'ing, by only storing whether the provider + /// provides a formatter, rather than actually storing the formatter. This in turn means, if there is a + /// formatter, we pay for the extra interface call on each AppendFormatted that needs it. + /// + private readonly bool _hasCustomFormatter; + + private readonly bool _debugActive; + + /// Creates a handler used to translate an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public DebugInterpolatedStringHandler(int literalLength, int formattedCount, Mobiles.BaseAI ai) + { + _debugActive = ai.Mobile?.Debug == true && Core.TickCount >= ai.NextDebugMessage; + + if (_debugActive) + { + _chars = _arrayToReturnToPool = STArrayPool.Shared.Rent(GetDefaultLength(literalLength, formattedCount)); + } + else + { + _chars = _arrayToReturnToPool = null; + } + + _provider = null; + _pos = 0; + _hasCustomFormatter = false; + } + + /// Creates a handler used to translate an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// An object that supplies culture-specific formatting information. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public DebugInterpolatedStringHandler(int literalLength, int formattedCount, IFormatProvider? provider, Mobiles.BaseAI ai) + { + _debugActive = (ai.Mobile as BaseCreature)?.Debug == true && Core.TickCount >= ai.NextDebugMessage; + + if (_debugActive) + { + _chars = _arrayToReturnToPool = STArrayPool.Shared.Rent(GetDefaultLength(literalLength, formattedCount)); + } + else + { + _chars = _arrayToReturnToPool = null; + } + + _provider = provider; + _pos = 0; + _hasCustomFormatter = provider is not null && HasCustomFormatter(provider); + } + + /// Derives a default length with which to seed the handler. + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // becomes a constant when inputs are constant + internal static int GetDefaultLength(int literalLength, int formattedCount) => + Math.Max(MinimumArrayPoolLength, literalLength + formattedCount * GuessedLengthPerHole); + + /// Clears the handler, returning any rented array to the pool. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // used only on a few hot paths + public void Clear() + { + char[]? toReturn = _arrayToReturnToPool; + this = default; // defensive clear + if (toReturn is not null) + { + STArrayPool.Shared.Return(toReturn); + } + } + + /// Gets a span of the written characters thus far. + public ReadOnlySpan Text => _chars[.._pos]; + + /// Writes the specified string to the handler. + /// The string to write. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AppendLiteral(string value) + { + if (!_debugActive) + { + return; + } + + if (value.Length == 1) + { + Span chars = _chars; + int pos = _pos; + if ((uint)pos < (uint)chars.Length) + { + chars[pos] = value[0]; + _pos = pos + 1; + } + else + { + GrowThenCopyString(value); + } + return; + } + + AppendStringDirect(value); + } + + /// Writes the specified string to the handler. + /// The string to write. + private void AppendStringDirect(string value) + { + if (value.TryCopyTo(_chars[_pos..])) + { + _pos += value.Length; + } + else + { + GrowThenCopyString(value); + } + } + + #region AppendFormatted + // Design note: + // The compiler requires a AppendFormatted overload for anything that might be within an interpolation expression; + // if it can't find an appropriate overload, for handlers in general it'll simply fail to compile. + // (For target-typing to string where it uses DefaultInterpolatedStringHandler implicitly, it'll instead fall back to + // its other mechanisms, e.g. using string.Format. This fallback has the benefit that if we miss a case, + // interpolated strings will still work, but it has the downside that a developer generally won't know + // if the fallback is happening and they're paying more.) + // + // At a minimum, then, we would need an overload that accepts: + // (object value, int alignment = 0, string? format = null) + // Such an overload would provide the same expressiveness as string.Format. However, this has several + // shortcomings: + // - Every value type in an interpolation expression would be boxed. + // - ReadOnlySpan could not be used in interpolation expressions. + // - Every AppendFormatted call would have three arguments at the call site, bloating the IL further. + // - Every invocation would be more expensive, due to lack of specialization, every call needing to account + // for alignment and format, etc. + // + // To address that, we could just have overloads for T and ReadOnlySpan: + // (T) + // (T, int alignment) + // (T, string? format) + // (T, int alignment, string? format) + // (ReadOnlySpan) + // (ReadOnlySpan, int alignment) + // (ReadOnlySpan, string? format) + // (ReadOnlySpan, int alignment, string? format) + // but this also has shortcomings: + // - Some expressions that would have worked with an object overload will now force a fallback to string.Format + // (or fail to compile if the handler is used in places where the fallback isn't provided), because the compiler + // can't always target type to T, e.g. `b switch { true => 1, false => null }` where `b` is a bool can successfully + // be passed as an argument of type `object` but not of type `T`. + // - Reference types get no benefit from going through the generic code paths, and actually incur some overheads + // from doing so. + // - Nullable value types also pay a heavy price, in particular around interface checks that would generally evaporate + // at compile time for value types but don't (currently) if the Nullable goes through the same code paths + // (see https://github.com/dotnet/runtime/issues/50915). + // + // We could try to take a more elaborate approach for DefaultInterpolatedStringHandler, since it is the most common handler + // and we want to minimize overheads both at runtime and in IL size, e.g. have a complete set of overloads for each of: + // (T, ...) where T : struct + // (T?, ...) where T : struct + // (object, ...) + // (ReadOnlySpan, ...) + // (string, ...) + // but this also has shortcomings, most importantly: + // - If you have an unconstrained T that happens to be a value type, it'll now end up getting boxed to use the object overload. + // This also necessitates the T? overload, since nullable value types don't meet a T : struct constraint, so without those + // they'd all map to the object overloads as well. + // - Any reference type with an implicit cast to ROS will fail to compile due to ambiguities between the overloads. string + // is one such type, hence needing dedicated overloads for it that can be bound to more tightly. + // + // A middle ground we've settled on, which is likely to be the right approach for most other handlers as well, would be the set: + // (T, ...) with no constraint + // (ReadOnlySpan) and (ReadOnlySpan, int) + // (object, int alignment = 0, string? format = null) + // (string) and (string, int) + // This would address most of the concerns, at the expense of: + // - Most reference types going through the generic code paths and so being a bit more expensive. + // - Nullable types being more expensive until https://github.com/dotnet/runtime/issues/50915 is addressed. + // We could choose to add a T? where T : struct set of overloads if necessary. + // Strings don't require their own overloads here, but as they're expected to be very common and as we can + // optimize them in several ways (can copy the contents directly, don't need to do any interface checks, don't + // need to pay the shared generic overheads, etc.) we can add overloads specifically to optimize for them. + // + // Hole values are formatted according to the following policy: + // 1. If an IFormatProvider was supplied and it provides an ICustomFormatter, use ICustomFormatter.Format (even if the value is null). + // 2. If the type implements ISpanFormattable, use ISpanFormattable.TryFormat. + // 3. If the type implements IFormattable, use IFormattable.ToString. + // 4. Otherwise, use object.ToString. + // This matches the behavior of string.Format, StringBuilder.AppendFormat, etc. The only overloads for which this doesn't + // apply is ReadOnlySpan, which isn't supported by either string.Format nor StringBuilder.AppendFormat, but more + // importantly which can't be boxed to be passed to ICustomFormatter.Format. + + #region AppendFormatted T + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(T value) + { + if (!_debugActive) + { + return; + } + + // This method could delegate to AppendFormatted with a null format, but explicitly passing + // default as the format to TryFormat helps to improve code quality in some cases when TryFormat is inlined, + // e.g. for Int32 it enables the JIT to eliminate code in the inlined method based on a length check on the format. + + // If there's a custom formatter, always use it. + if (_hasCustomFormatter) + { + AppendCustomFormatter(value, format: null); + return; + } + + // Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter + // requires the former. For value types, it won't matter as the type checks devolve into + // JIT-time constants. For reference types, they're more likely to implement IFormattable + // than they are to implement ISpanFormattable: if they don't implement either, we save an + // interface check over first checking for ISpanFormattable and then for IFormattable, and + // if it only implements IFormattable, we come out even: only if it implements both do we + // end up paying for an extra interface check. + string? s; + if (value is IFormattable) + { + // If the value can format itself directly into our buffer, do so. + if (value is ISpanFormattable) + { + int charsWritten; + while (!((ISpanFormattable)value).TryFormat(_chars[_pos..], out charsWritten, default, _provider)) // constrained call avoiding boxing for value types + { + Grow(); + } + + _pos += charsWritten; + return; + } + + s = ((IFormattable)value).ToString(format: null, _provider); // constrained call avoiding boxing for value types + } + else + { + s = value?.ToString(); + } + + if (s is not null) + { + AppendStringDirect(s); + } + } + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + public void AppendFormatted(T value, string? format) + { + if (!_debugActive) + { + return; + } + + // If there's a custom formatter, always use it. + if (_hasCustomFormatter) + { + AppendCustomFormatter(value, format); + return; + } + + // Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter + // requires the former. For value types, it won't matter as the type checks devolve into + // JIT-time constants. For reference types, they're more likely to implement IFormattable + // than they are to implement ISpanFormattable: if they don't implement either, we save an + // interface check over first checking for ISpanFormattable and then for IFormattable, and + // if it only implements IFormattable, we come out even: only if it implements both do we + // end up paying for an extra interface check. + string? s; + if (value is IFormattable) + { + // If the value can format itself directly into our buffer, do so. + if (value is ISpanFormattable) + { + int charsWritten; + while (!((ISpanFormattable)value).TryFormat(_chars[_pos..], out charsWritten, format, _provider)) // constrained call avoiding boxing for value types + { + Grow(); + } + + _pos += charsWritten; + return; + } + + s = ((IFormattable)value).ToString(format, _provider); // constrained call avoiding boxing for value types + } + else + { + s = value?.ToString(); + } + + if (s is not null) + { + AppendStringDirect(s); + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public void AppendFormatted(T value, int alignment) + { + if (!_debugActive) + { + return; + } + + int startingPos = _pos; + AppendFormatted(value); + if (alignment != 0) + { + AppendOrInsertAlignmentIfNeeded(startingPos, alignment); + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + public void AppendFormatted(T value, int alignment, string? format) + { + if (!_debugActive) + { + return; + } + + int startingPos = _pos; + AppendFormatted(value, format); + if (alignment != 0) + { + AppendOrInsertAlignmentIfNeeded(startingPos, alignment); + } + } + #endregion + + #region AppendFormatted ReadOnlySpan + /// Writes the specified character span to the handler. + /// The span to write. + public void AppendFormatted(ReadOnlySpan value) + { + if (!_debugActive) + { + return; + } + + // Fast path for when the value fits in the current buffer + if (value.TryCopyTo(_chars[_pos..])) + { + _pos += value.Length; + } + else + { + GrowThenCopySpan(value); + } + } + + /// Writes the specified string of chars to the handler. + /// The span to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null) + { + if (!_debugActive) + { + return; + } + + bool leftAlign = false; + if (alignment < 0) + { + leftAlign = true; + alignment = -alignment; + } + + int paddingRequired = alignment - value.Length; + if (paddingRequired <= 0) + { + // The value is as large or larger than the required amount of padding, + // so just write the value. + AppendFormatted(value); + return; + } + + // Write the value along with the appropriate padding. + EnsureCapacityForAdditionalChars(value.Length + paddingRequired); + if (leftAlign) + { + value.CopyTo(_chars[_pos..]); + _pos += value.Length; + _chars.Slice(_pos, paddingRequired).Fill(' '); + _pos += paddingRequired; + } + else + { + _chars.Slice(_pos, paddingRequired).Fill(' '); + _pos += paddingRequired; + value.CopyTo(_chars[_pos..]); + _pos += value.Length; + } + } + #endregion + + #region AppendFormatted string + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(string? value) + { + if (!_debugActive) + { + return; + } + + // Fast-path for no custom formatter and a non-null string that fits in the current destination buffer. + if (!_hasCustomFormatter && value?.TryCopyTo(_chars[_pos..]) == true) + { + _pos += value.Length; + } + else + { + AppendFormattedSlow(value); + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// + /// Slow path to handle a custom formatter, potentially null value, + /// or a string that doesn't fit in the current buffer. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void AppendFormattedSlow(string? value) + { + if (_hasCustomFormatter) + { + AppendCustomFormatter(value, format: null); + } + else if (value is not null) + { + EnsureCapacityForAdditionalChars(value.Length); + value.CopyTo(_chars[_pos..]); + _pos += value.Length; + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(string? value, int alignment = 0, string? format = null) => + // Format is meaningless for strings and doesn't make sense for someone to specify. We have the overload + // simply to disambiguate between ROS and object, just in case someone does specify a format, as + // string is implicitly convertible to both. Just delegate to the T-based implementation. + AppendFormatted(value, alignment, format); + #endregion + + #region AppendFormatted object + /// Writes the specified value to the handler. + /// The value to write. + /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + /// The format string. + public void AppendFormatted(object? value, int alignment = 0, string? format = null) => + // This overload is expected to be used rarely, only if either a) something strongly typed as object is + // formatted with both an alignment and a format, or b) the compiler is unable to target type to T. It + // exists purely to help make cases from (b) compile. Just delegate to the T-based implementation. + AppendFormatted(value, alignment, format); + #endregion + #endregion + + /// Gets whether the provider provides a custom formatter. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // only used in a few hot path call sites + internal static bool HasCustomFormatter(IFormatProvider provider) + { + Debug.Assert(provider is not null); + Debug.Assert(provider is not CultureInfo || provider.GetFormat(typeof(ICustomFormatter)) is null, "Expected CultureInfo to not provide a custom formatter"); + return + provider.GetType() != typeof(CultureInfo) && // optimization to avoid GetFormat in the majority case + provider.GetFormat(typeof(ICustomFormatter)) != null; + } + + /// Formats the value using the custom formatter from the provider. + /// The value to write. + /// The format string. + [MethodImpl(MethodImplOptions.NoInlining)] + private void AppendCustomFormatter(T value, string? format) + { + // This case is very rare, but we need to handle it prior to the other checks in case + // a provider was used that supplied an ICustomFormatter which wanted to intercept the particular value. + // We do the cast here rather than in the ctor, even though this could be executed multiple times per + // formatting, to make the cast pay for play. + Debug.Assert(_hasCustomFormatter); + Debug.Assert(_provider != null); + + ICustomFormatter? formatter = (ICustomFormatter?)_provider.GetFormat(typeof(ICustomFormatter)); + Debug.Assert(formatter != null, "An incorrectly written provider said it implemented ICustomFormatter, and then didn't"); + + if (formatter?.Format(format, value, _provider) is string customFormatted) + { + AppendStringDirect(customFormatted); + } + } + + /// Handles adding any padding required for aligning a formatted value in an interpolation expression. + /// The position at which the written value started. + /// Non-zero minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value. + private void AppendOrInsertAlignmentIfNeeded(int startingPos, int alignment) + { + Debug.Assert(startingPos >= 0 && startingPos <= _pos); + Debug.Assert(alignment != 0); + + int charsWritten = _pos - startingPos; + + bool leftAlign = false; + if (alignment < 0) + { + leftAlign = true; + alignment = -alignment; + } + + int paddingNeeded = alignment - charsWritten; + if (paddingNeeded > 0) + { + EnsureCapacityForAdditionalChars(paddingNeeded); + + if (leftAlign) + { + _chars.Slice(_pos, paddingNeeded).Fill(' '); + } + else + { + _chars.Slice(startingPos, charsWritten).CopyTo(_chars[(startingPos + paddingNeeded)..]); + _chars.Slice(startingPos, paddingNeeded).Fill(' '); + } + + _pos += paddingNeeded; + } + } + + /// Ensures has the capacity to store beyond . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureCapacityForAdditionalChars(int additionalChars) + { + if (_chars.Length - _pos < additionalChars) + { + Grow(additionalChars); + } + } + + /// Fallback for fast path in when there's not enough space in the destination. + /// The string to write. + [MethodImpl(MethodImplOptions.NoInlining)] + private void GrowThenCopyString(string value) + { + Grow(value.Length); + value.CopyTo(_chars[_pos..]); + _pos += value.Length; + } + + /// Fallback for for when not enough space exists in the current buffer. + /// The span to write. + [MethodImpl(MethodImplOptions.NoInlining)] + private void GrowThenCopySpan(ReadOnlySpan value) + { + Grow(value.Length); + value.CopyTo(_chars[_pos..]); + _pos += value.Length; + } + + /// Grows to have the capacity to store at least beyond . + [MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible + private void Grow(int additionalChars) + { + // This method is called when the remaining space (_chars.Length - _pos) is + // insufficient to store a specific number of additional characters. Thus, we + // need to grow to at least that new total. GrowCore will handle growing by more + // than that if possible. + Debug.Assert(additionalChars > _chars.Length - _pos); + GrowCore((uint)_pos + (uint)additionalChars); + } + + /// Grows the size of . + [MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible + private void Grow() + { + // This method is called when the remaining space in _chars isn't sufficient to continue + // the operation. Thus, we need at least one character beyond _chars.Length. GrowCore + // will handle growing by more than that if possible. + GrowCore((uint)_chars.Length + 1); + } + + /// Grow the size of to at least the specified . + [MethodImpl(MethodImplOptions.AggressiveInlining)] // but reuse this grow logic directly in both of the above grow routines + private void GrowCore(uint requiredMinCapacity) + { + // We want the max of how much space we actually required and doubling our capacity (without going beyond the max allowed length). We + // also want to avoid asking for small arrays, to reduce the number of times we need to grow, and since we're working with unsigned + // ints that could technically overflow if someone tried to, for example, append a huge string to a huge string, we also clamp to int.MaxValue. + // Even if the array creation fails in such a case, we may later fail in ToStringAndClear. + + uint newCapacity = Math.Max(requiredMinCapacity, Math.Min((uint)_chars.Length * 2, 0x3FFFFFDF)); + int arraySize = (int)Math.Clamp(newCapacity, MinimumArrayPoolLength, int.MaxValue); + + char[] newArray = STArrayPool.Shared.Rent(arraySize); + _chars[.._pos].CopyTo(newArray); + + char[]? toReturn = _arrayToReturnToPool; + _chars = _arrayToReturnToPool = newArray; + + if (toReturn is not null) + { + STArrayPool.Shared.Return(toReturn); + } + } +} diff --git a/Projects/UOContent/Mobiles/AI/BerserkAI.cs b/Projects/UOContent/Mobiles/AI/BerserkAI.cs index 207d1f727..f882d6fb5 100644 --- a/Projects/UOContent/Mobiles/AI/BerserkAI.cs +++ b/Projects/UOContent/Mobiles/AI/BerserkAI.cs @@ -8,17 +8,11 @@ public class BerserkAI : BaseAI public override bool DoActionWander() { - if (Mobile.Debug) - { - Mobile.DebugSay("I have no combatant"); - } + DebugSay("I have no combatant"); if (AcquireFocusMob(Mobile.RangePerception, FightMode.Closest, false, true, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have detected {Mobile.FocusMob.Name} and I will attack"); - } + this.DebugSayFormatted($"I have detected {Mobile.FocusMob.Name} and I will attack"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; @@ -38,10 +32,7 @@ public class BerserkAI : BaseAI if (combatant == null || combatant.Deleted || combatant.Map != Mobile.Map || !combatant.Alive || combatant.IsDeadBondedPet) { - if (Mobile.Debug) - { - Mobile.DebugSay("My combatant is gone, so my guard is up"); - } + DebugSay("My combatant is gone, so my guard is up"); Action = ActionType.Guard; return true; @@ -49,17 +40,11 @@ public class BerserkAI : BaseAI if (!WalkMobileRange(combatant, 1, true, Mobile.RangeFight, Mobile.RangeFight)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am still not in range of {combatant.Name}"); - } + this.DebugSayFormatted($"I am still not in range of {combatant.Name}"); if ((int)Mobile.GetDistanceToSqrt(combatant) > Mobile.RangePerception + 1) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have lost {combatant.Name}"); - } + this.DebugSayFormatted($"I have lost {combatant.Name}"); Action = ActionType.Guard; return true; @@ -72,10 +57,7 @@ public class BerserkAI : BaseAI if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I used my abilities on {combatant.Name}!"); - } + this.DebugSayFormatted($"I used my abilities on {combatant.Name}!"); } return true; @@ -85,10 +67,7 @@ public class BerserkAI : BaseAI { if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, true, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have detected {Mobile.FocusMob.Name}, attacking"); - } + this.DebugSayFormatted($"I have detected {Mobile.FocusMob.Name}, attacking"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; diff --git a/Projects/UOContent/Mobiles/AI/HealerAI.cs b/Projects/UOContent/Mobiles/AI/HealerAI.cs index 6b4dfa2ed..e6189e033 100644 --- a/Projects/UOContent/Mobiles/AI/HealerAI.cs +++ b/Projects/UOContent/Mobiles/AI/HealerAI.cs @@ -57,10 +57,7 @@ public class HealerAI : BaseAI { if (NeedCure(toHelp)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"{toHelp.Name} needs a cure"); - } + this.DebugSayFormatted($"{toHelp.Name} needs a cure"); if (!new CureSpell(Mobile).Cast()) { @@ -69,10 +66,7 @@ public class HealerAI : BaseAI } else if (NeedGHeal(toHelp)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"{toHelp.Name} needs a greater heal"); - } + this.DebugSayFormatted($"{toHelp.Name} needs a greater heal"); if (!new GreaterHealSpell(Mobile).Cast()) { @@ -81,10 +75,7 @@ public class HealerAI : BaseAI } else if (NeedLHeal(toHelp)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"{toHelp.Name} needs a lesser heal"); - } + this.DebugSayFormatted($"{toHelp.Name} needs a lesser heal"); new HealSpell(Mobile).Cast(); } @@ -103,10 +94,7 @@ public class HealerAI : BaseAI // TODO: Should it be able to do this? if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, Mobile.Combatant)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I used my abilities on {Mobile.Combatant.Name}!"); - } + this.DebugSayFormatted($"I used my abilities on {Mobile.Combatant.Name}!"); } return true; diff --git a/Projects/UOContent/Mobiles/AI/MageAI.cs b/Projects/UOContent/Mobiles/AI/MageAI.cs index d86aa67ad..f54326baf 100644 --- a/Projects/UOContent/Mobiles/AI/MageAI.cs +++ b/Projects/UOContent/Mobiles/AI/MageAI.cs @@ -83,10 +83,7 @@ public class MageAI : BaseAI { if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am going to attack {Mobile.FocusMob.Name}"); - } + this.DebugSayFormatted($"I am going to attack {Mobile.FocusMob.Name}"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; @@ -94,19 +91,13 @@ public class MageAI : BaseAI } else if (SmartAI && Mobile.Mana < Mobile.ManaMax && !Mobile.Meditating) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am going to meditate"); - } + DebugSay("I am going to meditate"); Mobile.UseSkill(SkillName.Meditation); } else { - if (Mobile.Debug) - { - Mobile.DebugSay("I am wandering"); - } + DebugSay("I am wandering"); Mobile.Warmode = false; @@ -225,24 +216,18 @@ public class MageAI : BaseAI new TeleportSpell(Mobile).Cast(); - if (Mobile.Debug) - { - Mobile.DebugSay("I am stuck, I'm going to try teleporting away"); - } + DebugSay("I am stuck, I'm going to try teleporting away"); } else if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"My move is blocked, so I am going to attack {Mobile.FocusMob.Name}"); - } + this.DebugSayFormatted($"My move is blocked, so I am going to attack {Mobile.FocusMob.Name}"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; } - else if (Mobile.Debug) + else { - Mobile.DebugSay("I am stuck"); + DebugSay("I am stuck"); } } @@ -415,20 +400,14 @@ public class MageAI : BaseAI goto default; } - if (Mobile.Debug) - { - Mobile.DebugSay("Attempting to poison"); - } + DebugSay("Attempting to poison"); spell = new PoisonSpell(Mobile); break; } case 2: // Bless ourselves { - if (Mobile.Debug) - { - Mobile.DebugSay("Blessing myself"); - } + DebugSay("Blessing myself"); spell = new BlessSpell(Mobile); break; @@ -436,10 +415,7 @@ public class MageAI : BaseAI case 3: case 4: // Curse them { - if (Mobile.Debug) - { - Mobile.DebugSay("Attempting to curse"); - } + DebugSay("Attempting to curse"); spell = GetRandomCurseSpell(); break; @@ -451,20 +427,14 @@ public class MageAI : BaseAI goto default; } - if (Mobile.Debug) - { - Mobile.DebugSay("Attempting to paralyze"); - } + DebugSay("Attempting to paralyze"); spell = new ParalyzeSpell(Mobile); break; } case 6: // Drain mana { - if (Mobile.Debug) - { - Mobile.DebugSay("Attempting to drain mana"); - } + DebugSay("Attempting to drain mana"); spell = GetRandomManaDrainSpell(); break; @@ -476,20 +446,14 @@ public class MageAI : BaseAI goto default; } - if (Mobile.Debug) - { - Mobile.DebugSay("Attempting to invis myself"); - } + DebugSay("Attempting to invis myself"); spell = new InvisibilitySpell(Mobile); break; } default: // Damage them { - if (Mobile.Debug) - { - Mobile.DebugSay("Just doing damage"); - } + DebugSay("Just doing damage"); spell = GetRandomDamageSpell(); break; @@ -525,10 +489,7 @@ public class MageAI : BaseAI { if (c.Paralyzed && !c.Poisoned && !Mobile.Meditating) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am going to meditate"); - } + DebugSay("I am going to meditate"); Mobile.UseSkill(SkillName.Meditation); } @@ -557,18 +518,7 @@ public class MageAI : BaseAI } } - if (Mobile.Debug) - { - if (spell != null) - { - Mobile.DebugSay($"Casting {spell.Name}"); - } - else - { - Mobile.DebugSay("I don't have a spell to use!"); - } - } - + DebugSay(spell != null ? $"Casting {spell.Name}" : "I don't have a spell to use!"); return spell; } @@ -677,21 +627,12 @@ public class MageAI : BaseAI { Mobile.Combatant = c = Mobile.FocusMob!; - if (Mobile.Debug) - { - Mobile.DebugSay( - $"Something happened to my combatant, so I am going to fight {c.Name}" - ); - } - + this.DebugSayFormatted($"Something happened to my combatant, so I am going to fight {c.Name}"); Mobile.FocusMob = null; } else { - if (Mobile.Debug) - { - Mobile.DebugSay("Something happened to my combatant, and nothing is around. I am on guard."); - } + DebugSay("Something happened to my combatant, and nothing is around. I am on guard."); Action = ActionType.Guard; return true; @@ -700,19 +641,13 @@ public class MageAI : BaseAI if (!Mobile.InLOS(c)) { - if (Mobile.Debug) - { - Mobile.DebugSay("I can't see my target"); - } + DebugSay("I can't see my target"); if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { Mobile.Combatant = c = Mobile.FocusMob!; - if (Mobile.Debug) - { - Mobile.DebugSay($"I will switch to {c.Name}"); - } + this.DebugSayFormatted($"I will switch to {c.Name}"); Mobile.FocusMob = null; } @@ -742,10 +677,7 @@ public class MageAI : BaseAI if (c == null) { - if (Mobile.Debug) - { - Mobile.DebugSay("My combatant has fled, so I am on guard"); - } + DebugSay("My combatant has fled, so I am on guard"); Action = ActionType.Guard; return true; @@ -760,10 +692,7 @@ public class MageAI : BaseAI if (Utility.Random(0, 100) > fleeChance) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am going to flee from {c.Name}"); - } + this.DebugSayFormatted($"I am going to flee from {c.Name}"); Action = ActionType.Flee; return true; @@ -772,10 +701,7 @@ public class MageAI : BaseAI if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, c)) { - if (Mobile.Debug) - { - Mobile.DebugSay("I used my abilities!"); - } + DebugSay("I used my abilities!"); } else if (Mobile.Spell == null && Core.TickCount - _nextCastTime >= 0) { @@ -785,19 +711,13 @@ public class MageAI : BaseAI if (Mobile.Poisoned) // Top cast priority is cure { - if (Mobile.Debug) - { - Mobile.DebugSay("I am going to cure myself"); - } + DebugSay("I am going to cure myself"); spell = new CureSpell(Mobile); } else if (toDispel != null) // Something dispellable is attacking us { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am going to dispel {toDispel}"); - } + this.DebugSayFormatted($"I am going to dispel {toDispel}"); spell = DoDispel(toDispel); // May return null if dumb AI and doesn't have enough skill } @@ -869,10 +789,7 @@ public class MageAI : BaseAI } else if (Mobile.Spell == null && Core.TickCount - _nextCastTime >= 0) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am going to reveal my last target"); - } + DebugSay("I am going to reveal my last target"); _revealTarget = new LandTarget(_lastTargetLoc, map); Spell spell = new RevealSpell(Mobile); @@ -888,10 +805,7 @@ public class MageAI : BaseAI if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am going to attack {Mobile.FocusMob.Name}"); - } + this.DebugSayFormatted($"I am going to attack {Mobile.FocusMob.Name}"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; @@ -919,19 +833,13 @@ public class MageAI : BaseAI if ((Mobile.Mana > 20 || Mobile.Mana == Mobile.ManaMax) && Mobile.Hits > Mobile.HitsMax / 2) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am stronger now, my guard is up"); - } + DebugSay("I am stronger now, my guard is up"); Action = ActionType.Guard; } else if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am scared of {Mobile.FocusMob.Name}"); - } + this.DebugSayFormatted($"I am scared of {Mobile.FocusMob.Name}"); RunFrom(Mobile.FocusMob); Mobile.FocusMob = null; @@ -943,10 +851,7 @@ public class MageAI : BaseAI } else { - if (Mobile.Debug) - { - Mobile.DebugSay("Area seems clear, but my guard is up"); - } + DebugSay("Area seems clear, but my guard is up"); Action = ActionType.Guard; Mobile.Warmode = true; diff --git a/Projects/UOContent/Mobiles/AI/MeleeAI.cs b/Projects/UOContent/Mobiles/AI/MeleeAI.cs index 075b4f06c..adc8d566b 100644 --- a/Projects/UOContent/Mobiles/AI/MeleeAI.cs +++ b/Projects/UOContent/Mobiles/AI/MeleeAI.cs @@ -12,20 +12,14 @@ public class MeleeAI : BaseAI { if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have detected {Mobile.FocusMob.Name}, attacking"); - } + this.DebugSayFormatted($"I have detected {Mobile.FocusMob.Name}, attacking"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; } else { - if (Mobile.Debug) - { - Mobile.DebugSay("I am wandering"); - } + DebugSay("I am wandering"); Mobile.Warmode = false; @@ -42,10 +36,7 @@ public class MeleeAI : BaseAI if (combatant == null || combatant.Deleted || combatant.Map != Mobile.Map || !combatant.Alive || combatant.IsDeadBondedPet) { - if (Mobile.Debug) - { - Mobile.DebugSay("My combatant is gone, so my guard is up"); - } + DebugSay("My combatant is gone, so my guard is up"); Action = ActionType.Guard; return true; @@ -69,10 +60,7 @@ public class MeleeAI : BaseAI if (combatant == null) { - if (Mobile.Debug) - { - Mobile.DebugSay("My combatant has fled, so I am on guard"); - } + DebugSay("My combatant has fled, so I am on guard"); Action = ActionType.Guard; return true; @@ -88,10 +76,7 @@ public class MeleeAI : BaseAI if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"My move is blocked, so I am going to attack {Mobile.FocusMob!.Name}"); - } + this.DebugSayFormatted($"My move is blocked, so I am going to attack {Mobile.FocusMob!.Name}"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; @@ -100,19 +85,13 @@ public class MeleeAI : BaseAI if (Mobile.GetDistanceToSqrt(combatant) > Mobile.RangePerception + 1) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I cannot find {combatant.Name}, so my guard is up"); - } + this.DebugSayFormatted($"I cannot find {combatant.Name}, so my guard is up"); Action = ActionType.Guard; return true; } - if (Mobile.Debug) - { - Mobile.DebugSay($"I cannot find {combatant.Name}, so my guard is up"); - } + this.DebugSayFormatted($"I cannot find {combatant.Name}, so my guard is up"); } else if (Core.TickCount - Mobile.LastMoveTime > 400) { @@ -128,10 +107,7 @@ public class MeleeAI : BaseAI var fleeChance = 10 + Math.Max(0, combatant.Hits - Mobile.Hits); // (10 + diff)% chance to flee; if (Utility.Random(0, 100) < fleeChance) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am going to flee from {combatant.Name}"); - } + this.DebugSayFormatted($"I am going to flee from {combatant.Name}"); Action = ActionType.Flee; return true; @@ -141,10 +117,7 @@ public class MeleeAI : BaseAI if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant)) { - if (Mobile.Debug) - { - Mobile.DebugSay("I used my abilities!"); - } + DebugSay("I used my abilities!"); } return true; @@ -154,10 +127,7 @@ public class MeleeAI : BaseAI { if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have detected {Mobile.FocusMob.Name}, attacking"); - } + this.DebugSayFormatted($"I have detected {Mobile.FocusMob.Name}, attacking"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; @@ -174,10 +144,7 @@ public class MeleeAI : BaseAI { if (Mobile.Hits > Mobile.HitsMax / 2) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am stronger now, so I will continue fighting"); - } + DebugSay("I am stronger now, so I will continue fighting"); Mobile.PlaySound(Mobile.GetAttackSound()); Mobile.CurrentSpeed = Mobile.ActiveSpeed; diff --git a/Projects/UOContent/Mobiles/AI/PredatorAI.cs b/Projects/UOContent/Mobiles/AI/PredatorAI.cs index 31a683ca5..fe5b06a6f 100644 --- a/Projects/UOContent/Mobiles/AI/PredatorAI.cs +++ b/Projects/UOContent/Mobiles/AI/PredatorAI.cs @@ -10,19 +10,13 @@ public class PredatorAI : BaseAI { if (Mobile.Combatant != null) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am hurt or being attacked, I kill him"); - } + DebugSay("I am hurt or being attacked, I kill him"); Action = ActionType.Combat; } else if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, true, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay("There is something near, I go away"); - } + DebugSay("There is something near, I go away"); Action = ActionType.Backoff; } @@ -41,10 +35,7 @@ public class PredatorAI : BaseAI if (combatant == null || combatant.Deleted || combatant.Map != Mobile.Map || !combatant.Alive || combatant.IsDeadBondedPet) { - if (Mobile.Debug) - { - Mobile.DebugSay("My combatant is gone, so my guard is up"); - } + DebugSay("My combatant is gone, so my guard is up"); Action = ActionType.Guard; return true; @@ -54,27 +45,18 @@ public class PredatorAI : BaseAI { if (Mobile.GetDistanceToSqrt(combatant) > Mobile.RangePerception + 1) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I cannot find {combatant.Name}"); - } + this.DebugSayFormatted($"I cannot find {combatant.Name}"); Action = ActionType.Wander; return true; } - if (Mobile.Debug) - { - Mobile.DebugSay($"I should be closer to {combatant.Name}"); - } + this.DebugSayFormatted($"I should be closer to {combatant.Name}"); } if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I used my abilities on {combatant.Name}!"); - } + this.DebugSayFormatted($"I used my abilities on {combatant.Name}!"); } return true; @@ -90,20 +72,14 @@ public class PredatorAI : BaseAI { if (WalkMobileRange(Mobile.FocusMob, 1, false, Mobile.RangePerception, Mobile.RangePerception * 2)) { - if (Mobile.Debug) - { - Mobile.DebugSay("Well, here I am safe"); - } + DebugSay("Well, here I am safe"); Action = ActionType.Wander; } } else { - if (Mobile.Debug) - { - Mobile.DebugSay("I have lost my focus, lets relax"); - } + DebugSay("I have lost my focus, lets relax"); Action = ActionType.Wander; } diff --git a/Projects/UOContent/Mobiles/AI/ThiefAI.cs b/Projects/UOContent/Mobiles/AI/ThiefAI.cs index 30adec3cf..ff707f3df 100644 --- a/Projects/UOContent/Mobiles/AI/ThiefAI.cs +++ b/Projects/UOContent/Mobiles/AI/ThiefAI.cs @@ -13,17 +13,11 @@ public class ThiefAI : BaseAI public override bool DoActionWander() { - if (Mobile.Debug) - { - Mobile.DebugSay("I have no combatant"); - } + DebugSay("I have no combatant"); if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have detected {Mobile.FocusMob.Name}, attacking"); - } + this.DebugSayFormatted($"I have detected {Mobile.FocusMob.Name}, attacking"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; @@ -43,10 +37,7 @@ public class ThiefAI : BaseAI if (combatant == null || combatant.Deleted || combatant.Map != Mobile.Map || !combatant.Alive || combatant.IsDeadBondedPet) { - if (Mobile.Debug) - { - Mobile.DebugSay("My combatant is gone, so my guard is up"); - } + DebugSay("My combatant is gone, so my guard is up"); Action = ActionType.Guard; return true; @@ -54,10 +45,7 @@ public class ThiefAI : BaseAI if (!WalkMobileRange(combatant, 1, true, Mobile.RangeFight, Mobile.RangeFight)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I should be closer to {combatant.Name}"); - } + this.DebugSayFormatted($"I should be closer to {combatant.Name}"); } else { @@ -76,20 +64,14 @@ public class ThiefAI : BaseAI Core.TickCount - Mobile.NextSkillTime >= 0 && _toDisarm.LootType != LootType.Blessed && _toDisarm.LootType != LootType.Newbied) { - if (Mobile.Debug) - { - Mobile.DebugSay("Trying to steal from combatant."); - } + DebugSay("Trying to steal from combatant."); Mobile.UseSkill(SkillName.Stealing); Mobile.Target?.Invoke(Mobile, _toDisarm); } else if (_toDisarm == null && Core.TickCount - Mobile.NextSkillTime >= 0) { - if (Mobile.Debug) - { - Mobile.DebugSay($"Trying to steal from {combatant.Name}."); - } + this.DebugSayFormatted($"Trying to steal from {combatant.Name}."); bool didSteal = TryStealFrom(combatant); didSteal = TryStealFrom(combatant) || didSteal; @@ -98,10 +80,7 @@ public class ThiefAI : BaseAI if (!didSteal) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am going to flee from {combatant.Name}"); - } + this.DebugSayFormatted($"I am going to flee from {combatant.Name}"); Action = ActionType.Flee; return true; @@ -117,10 +96,7 @@ public class ThiefAI : BaseAI if (Utility.Random(0, 100) > fleeChance) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am going to flee from {combatant.Name}"); - } + this.DebugSayFormatted($"I am going to flee from {combatant.Name}"); Action = ActionType.Flee; } @@ -130,10 +106,7 @@ public class ThiefAI : BaseAI if (Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, Mobile.Combatant)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I used my abilities on {Mobile.Combatant.Name}!"); - } + this.DebugSayFormatted($"I used my abilities on {Mobile.Combatant.Name}!"); } return true; } @@ -155,10 +128,7 @@ public class ThiefAI : BaseAI { if (AcquireFocusMob(Mobile.RangePerception, Mobile.FightMode, false, false, true)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I have detected {Mobile.FocusMob.Name}, attacking"); - } + this.DebugSayFormatted($"I have detected {Mobile.FocusMob.Name}, attacking"); Mobile.Combatant = Mobile.FocusMob; Action = ActionType.Combat; @@ -175,10 +145,7 @@ public class ThiefAI : BaseAI { if (Mobile.Hits > Mobile.HitsMax / 2) { - if (Mobile.Debug) - { - Mobile.DebugSay("I am stronger now, so I will continue fighting"); - } + DebugSay("I am stronger now, so I will continue fighting"); Action = ActionType.Combat; } diff --git a/Projects/UOContent/Mobiles/AI/VendorAI.cs b/Projects/UOContent/Mobiles/AI/VendorAI.cs index 7b2e7950a..30f63dd29 100644 --- a/Projects/UOContent/Mobiles/AI/VendorAI.cs +++ b/Projects/UOContent/Mobiles/AI/VendorAI.cs @@ -15,17 +15,11 @@ public class VendorAI : BaseAI public override bool DoActionWander() { - if (Mobile.Debug) - { - Mobile.DebugSay("I'm fine"); - } + DebugSay("I'm fine"); if (Mobile.Combatant != null) { - if (Mobile.Debug) - { - Mobile.DebugSay($"{Mobile.Combatant.Name} is attacking me"); - } + this.DebugSayFormatted($"{Mobile.Combatant.Name} is attacking me"); Mobile.Say(GetRandomGuardMessage()); Action = ActionType.Flee; @@ -34,10 +28,7 @@ public class VendorAI : BaseAI { if (Mobile.FocusMob != null) { - if (Mobile.Debug) - { - Mobile.DebugSay($"{Mobile.FocusMob.Name} has talked to me"); - } + this.DebugSayFormatted($"{Mobile.FocusMob.Name} has talked to me"); Action = ActionType.Interact; } @@ -58,10 +49,7 @@ public class VendorAI : BaseAI if (Mobile.Combatant != null) { - if (Mobile.Debug) - { - Mobile.DebugSay($"{Mobile.Combatant.Name} is attacking me"); - } + this.DebugSayFormatted($"{Mobile.Combatant.Name} is attacking me"); Mobile.Say(GetRandomGuardMessage()); @@ -72,10 +60,7 @@ public class VendorAI : BaseAI if (customer?.Deleted != false || customer.Map != Mobile.Map) { - if (Mobile.Debug) - { - Mobile.DebugSay("My customer have disapeared"); - } + DebugSay("My customer have disapeared"); Mobile.FocusMob = null; @@ -83,19 +68,13 @@ public class VendorAI : BaseAI } else if (customer.InRange(Mobile, Mobile.RangeFight)) { - if (Mobile.Debug) - { - Mobile.DebugSay($"I am with {customer.Name}"); - } + this.DebugSayFormatted($"I am with {customer.Name}"); Mobile.Direction = Mobile.GetDirectionTo(customer); } else { - if (Mobile.Debug) - { - Mobile.DebugSay($"{customer.Name} is gone"); - } + this.DebugSayFormatted($"{customer.Name} is gone"); Mobile.FocusMob = null; Action = ActionType.Wander; diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index d15bf8ef8..ecfbba73f 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -2414,13 +2414,6 @@ namespace Server.Mobiles base.OnAfterDelete(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void DebugSay(string text) - { - // Moved the debug check to implementation layer so we can avoid string formatting when we do not need it - PublicOverheadMessage(MessageType.Regular, 41, false, text); - } - /* * This function can be overridden.. so a "Strongest" mobile, can have a different definition depending * on who check for value @@ -2498,7 +2491,7 @@ namespace Server.Mobiles } else { - DebugSay("I'm being attacked but my master told me not to fight."); + AIObject.DebugSay("I'm being attacked but my master told me not to fight."); Warmode = false; return; }