Lag compensation for melee (#11885)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Movement.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Track lag compensation components that may need to have their data culled for memory reasons.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ActiveLagCompensationComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Movement.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class LagCompensationComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public readonly Queue<ValueTuple<TimeSpan, EntityCoordinates, Angle>> Positions = new();
|
||||
}
|
||||
113
Content.Server/Movement/Systems/LagCompensationSystem.cs
Normal file
113
Content.Server/Movement/Systems/LagCompensationSystem.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using Content.Server.Movement.Components;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Movement.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Stores a buffer of previous positions of the relevant entity.
|
||||
/// Can be used to check the entity's position at a recent point in time.
|
||||
/// </summary>
|
||||
public sealed class LagCompensationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
// I figured 500 ping is max, so 1.5 is 750.
|
||||
// Max ping I've had is 350ms from aus to spain.
|
||||
public static readonly TimeSpan BufferTime = TimeSpan.FromMilliseconds(750);
|
||||
|
||||
private ISawmill _sawmill = Logger.GetSawmill("lagcomp");
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_sawmill.Level = LogLevel.Info;
|
||||
SubscribeLocalEvent<LagCompensationComponent, MoveEvent>(OnLagMove);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
var earliestTime = curTime - BufferTime;
|
||||
|
||||
// Cull any old ones from active updates
|
||||
// Probably fine to include ignored.
|
||||
foreach (var (_, comp) in EntityQuery<ActiveLagCompensationComponent, LagCompensationComponent>(true))
|
||||
{
|
||||
while (comp.Positions.TryPeek(out var pos))
|
||||
{
|
||||
if (pos.Item1 < earliestTime)
|
||||
{
|
||||
comp.Positions.Dequeue();
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (comp.Positions.Count == 0)
|
||||
{
|
||||
RemComp<ActiveLagCompensationComponent>(comp.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLagMove(EntityUid uid, LagCompensationComponent component, ref MoveEvent args)
|
||||
{
|
||||
EnsureComp<ActiveLagCompensationComponent>(uid);
|
||||
component.Positions.Enqueue((_timing.CurTime, args.NewPosition, args.NewRotation));
|
||||
}
|
||||
|
||||
public (EntityCoordinates Coordinates, Angle Angle) GetCoordinatesAngle(EntityUid uid, IPlayerSession pSession,
|
||||
TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref xform))
|
||||
return (EntityCoordinates.Invalid, Angle.Zero);
|
||||
|
||||
if (!TryComp<LagCompensationComponent>(uid, out var lag) || lag.Positions.Count == 0)
|
||||
return (xform.Coordinates, xform.LocalRotation);
|
||||
|
||||
var angle = Angle.Zero;
|
||||
var coordinates = EntityCoordinates.Invalid;
|
||||
var ping = pSession.Ping;
|
||||
// Use 1.5 due to the trip buffer.
|
||||
var sentTime = _timing.CurTime - TimeSpan.FromMilliseconds(ping * 1.5);
|
||||
|
||||
foreach (var pos in lag.Positions)
|
||||
{
|
||||
coordinates = pos.Item2;
|
||||
angle = pos.Item3;
|
||||
|
||||
if (pos.Item1 >= sentTime)
|
||||
break;
|
||||
}
|
||||
|
||||
if (coordinates == default)
|
||||
{
|
||||
_sawmill.Debug($"No long comp coords found, using {xform.Coordinates}");
|
||||
coordinates = xform.Coordinates;
|
||||
angle = xform.LocalRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sawmill.Debug($"Actual coords is {xform.Coordinates} and got {coordinates}");
|
||||
}
|
||||
|
||||
return (coordinates, angle);
|
||||
}
|
||||
|
||||
public Angle GetAngle(EntityUid uid, IPlayerSession pSession, TransformComponent? xform = null)
|
||||
{
|
||||
var (_, angle) = GetCoordinatesAngle(uid, pSession, xform);
|
||||
return angle;
|
||||
}
|
||||
|
||||
public EntityCoordinates GetCoordinates(EntityUid uid, IPlayerSession pSession, TransformComponent? xform = null)
|
||||
{
|
||||
var (coordinates, _) = GetCoordinatesAngle(uid, pSession, xform);
|
||||
return coordinates;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Content.Server.Contests;
|
||||
using Content.Server.Damage.Systems;
|
||||
using Content.Server.Examine;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Movement.Systems;
|
||||
using Content.Server.Weapons.Melee.Components;
|
||||
using Content.Server.Weapons.Melee.Events;
|
||||
using Content.Shared.CombatMode;
|
||||
@@ -24,11 +25,13 @@ using Content.Shared.Physics;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -43,6 +46,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
[Dependency] private readonly ContestsSystem _contests = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly ExamineSystem _examine = default!;
|
||||
[Dependency] private readonly LagCompensationSystem _lag = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutions = default!;
|
||||
@@ -106,9 +110,9 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
PopupSystem.PopupEntity(message, uid.Value, Filter.Pvs(uid.Value, entityManager: EntityManager).RemoveWhereAttachedEntity(e => e == user));
|
||||
}
|
||||
|
||||
protected override void DoLightAttack(EntityUid user, LightAttackEvent ev, MeleeWeaponComponent component)
|
||||
protected override void DoLightAttack(EntityUid user, LightAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
|
||||
{
|
||||
base.DoLightAttack(user, ev, component);
|
||||
base.DoLightAttack(user, ev, component, session);
|
||||
|
||||
// Can't attack yourself
|
||||
// Not in LOS.
|
||||
@@ -122,7 +126,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(user, ev.Target.Value, component.Range))
|
||||
if (!InRange(user, ev.Target.Value, component.Range, session))
|
||||
return;
|
||||
|
||||
var damage = component.Damage * GetModifier(component, true);
|
||||
@@ -191,9 +195,9 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, MeleeWeaponComponent component)
|
||||
protected override void DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
|
||||
{
|
||||
base.DoHeavyAttack(user, ev, component);
|
||||
base.DoHeavyAttack(user, ev, component, session);
|
||||
|
||||
// TODO: This is copy-paste as fuck with DoPreciseAttack
|
||||
if (!TryComp<TransformComponent>(user, out var userXform))
|
||||
@@ -303,9 +307,9 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, MeleeWeaponComponent component)
|
||||
protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
|
||||
{
|
||||
if (!base.DoDisarm(user, ev, component))
|
||||
if (!base.DoDisarm(user, ev, component, session))
|
||||
return false;
|
||||
|
||||
if (!TryComp<CombatModeComponent>(user, out var combatMode) ||
|
||||
@@ -322,7 +326,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(user, ev.Target.Value, component.Range + 0.1f))
|
||||
if (!InRange(user, ev.Target.Value, component.Range, session))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -377,6 +381,25 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool InRange(EntityUid user, EntityUid target, float range, ICommonSession? session)
|
||||
{
|
||||
EntityCoordinates targetCoordinates;
|
||||
Angle targetLocalAngle;
|
||||
|
||||
if (session is IPlayerSession pSession)
|
||||
{
|
||||
(targetCoordinates, targetLocalAngle) = _lag.GetCoordinatesAngle(target, pSession);
|
||||
}
|
||||
else
|
||||
{
|
||||
var xform = Transform(target);
|
||||
targetCoordinates = xform.Coordinates;
|
||||
targetLocalAngle = xform.LocalRotation;
|
||||
}
|
||||
|
||||
return _interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range);
|
||||
}
|
||||
|
||||
private float CalculateDisarmChance(EntityUid disarmer, EntityUid disarmed, EntityUid? inTargetHand, SharedCombatModeComponent disarmerComp)
|
||||
{
|
||||
if (HasComp<DisarmProneComponent>(disarmer))
|
||||
|
||||
Reference in New Issue
Block a user