Updates movement packets & Fastwalk (#324)

- [X] Updates Movement packets
- [X] Adds OSI fastwalk packets. These aren't used on OSi anymore.
- [X] Adds new movement handling, but looks like the client doesn't use it. (Also leaving the 0x4000 Character List Flag off)
- [X] Adds time sync request handler, but also looks like the client doesn't use it.
- [X] Adds time sync response for time sync, just in case, but it isn't used, so not sure about the arguments.
- [X] Reimplements RunUO's fastwalk to use a circular array on the netstate instead of every mobile
- [X] Removes ClearFastwalkStack from RunUO implementation. This shouldn't be needed anymore

Changed fastwalk settings:
```cs
        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 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;
```

modernuo.json
```json
{
  "settings": {
    "movement.delay.runFoot": "220",
    "movement.delay.runMount": "110",
    "movement.delay.walkFoot": "440",
    "movement.delay.walkMount": "220",
    "movement.enableFastWalkPrevention": "True",
    "movement.fastwalkExemptionLevel": "Counselor",
    "movement.maxSteps": "4"
  }
}
```

Notes about OSI fastwalk:
While it does work, I can't find a benefit in using it because of the variable speeds. If players moved at a single speed then we could refill the stack every X milliseconds with 6 keys and use a naive token bucket implementation.
Unfortunately variable speeds mount/run/walk/etc means we would have to use a leaky bucket algorithm.
If we are using a leaky bucket algorithm with a variable leak, then we don't need to send tokens because we are already tracking it on the server side.

ModernUO vs OSI Fastwalk:
When the fastwalk was implemented on OSI, it used up to 6 tokens. These tokens were probably distributed every 600-750 milliseconds. To get the same effect, the new fastwalk settings might need to be adjusted. I would tweak them and feel free to let me know what worked for you!

Bumps release version
This commit is contained in:
Kamron Batman 2020-11-26 23:53:47 -08:00 • committed by GitHub
parent b6cd408623
commit 351611a23a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 781 additions and 490 deletions

View file

@ -85,12 +85,12 @@ namespace Server.Commands
{
if (e.Length == 1 && !e.GetBoolean(0))
{
from.Send(SpeedControl.Disable);
from.NetState.SendSpeedControl(SpeedControlSetting.Disable);
from.SendMessage("Speed boost has been disabled.");
}
else
{
from.Send(SpeedControl.MountSpeed);
from.NetState.SendSpeedControl(SpeedControlSetting.Mount);
from.SendMessage("Speed boost has been enabled.");
}
}

View file

@ -79,9 +79,8 @@ namespace Server.Items
else if (Core.SE && Utility.RandomDouble() > .20 && (from.Direction & Direction.Running) != 0 &&
Core.TickCount - from.LastMoveTime < from.ComputeMovementSpeed(from.Direction))
{
from.SendLocalizedMessage(
1063305
); // Didn't your parents ever tell you not to run with scissors in your hand?!
// Didn't your parents ever tell you not to run with scissors in your hand?!
from.SendLocalizedMessage(1063305);
}
else if (targeted is Item item && !item.Movable)
{

View file

@ -1,34 +0,0 @@
namespace Server.Misc
{
// This fastwalk detection is no longer required
// As of B36 PlayerMobile implements movement packet throttling which more reliably controls movement speeds
public static class Fastwalk
{
private static readonly int MaxSteps = 4; // Maximum number of queued steps until fastwalk is detected
private static readonly bool Enabled = false; // Is fastwalk detection enabled?
private static readonly bool UOTDOverride = false; // Should UO:TD clients not be checked for fastwalk?
private static readonly AccessLevel
AccessOverride = AccessLevel.GameMaster; // Anyone with this or higher access level is not checked for fastwalk
public static void Initialize()
{
Mobile.FwdMaxSteps = MaxSteps;
Mobile.FwdEnabled = Enabled;
Mobile.FwdUOTDOverride = UOTDOverride;
Mobile.FwdAccessOverride = AccessOverride;
if (Enabled)
{
EventSink.FastWalk += OnFastWalk;
}
}
public static void OnFastWalk(FastWalkEventArgs e)
{
e.Blocked = true; // disallow this fastwalk
var state = e.NetState;
state.WriteConsole("Fast movement detected (name={0})", state.Mobile.Name);
}
}
}

View file

@ -8,7 +8,7 @@ namespace Server.Misc
public static void Initialize()
{
IncomingPackets.Register(0xF0, 0, false, DecodeBundledPacket);
// IncomingPackets.Register(0xF0, 0, false, DecodeBundledPacket);
}
public static void Register(int packetID, bool ingame, OnPacketReceive onReceive)

View file

@ -221,7 +221,7 @@ namespace Server.Mobiles
{
if (to.Alive && to.Player && !UnderCacophonicAttack(to))
{
to.Send(SpeedControl.WalkSpeed);
to.NetState.SendSpeedControl(SpeedControlSetting.Walk);
to.SendLocalizedMessage(1072069); // A cacophonic sound lambastes you, suppressing your ability to move.
to.PlaySound(0x584);
@ -233,7 +233,7 @@ namespace Server.Mobiles
public virtual void CacophonicEnd(Mobile from)
{
m_Table.Remove(from);
from.Send(SpeedControl.Disable);
from.NetState.SendSpeedControl(SpeedControlSetting.Disable);
}
public static bool UnderCacophonicAttack(Mobile from) => m_Table.Contains(from);

View file

@ -34,6 +34,7 @@ using Server.Spells.Spellweaving;
using Server.Targeting;
using Server.Utilities;
using BaseQuestGump = Server.Engines.MLQuests.Gumps.BaseQuestGump;
using CalcMoves = Server.Movement.Movement;
using QuestOfferGump = Server.Engines.MLQuests.Gumps.QuestOfferGump;
using RankDefinition = Server.Guilds.RankDefinition;
@ -96,9 +97,6 @@ namespace Server.Mobiles
{
private static bool m_NoRecursion;
private static readonly bool FastwalkPrevention = true; // Is fastwalk prevention enabled?
private static readonly int FastwalkThreshold = 400; // Fastwalk prevention will become active after 0.4 seconds
private static readonly Point3D[] m_TrammelDeathDestinations =
{
new Point3D(1481, 1612, 20),
@ -166,7 +164,6 @@ namespace Server.Mobiles
private RankDefinition m_GuildRank;
private int m_HairModID = -1, m_HairModHue;
private bool m_HasMoved;
public DateTime m_hontime;
@ -188,7 +185,7 @@ namespace Server.Mobiles
private DateTime m_NextJustAward;
private long m_NextMovementTime;
private int m_NextMovementTime;
private int m_NextProtectionCheck = 10;
private DateTime m_NextSmithBulkOrder;
private DateTime m_NextTailorBulkOrder;
@ -715,8 +712,6 @@ namespace Server.Mobiles
[CommandProperty(AccessLevel.GameMaster)]
public SolenFriendship SolenFriendship { get; set; }
public virtual bool UsesFastwalkPrevention => AccessLevel < AccessLevel.Counselor;
public Type EnemyOfOneType
{
get => m_EnemyOfOneType;
@ -1031,11 +1026,6 @@ namespace Server.Mobiles
public static void Initialize()
{
if (FastwalkPrevention)
{
IncomingPackets.RegisterThrottler(0x02, MovementThrottle_Callback);
}
EventSink.Login += OnLogin;
EventSink.Logout += OnLogout;
EventSink.Connected += EventSink_Connected;
@ -4264,59 +4254,30 @@ namespace Server.Mobiles
SentHonorContext?.Cancel();
}
public override int ComputeMovementSpeed(Direction dir, bool checkTurning)
public override int ComputeMovementSpeed(Direction dir, bool checkTurning = true)
{
if (checkTurning && (dir & Direction.Mask) != (Direction & Direction.Mask))
{
return RunMount; // We are NOT actually moving (just a direction change)
return CalcMoves.RunMountDelay; // We are NOT actually moving (just a direction change)
}
var context = TransformationSpellHelper.GetContext(this);
if (context?.Type == typeof(ReaperFormSpell))
{
return WalkFoot;
return CalcMoves.WalkFootDelay;
}
var running = (dir & Direction.Running) != 0;
var onHorse = Mount != null;
var animalContext = AnimalForm.GetContext(this);
if (onHorse || animalContext?.SpeedBoost == true)
if (onHorse || AnimalForm.GetContext(this)?.SpeedBoost == true)
{
return running ? RunMount : WalkMount;
return running ? CalcMoves.RunMountDelay : CalcMoves.WalkMountDelay;
}
return running ? RunFoot : WalkFoot;
}
public static TimeSpan MovementThrottle_Callback(NetState ns)
{
if (!(ns.Mobile is PlayerMobile pm) || !pm.UsesFastwalkPrevention)
{
return TimeSpan.Zero;
}
if (!pm.m_HasMoved)
{
// has not yet moved
pm.m_NextMovementTime = Core.TickCount;
pm.m_HasMoved = true;
return TimeSpan.Zero;
}
var ts = pm.m_NextMovementTime - Core.TickCount;
if (ts < 0)
{
// been a while since we've last moved
pm.m_NextMovementTime = Core.TickCount;
return TimeSpan.Zero;
}
return ts < FastwalkThreshold ? TimeSpan.Zero : TimeSpan.FromTicks(ts);
return running ? CalcMoves.RunFootDelay : CalcMoves.WalkFootDelay;
}
private void DeltaEnemies(Type oldType, Type newType)

View file

@ -23,7 +23,7 @@ namespace Server.Regions
if (ns != null && !TransformationSpellHelper.UnderTransformation(m, typeof(AnimalForm)) &&
m.AccessLevel == AccessLevel.Player)
{
ns.Send(SpeedControl.WalkSpeed);
ns.SendSpeedControl(SpeedControlSetting.Walk);
}
}
@ -32,7 +32,7 @@ namespace Server.Regions
var ns = m.NetState;
if (ns != null && !TransformationSpellHelper.UnderTransformation(m, typeof(AnimalForm)))
{
ns.Send(SpeedControl.Disable);
ns.SendSpeedControl(SpeedControlSetting.Disable);
}
}
@ -40,7 +40,7 @@ namespace Server.Regions
{
if (m.Region.IsPartOf<TwistedWealdDesertRegion>() && m.AccessLevel == AccessLevel.Player)
{
m.NetState.Send(SpeedControl.WalkSpeed);
m.NetState.SendSpeedControl(SpeedControlSetting.Walk);
}
}
}

View file

@ -72,7 +72,7 @@ namespace Server.Spells.Ninjitsu
{
if (GetContext(m)?.SpeedBoost == true)
{
m.Send(SpeedControl.MountSpeed);
m.NetState.SendSpeedControl(SpeedControlSetting.Mount);
}
}
@ -243,7 +243,7 @@ namespace Server.Spells.Ninjitsu
if (entry.SpeedBoost)
{
m.Send(SpeedControl.MountSpeed);
m.NetState.SendSpeedControl(SpeedControlSetting.Mount);
}
SkillMod mod = null;
@ -296,7 +296,7 @@ namespace Server.Spells.Ninjitsu
if (context.SpeedBoost)
{
m.Send(SpeedControl.Disable);
m.NetState.SendSpeedControl(SpeedControlSetting.Disable);
}
var mod = context.Mod;

View file

@ -38,7 +38,7 @@ namespace Server.Spells.Spellweaving
if (context?.Type == typeof(ReaperFormSpell))
{
m.Send(SpeedControl.WalkSpeed);
m.NetState.SendSpeedControl(SpeedControlSetting.Walk);
}
}
@ -46,12 +46,12 @@ namespace Server.Spells.Spellweaving
{
m.PlaySound(0x1BA);
m.Send(SpeedControl.WalkSpeed);
m.NetState.SendSpeedControl(SpeedControlSetting.Walk);
}
public override void RemoveEffect(Mobile m)
{
m.Send(SpeedControl.Disable);
m.NetState.SendSpeedControl(SpeedControlSetting.Disable);
}
}
}