From e873d2ed7e8cca91ed1f19e2623886b9a29e5816 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 27 Feb 2022 02:20:03 -0800 Subject: [PATCH] fix: Simplifies fastwalk detection (#907) * Uses a circular buffer for steps. * Limits to 3 steps instead of 4. * Gives a 5% buffer on the first step. --- Projects/Server/Mobiles/Mobile.cs | 20 ++- Projects/Server/Mobiles/Movement.cs | 10 +- .../Network/NetState/NetState.Fastwalk.cs | 167 ++++++++++-------- .../UOContent/Network/FastwalkDetection.cs | 67 +++++++ 4 files changed, 186 insertions(+), 78 deletions(-) create mode 100644 Projects/UOContent/Network/FastwalkDetection.cs diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index c94b62b9c..84e12e759 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -9029,7 +9029,10 @@ namespace Server public Direction GetDirectionTo(IPoint2D p, bool run = false) => p == null ? Direction.North | (run ? Direction.Running : 0) : GetDirectionTo(p.X, p.Y, run); - public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text, bool noLineOfSight = true) + public void PublicOverheadMessage( + MessageType type, int hue, bool ascii, string text, bool noLineOfSight = true, + AccessLevel accessLevel = AccessLevel.Player + ) { if (m_Map == null) { @@ -9042,7 +9045,11 @@ namespace Server foreach (var state in eable) { - if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) + if ( + state.Mobile.AccessLevel >= accessLevel && + state.Mobile.CanSee(this) && + (noLineOfSight || state.Mobile.InLOS(this)) + ) { var length = OutgoingMessagePackets.CreateMessage( buffer, Serial, Body, type, hue, 3, ascii, Language, Name, text @@ -9093,7 +9100,8 @@ namespace Server public void PublicOverheadMessage( MessageType type, int hue, int number, AffixType affixType, string affix, - string args = "", bool noLineOfSight = false + string args = "", bool noLineOfSight = false, + AccessLevel accessLevel = AccessLevel.Player ) { if (m_Map == null) @@ -9107,7 +9115,11 @@ namespace Server foreach (var state in eable) { - if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) + if ( + state.Mobile.AccessLevel >= accessLevel && + state.Mobile.CanSee(this) && + (noLineOfSight || state.Mobile.InLOS(this)) + ) { var length = OutgoingMessagePackets.CreateMessageLocalizedAffix( buffer, Serial, Body, type, hue, 3, number, Name, affixType, affix, args diff --git a/Projects/Server/Mobiles/Movement.cs b/Projects/Server/Mobiles/Movement.cs index 3ea012573..44cfc473d 100644 --- a/Projects/Server/Mobiles/Movement.cs +++ b/Projects/Server/Mobiles/Movement.cs @@ -19,16 +19,16 @@ namespace Server.Movement { // Movement implementation algorithm public static IMovementImpl Impl { get; set; } - public static int WalkFootDelay { get; set; } = 440; - public static int RunFootDelay { get; set; } = 220; - public static int WalkMountDelay { get; set; } = 220; - public static int RunMountDelay { get; set; } = 110; + public static int WalkFootDelay { get; set; } = 400; + public static int RunFootDelay { get; set; } = 200; + public static int WalkMountDelay { get; set; } = 200; + public static int RunMountDelay { get; set; } = 100; public static bool EnableFastwalkPrevention { get; set; } = true; public static AccessLevel FastwalkExemptionLevel { get; set; } = AccessLevel.Counselor; // If this is changed during runtime, then the steps array needs resizing. - public static int MaxSteps { get; private set; } = 4; + public static int MaxSteps { get; private set; } = 3; public static void Configure() { diff --git a/Projects/Server/Network/NetState/NetState.Fastwalk.cs b/Projects/Server/Network/NetState/NetState.Fastwalk.cs index eb6934aaf..3b1036254 100644 --- a/Projects/Server/Network/NetState/NetState.Fastwalk.cs +++ b/Projects/Server/Network/NetState/NetState.Fastwalk.cs @@ -13,80 +13,109 @@ * along with this program. If not, see . * *************************************************************************/ +using System.Runtime.CompilerServices; using CalcMoves = Server.Movement.Movement; -namespace Server.Network +namespace Server.Network; + +public partial class NetState { - public partial class NetState + // The next step + private int _stepIndex; + // The last index to expire + private int _expiredIndex; + + private long[] _steps; + + public bool AddStep(Direction d) { - private int _stepIndex; - private int _stepCount; - private long _startDelay; - private long[] _stepDelays; - - public bool AddStep(Direction d) + if (Mobile == null) { - if (Mobile == null) - { - return false; - } - - var maxSteps = CalcMoves.MaxSteps; - - _stepDelays ??= new long[maxSteps]; - var length = _stepDelays.Length; - - var index = _stepIndex - _stepCount; - if (index < 0) - { - index += length; - } - - var now = Core.TickCount; - var last = _startDelay; - - // Discard old steps by decrementing the step counter - while (index != _stepIndex || _stepCount >= maxSteps) - { - var step = _stepDelays[index++]; - if (now - last < step) - { - break; - } - - last += step; - _stepCount--; - if (index >= length) - { - index = 0; - } - } - - _startDelay = last; - - // If we are out of steps, fail - if (_stepCount >= maxSteps) - { - return false; - } - - var delay = Mobile.ComputeMovementSpeed(d); - - // Add the delay - _stepDelays[_stepIndex++] = delay; - - if (_stepIndex >= length) - { - _stepIndex = 0; - } - - if (_stepCount == 0) - { - _startDelay = now; - } - _stepCount++; - - return true; + return false; } + + _steps ??= new long[CalcMoves.MaxSteps + 1]; // Extra index as a sentinel + var stepsLength = _steps.Length; + + var now = Core.TickCount; + + var lastIndex = -1; + + // Expire old steps + while (_expiredIndex != _stepIndex) + { + var step = _steps[_expiredIndex]; + + // Is the step ahead of us, or the next step rolled over and we didn't yet + if (step > now || lastIndex > -1 && _steps[lastIndex] > step) + { + break; + } + + lastIndex = _expiredIndex++; + + if (_expiredIndex == stepsLength) + { + _expiredIndex -= stepsLength; + } + } + + var stepsTaken = (_stepIndex < _expiredIndex ? _stepIndex + stepsLength : _stepIndex) - _expiredIndex; + var maxSteps = _steps.Length - 1; + + // Can we take a step? + if (stepsTaken >= maxSteps) + { + return false; + } + + var delay = Mobile.ComputeMovementSpeed(d); + + var prev = _stepIndex - 1; + if (prev < 0) + { + prev += stepsLength; + } + + // Give a 5% buffer on the first step + _steps[_stepIndex++] = stepsTaken > 0 ? _steps[prev] + delay : now + delay * 950 / 1000; + + if (_stepIndex == stepsLength) + { + _stepIndex -= stepsLength; + } + + // If CalcMoves.MaxSteps is modified, we need to adjust accordingly + AdjustSteps(CalcMoves.MaxSteps); + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AdjustSteps(int maxSteps) + { + var stepsLength = maxSteps + 1; + + if (_steps.Length == stepsLength) + { + return; + } + + var oldSteps = _steps; + _steps = new long[stepsLength]; + + var expiredIndex = _expiredIndex; + var newStepIndex = 0; + while (newStepIndex < maxSteps && expiredIndex != _stepIndex) + { + _steps[newStepIndex++] = oldSteps[expiredIndex++]; + if (expiredIndex >= oldSteps.Length) + { + expiredIndex -= oldSteps.Length; + } + } + + _expiredIndex = 0; + _stepIndex = newStepIndex; } } diff --git a/Projects/UOContent/Network/FastwalkDetection.cs b/Projects/UOContent/Network/FastwalkDetection.cs new file mode 100644 index 000000000..4328f0f12 --- /dev/null +++ b/Projects/UOContent/Network/FastwalkDetection.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using Server.Commands.Generic; + +namespace Server.Network; + +public static class FastwalkDetection +{ + private static readonly HashSet _debugFastwalk = new(); + + public static void Initialize() + { + TargetCommands.Register(new DebugFastwalk()); + EventSink.FastWalk += OnFastwalk; + } + + private static void OnFastwalk(FastWalkEventArgs e) + { + var from = e.NetState.Mobile; + if (from == null) + { + return; + } + + if (!_debugFastwalk.Contains(from)) + { + return; + } + + from.PublicOverheadMessage(MessageType.Emote, from.EmoteHue, false, "Fastwalk Detected", accessLevel: AccessLevel.GameMaster); + } + + public class DebugFastwalk : BaseCommand + { + public DebugFastwalk() + { + AccessLevel = AccessLevel.GameMaster; + Supports = CommandSupport.AllMobiles; + Commands = new[] { "DebugFastwalk" }; + ObjectTypes = ObjectTypes.Mobiles; + ListOptimized = true; + Usage = "DebugFastwalk "; + Description = "Enables fastwalk debug messages"; + } + + public override void ExecuteList(CommandEventArgs e, List list) + { + var on = e.Arguments.Length == 0 || e.GetBoolean(0); + + foreach (var o in list) + { + if (o is not Mobile m) + { + continue; + } + + if (on) + { + _debugFastwalk.Add(m); + } + else + { + _debugFastwalk.Remove(m); + } + } + } + } +}