Lag compensation for melee (#11885)

Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
metalgearsloth
2022-10-17 15:54:31 +11:00
committed by GitHub
parent ece9bf372a
commit 123a9dbf02
12 changed files with 242 additions and 56 deletions

View File

@@ -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
{
}

View File

@@ -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();
}

View 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;
}
}

View File

@@ -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))