Lag compensation for melee (#11885)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -308,7 +308,6 @@ namespace Content.Client.NPC
|
|||||||
break;
|
break;
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using Robust.Shared.Animations;
|
|||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
@@ -205,9 +206,9 @@ public sealed partial 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;
|
return false;
|
||||||
|
|
||||||
if (!TryComp<CombatModeComponent>(user, out var combatMode) ||
|
if (!TryComp<CombatModeComponent>(user, out var combatMode) ||
|
||||||
|
|||||||
@@ -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.Damage.Systems;
|
||||||
using Content.Server.Examine;
|
using Content.Server.Examine;
|
||||||
using Content.Server.Hands.Components;
|
using Content.Server.Hands.Components;
|
||||||
|
using Content.Server.Movement.Systems;
|
||||||
using Content.Server.Weapons.Melee.Components;
|
using Content.Server.Weapons.Melee.Components;
|
||||||
using Content.Server.Weapons.Melee.Events;
|
using Content.Server.Weapons.Melee.Events;
|
||||||
using Content.Shared.CombatMode;
|
using Content.Shared.CombatMode;
|
||||||
@@ -24,11 +25,13 @@ using Content.Shared.Physics;
|
|||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Content.Shared.Weapons.Melee;
|
using Content.Shared.Weapons.Melee;
|
||||||
using Content.Shared.Weapons.Melee.Events;
|
using Content.Shared.Weapons.Melee.Events;
|
||||||
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Systems;
|
using Robust.Shared.Physics.Systems;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
@@ -43,6 +46,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||||||
[Dependency] private readonly ContestsSystem _contests = default!;
|
[Dependency] private readonly ContestsSystem _contests = default!;
|
||||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||||
[Dependency] private readonly ExamineSystem _examine = default!;
|
[Dependency] private readonly ExamineSystem _examine = default!;
|
||||||
|
[Dependency] private readonly LagCompensationSystem _lag = default!;
|
||||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
[Dependency] private readonly SolutionContainerSystem _solutions = 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));
|
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
|
// Can't attack yourself
|
||||||
// Not in LOS.
|
// Not in LOS.
|
||||||
@@ -122,7 +126,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_interaction.InRangeUnobstructed(user, ev.Target.Value, component.Range))
|
if (!InRange(user, ev.Target.Value, component.Range, session))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var damage = component.Damage * GetModifier(component, true);
|
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
|
// TODO: This is copy-paste as fuck with DoPreciseAttack
|
||||||
if (!TryComp<TransformComponent>(user, out var userXform))
|
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;
|
return false;
|
||||||
|
|
||||||
if (!TryComp<CombatModeComponent>(user, out var combatMode) ||
|
if (!TryComp<CombatModeComponent>(user, out var combatMode) ||
|
||||||
@@ -322,7 +326,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_interaction.InRangeUnobstructed(user, ev.Target.Value, component.Range + 0.1f))
|
if (!InRange(user, ev.Target.Value, component.Range, session))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -377,6 +381,25 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||||||
return true;
|
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)
|
private float CalculateDisarmChance(EntityUid disarmer, EntityUid disarmed, EntityUid? inTargetHand, SharedCombatModeComponent disarmerComp)
|
||||||
{
|
{
|
||||||
if (HasComp<DisarmProneComponent>(disarmer))
|
if (HasComp<DisarmProneComponent>(disarmer))
|
||||||
|
|||||||
@@ -40,16 +40,17 @@ namespace Content.Shared.Interaction
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public abstract class SharedInteractionSystem : EntitySystem
|
public abstract class SharedInteractionSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
|
||||||
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
|
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||||
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
|
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||||
|
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
|
||||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||||
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
|
|
||||||
|
|
||||||
private const CollisionGroup InRangeUnobstructedMask
|
private const CollisionGroup InRangeUnobstructedMask
|
||||||
= CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
|
= CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
|
||||||
@@ -88,13 +89,13 @@ namespace Content.Shared.Interaction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
|
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
|
||||||
{
|
{
|
||||||
if (ev.Sender.AttachedEntity is not EntityUid user || !_actionBlockerSystem.CanInteract(user, ev.Target))
|
if (ev.Sender.AttachedEntity is not { } user || !_actionBlockerSystem.CanInteract(user, ev.Target))
|
||||||
{
|
{
|
||||||
ev.Cancel();
|
ev.Cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ContainerSystem.IsInSameOrParentContainer(user, ev.Target) && !CanAccessViaStorage(user, ev.Target))
|
if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target) && !CanAccessViaStorage(user, ev.Target))
|
||||||
{
|
{
|
||||||
ev.Cancel();
|
ev.Cancel();
|
||||||
return;
|
return;
|
||||||
@@ -103,7 +104,6 @@ namespace Content.Shared.Interaction
|
|||||||
if (!InRangeUnobstructed(user, ev.Target))
|
if (!InRangeUnobstructed(user, ev.Target))
|
||||||
{
|
{
|
||||||
ev.Cancel();
|
ev.Cancel();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ namespace Content.Shared.Interaction
|
|||||||
// Also checks if the item is accessible via some storage UI (e.g., open backpack)
|
// Also checks if the item is accessible via some storage UI (e.g., open backpack)
|
||||||
if (checkAccess
|
if (checkAccess
|
||||||
&& target != null
|
&& target != null
|
||||||
&& !ContainerSystem.IsInSameOrParentContainer(user, target.Value)
|
&& !_containerSystem.IsInSameOrParentContainer(user, target.Value)
|
||||||
&& !CanAccessViaStorage(user, target.Value))
|
&& !CanAccessViaStorage(user, target.Value))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ namespace Content.Shared.Interaction
|
|||||||
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
|
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
|
||||||
|
|
||||||
// empty-hand interactions
|
// empty-hand interactions
|
||||||
if (hands.ActiveHandEntity is not EntityUid held)
|
if (hands.ActiveHandEntity is not { } held)
|
||||||
{
|
{
|
||||||
if (inRangeUnobstructed && target != null)
|
if (inRangeUnobstructed && target != null)
|
||||||
InteractHand(user, target.Value);
|
InteractHand(user, target.Value);
|
||||||
@@ -346,13 +346,16 @@ namespace Content.Shared.Interaction
|
|||||||
{
|
{
|
||||||
var dir = other.Position - origin.Position;
|
var dir = other.Position - origin.Position;
|
||||||
|
|
||||||
if (dir.LengthSquared.Equals(0f)) return 0f;
|
if (dir.LengthSquared.Equals(0f))
|
||||||
|
return 0f;
|
||||||
|
|
||||||
predicate ??= _ => false;
|
predicate ??= _ => false;
|
||||||
var ray = new CollisionRay(origin.Position, dir.Normalized, collisionMask);
|
var ray = new CollisionRay(origin.Position, dir.Normalized, collisionMask);
|
||||||
var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList();
|
var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList();
|
||||||
|
|
||||||
if (rayResults.Count == 0) return dir.Length;
|
if (rayResults.Count == 0)
|
||||||
|
return dir.Length;
|
||||||
|
|
||||||
return (rayResults[0].HitPos - origin.Position).Length;
|
return (rayResults[0].HitPos - origin.Position).Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,15 +387,18 @@ namespace Content.Shared.Interaction
|
|||||||
Ignored? predicate = null)
|
Ignored? predicate = null)
|
||||||
{
|
{
|
||||||
// Have to be on same map regardless.
|
// Have to be on same map regardless.
|
||||||
if (other.MapId != origin.MapId) return false;
|
if (other.MapId != origin.MapId)
|
||||||
|
return false;
|
||||||
|
|
||||||
var dir = other.Position - origin.Position;
|
var dir = other.Position - origin.Position;
|
||||||
var length = dir.Length;
|
var length = dir.Length;
|
||||||
|
|
||||||
// If range specified also check it
|
// If range specified also check it
|
||||||
if (range > 0f && length > range) return false;
|
if (range > 0f && length > range)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (MathHelper.CloseTo(length, 0)) return true;
|
if (MathHelper.CloseTo(length, 0))
|
||||||
|
return true;
|
||||||
|
|
||||||
predicate ??= _ => false;
|
predicate ??= _ => false;
|
||||||
|
|
||||||
@@ -408,6 +414,21 @@ namespace Content.Shared.Interaction
|
|||||||
return rayResults.Count == 0;
|
return rayResults.Count == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool InRangeUnobstructed(
|
||||||
|
EntityUid origin,
|
||||||
|
EntityUid other,
|
||||||
|
float range = InteractionRange,
|
||||||
|
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
||||||
|
Ignored? predicate = null,
|
||||||
|
bool popup = false)
|
||||||
|
{
|
||||||
|
if (!TryComp<TransformComponent>(other, out var otherXform))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate,
|
||||||
|
popup);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks that two entities are within a certain distance without any
|
/// Checks that two entities are within a certain distance without any
|
||||||
/// entity that matches the collision mask obstructing them.
|
/// entity that matches the collision mask obstructing them.
|
||||||
@@ -418,6 +439,7 @@ namespace Content.Shared.Interaction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="origin">The first entity to use.</param>
|
/// <param name="origin">The first entity to use.</param>
|
||||||
/// <param name="other">Other entity to use.</param>
|
/// <param name="other">Other entity to use.</param>
|
||||||
|
/// <param name="otherAngle">The local rotation to use for the other entity.</param>
|
||||||
/// <param name="range">
|
/// <param name="range">
|
||||||
/// Maximum distance between the two entities.
|
/// Maximum distance between the two entities.
|
||||||
/// </param>
|
/// </param>
|
||||||
@@ -430,12 +452,15 @@ namespace Content.Shared.Interaction
|
|||||||
/// Whether or not to popup a feedback message on the origin entity for
|
/// Whether or not to popup a feedback message on the origin entity for
|
||||||
/// it to see.
|
/// it to see.
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <param name="otherCoordinates">The coordinates to use for the other entity.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// True if the two points are within a given range without being obstructed.
|
/// True if the two points are within a given range without being obstructed.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public bool InRangeUnobstructed(
|
public bool InRangeUnobstructed(
|
||||||
EntityUid origin,
|
EntityUid origin,
|
||||||
EntityUid other,
|
EntityUid other,
|
||||||
|
EntityCoordinates otherCoordinates,
|
||||||
|
Angle otherAngle,
|
||||||
float range = InteractionRange,
|
float range = InteractionRange,
|
||||||
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
||||||
Ignored? predicate = null,
|
Ignored? predicate = null,
|
||||||
@@ -444,9 +469,13 @@ namespace Content.Shared.Interaction
|
|||||||
Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
|
Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
|
||||||
var inRange = true;
|
var inRange = true;
|
||||||
MapCoordinates originPos = default;
|
MapCoordinates originPos = default;
|
||||||
MapCoordinates targetPos = default;
|
var targetPos = otherCoordinates.ToMap(EntityManager);
|
||||||
Angle targetRot = default;
|
Angle targetRot = default;
|
||||||
|
|
||||||
|
// So essentially:
|
||||||
|
// 1. If fixtures available check nearest point. We take in coordinates / angles because we might want to use a lag compensated position
|
||||||
|
// 2. Fall back to centre of body.
|
||||||
|
|
||||||
// Alternatively we could check centre distances first though
|
// Alternatively we could check centre distances first though
|
||||||
// that means we wouldn't be able to easily check overlap interactions.
|
// that means we wouldn't be able to easily check overlap interactions.
|
||||||
if (range > 0f &&
|
if (range > 0f &&
|
||||||
@@ -456,13 +485,17 @@ namespace Content.Shared.Interaction
|
|||||||
fixtureA.FixtureCount > 0 &&
|
fixtureA.FixtureCount > 0 &&
|
||||||
TryComp<FixturesComponent>(other, out var fixtureB) &&
|
TryComp<FixturesComponent>(other, out var fixtureB) &&
|
||||||
fixtureB.FixtureCount > 0 &&
|
fixtureB.FixtureCount > 0 &&
|
||||||
TryComp<TransformComponent>(origin, out var xformA) &&
|
TryComp<TransformComponent>(origin, out var xformA))
|
||||||
TryComp<TransformComponent>(other, out var xformB))
|
|
||||||
{
|
{
|
||||||
|
var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation();
|
||||||
|
var xfA = new Robust.Shared.Physics.Transform(worldPosA, worldRotA);
|
||||||
|
var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId);
|
||||||
|
var xfB = new Robust.Shared.Physics.Transform(targetPos.Position, parentRotB + otherAngle);
|
||||||
|
|
||||||
// Different map or the likes.
|
// Different map or the likes.
|
||||||
if (!_sharedBroadphaseSystem.TryGetNearest(origin, other,
|
if (!_sharedBroadphaseSystem.TryGetNearest(origin, other,
|
||||||
out var pointA, out var pointB, out var distance,
|
out _, out _, out var distance,
|
||||||
xformA, xformB, fixtureA, fixtureB))
|
xfA, xfB, fixtureA, fixtureB))
|
||||||
{
|
{
|
||||||
inRange = false;
|
inRange = false;
|
||||||
}
|
}
|
||||||
@@ -480,19 +513,12 @@ namespace Content.Shared.Interaction
|
|||||||
{
|
{
|
||||||
// We'll still do the raycast from the centres but we'll bump the range as we know they're in range.
|
// We'll still do the raycast from the centres but we'll bump the range as we know they're in range.
|
||||||
originPos = xformA.MapPosition;
|
originPos = xformA.MapPosition;
|
||||||
(var targetWorld, targetRot) = xformB.GetWorldPositionRotation();
|
range = (originPos.Position - targetPos.Position).Length;
|
||||||
targetPos = new MapCoordinates(targetWorld, xformB.MapID);
|
|
||||||
|
|
||||||
range = (originPos.Position - targetWorld).Length;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
originPos = Transform(origin).MapPosition;
|
originPos = Transform(origin).MapPosition;
|
||||||
|
|
||||||
xformB = Transform(other);
|
|
||||||
(var targetWorld, targetRot) = xformB.GetWorldPositionRotation();
|
|
||||||
targetPos = new MapCoordinates(targetWorld, xformB.MapID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do a raycast to check if relevant
|
// Do a raycast to check if relevant
|
||||||
@@ -542,8 +568,6 @@ namespace Content.Shared.Interaction
|
|||||||
{
|
{
|
||||||
HashSet<EntityUid> ignored = new();
|
HashSet<EntityUid> ignored = new();
|
||||||
|
|
||||||
bool ignoreAnchored = false;
|
|
||||||
|
|
||||||
if (HasComp<ItemComponent>(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide)
|
if (HasComp<ItemComponent>(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide)
|
||||||
{
|
{
|
||||||
// If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck
|
// If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck
|
||||||
@@ -554,6 +578,7 @@ namespace Content.Shared.Interaction
|
|||||||
{
|
{
|
||||||
// wall-mount exemptions may be restricted to a specific angle range.da
|
// wall-mount exemptions may be restricted to a specific angle range.da
|
||||||
|
|
||||||
|
bool ignoreAnchored;
|
||||||
if (wallMount.Arc >= Math.Tau)
|
if (wallMount.Arc >= Math.Tau)
|
||||||
ignoreAnchored = true;
|
ignoreAnchored = true;
|
||||||
else
|
else
|
||||||
@@ -665,7 +690,7 @@ namespace Content.Shared.Interaction
|
|||||||
bool canReach)
|
bool canReach)
|
||||||
{
|
{
|
||||||
var ev = new BeforeRangedInteractEvent(user, used, target, clickLocation, canReach);
|
var ev = new BeforeRangedInteractEvent(user, used, target, clickLocation, canReach);
|
||||||
RaiseLocalEvent(used, ev, false);
|
RaiseLocalEvent(used, ev);
|
||||||
return ev.Handled;
|
return ev.Handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,7 +742,7 @@ namespace Content.Shared.Interaction
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach);
|
var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach);
|
||||||
RaiseLocalEvent(target.Value, afterInteractUsingEvent, false);
|
RaiseLocalEvent(target.Value, afterInteractUsingEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region ActivateItemInWorld
|
#region ActivateItemInWorld
|
||||||
@@ -764,7 +789,7 @@ namespace Content.Shared.Interaction
|
|||||||
|
|
||||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||||
// This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
|
// This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
|
||||||
if (checkAccess && !ContainerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used))
|
if (checkAccess && !_containerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Does the user have hands?
|
// Does the user have hands?
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Content.Shared.Weapons.Melee.Events;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||||||
if (weapon?.Owner != msg.Weapon)
|
if (weapon?.Owner != msg.Weapon)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AttemptAttack(args.SenderSession.AttachedEntity!.Value, weapon, msg);
|
AttemptAttack(args.SenderSession.AttachedEntity!.Value, weapon, msg, args.SenderSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStopHeavyAttack(StopHeavyAttackEvent msg, EntitySessionEventArgs args)
|
private void OnStopHeavyAttack(StopHeavyAttackEvent msg, EntitySessionEventArgs args)
|
||||||
@@ -147,7 +148,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||||||
if (userWeapon != weapon)
|
if (userWeapon != weapon)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AttemptAttack(args.SenderSession.AttachedEntity.Value, weapon, msg);
|
AttemptAttack(args.SenderSession.AttachedEntity.Value, weapon, msg, args.SenderSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args)
|
private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args)
|
||||||
@@ -162,7 +163,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||||||
if (userWeapon == null)
|
if (userWeapon == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AttemptAttack(args.SenderSession.AttachedEntity.Value, userWeapon, msg);
|
AttemptAttack(args.SenderSession.AttachedEntity.Value, userWeapon, msg, args.SenderSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGetState(EntityUid uid, MeleeWeaponComponent component, ref ComponentGetState args)
|
private void OnGetState(EntityUid uid, MeleeWeaponComponent component, ref ComponentGetState args)
|
||||||
@@ -219,7 +220,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||||||
if (!TryComp<TransformComponent>(target, out var targetXform))
|
if (!TryComp<TransformComponent>(target, out var targetXform))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AttemptAttack(user, weapon, new LightAttackEvent(target, weapon.Owner, targetXform.Coordinates));
|
AttemptAttack(user, weapon, new LightAttackEvent(target, weapon.Owner, targetXform.Coordinates), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AttemptDisarmAttack(EntityUid user, MeleeWeaponComponent weapon, EntityUid target)
|
public void AttemptDisarmAttack(EntityUid user, MeleeWeaponComponent weapon, EntityUid target)
|
||||||
@@ -227,13 +228,13 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||||||
if (!TryComp<TransformComponent>(target, out var targetXform))
|
if (!TryComp<TransformComponent>(target, out var targetXform))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AttemptAttack(user, weapon, new DisarmAttackEvent(target, targetXform.Coordinates));
|
AttemptAttack(user, weapon, new DisarmAttackEvent(target, targetXform.Coordinates), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when a windup is finished and an attack is tried.
|
/// Called when a windup is finished and an attack is tried.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void AttemptAttack(EntityUid user, MeleeWeaponComponent weapon, AttackEvent attack)
|
private void AttemptAttack(EntityUid user, MeleeWeaponComponent weapon, AttackEvent attack, ICommonSession? session)
|
||||||
{
|
{
|
||||||
var curTime = Timing.CurTime;
|
var curTime = Timing.CurTime;
|
||||||
|
|
||||||
@@ -258,15 +259,15 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||||||
switch (attack)
|
switch (attack)
|
||||||
{
|
{
|
||||||
case LightAttackEvent light:
|
case LightAttackEvent light:
|
||||||
DoLightAttack(user, light, weapon);
|
DoLightAttack(user, light, weapon, session);
|
||||||
break;
|
break;
|
||||||
case DisarmAttackEvent disarm:
|
case DisarmAttackEvent disarm:
|
||||||
if (!DoDisarm(user, disarm, weapon))
|
if (!DoDisarm(user, disarm, weapon, session))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case HeavyAttackEvent heavy:
|
case HeavyAttackEvent heavy:
|
||||||
DoHeavyAttack(user, heavy, weapon);
|
DoHeavyAttack(user, heavy, weapon, session);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
@@ -314,17 +315,17 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
|||||||
return (float) fraction * component.HeavyDamageModifier.Float();
|
return (float) fraction * component.HeavyDamageModifier.Float();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, MeleeWeaponComponent component)
|
protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, MeleeWeaponComponent component)
|
protected virtual void DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual bool DoDisarm(EntityUid user, DisarmAttackEvent ev, MeleeWeaponComponent component)
|
protected virtual bool DoDisarm(EntityUid user, DisarmAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
|
||||||
{
|
{
|
||||||
if (Deleted(ev.Target) ||
|
if (Deleted(ev.Target) ||
|
||||||
user == ev.Target)
|
user == ev.Target)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
abstract: true
|
abstract: true
|
||||||
id: MobSiliconBase
|
id: MobSiliconBase
|
||||||
components:
|
components:
|
||||||
|
- type: LagCompensation
|
||||||
- type: Reactive
|
- type: Reactive
|
||||||
groups:
|
groups:
|
||||||
Acidic: [Touch]
|
Acidic: [Touch]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
id: SimpleSpaceMobBase # Mob without barotrauma, freezing and asphyxiation (for space carps!?)
|
id: SimpleSpaceMobBase # Mob without barotrauma, freezing and asphyxiation (for space carps!?)
|
||||||
suffix: AI
|
suffix: AI
|
||||||
components:
|
components:
|
||||||
|
- type: LagCompensation
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
- DoorBumpOpener
|
- DoorBumpOpener
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
description: guardian
|
description: guardian
|
||||||
save: false
|
save: false
|
||||||
components:
|
components:
|
||||||
|
- type: LagCompensation
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
allowMovement: true
|
allowMovement: true
|
||||||
allowSpeech: true
|
allowSpeech: true
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
id: BaseMobOrganic
|
id: BaseMobOrganic
|
||||||
noSpawn: true
|
noSpawn: true
|
||||||
components:
|
components:
|
||||||
|
- type: LagCompensation
|
||||||
- type: RangedDamageSound
|
- type: RangedDamageSound
|
||||||
soundGroups:
|
soundGroups:
|
||||||
Brute:
|
Brute:
|
||||||
|
|||||||
Reference in New Issue
Block a user