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

@@ -308,7 +308,6 @@ namespace Content.Client.NPC
break; break;
*/ */
} }
} }
} }

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@
id: BaseMobOrganic id: BaseMobOrganic
noSpawn: true noSpawn: true
components: components:
- type: LagCompensation
- type: RangedDamageSound - type: RangedDamageSound
soundGroups: soundGroups:
Brute: Brute: