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