diff --git a/Content.Client/NPC/PathfindingSystem.cs b/Content.Client/NPC/PathfindingSystem.cs index 3d5fdf85cc..1495f20044 100644 --- a/Content.Client/NPC/PathfindingSystem.cs +++ b/Content.Client/NPC/PathfindingSystem.cs @@ -308,7 +308,6 @@ namespace Content.Client.NPC break; */ } - } } diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index dc5a9c6501..8be14b9df7 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -16,6 +16,7 @@ using Robust.Shared.Animations; using Robust.Shared.Input; using Robust.Shared.Map; using Robust.Shared.Player; +using Robust.Shared.Players; using Robust.Shared.Prototypes; 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; if (!TryComp(user, out var combatMode) || diff --git a/Content.Server/Movement/Components/ActiveLagCompensationComponent.cs b/Content.Server/Movement/Components/ActiveLagCompensationComponent.cs new file mode 100644 index 0000000000..ae202b483a --- /dev/null +++ b/Content.Server/Movement/Components/ActiveLagCompensationComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.Movement.Components; + +/// +/// Track lag compensation components that may need to have their data culled for memory reasons. +/// +[RegisterComponent] +public sealed class ActiveLagCompensationComponent : Component +{ + +} diff --git a/Content.Server/Movement/Components/LagCompensationComponent.cs b/Content.Server/Movement/Components/LagCompensationComponent.cs new file mode 100644 index 0000000000..8178a8f2af --- /dev/null +++ b/Content.Server/Movement/Components/LagCompensationComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Map; + +namespace Content.Server.Movement.Components; + +[RegisterComponent] +public sealed class LagCompensationComponent : Component +{ + [ViewVariables] + public readonly Queue> Positions = new(); +} diff --git a/Content.Server/Movement/Systems/LagCompensationSystem.cs b/Content.Server/Movement/Systems/LagCompensationSystem.cs new file mode 100644 index 0000000000..6ffd55c6b9 --- /dev/null +++ b/Content.Server/Movement/Systems/LagCompensationSystem.cs @@ -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; + +/// +/// 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. +/// +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(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(true)) + { + while (comp.Positions.TryPeek(out var pos)) + { + if (pos.Item1 < earliestTime) + { + comp.Positions.Dequeue(); + continue; + } + + break; + } + + if (comp.Positions.Count == 0) + { + RemComp(comp.Owner); + } + } + } + + private void OnLagMove(EntityUid uid, LagCompensationComponent component, ref MoveEvent args) + { + EnsureComp(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(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; + } +} diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 7cb349d28d..eeb2bd4947 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -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(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(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(disarmer)) diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 86c320a9b3..155ccd1613 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -40,16 +40,17 @@ namespace Content.Shared.Interaction [UsedImplicitly] public abstract class SharedInteractionSystem : EntitySystem { - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; - [Dependency] private readonly SharedVerbSystem _verbSystem = default!; + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = 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 UseDelaySystem _useDelay = default!; - [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; @@ -88,13 +89,13 @@ namespace Content.Shared.Interaction /// 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(); return; } - if (!ContainerSystem.IsInSameOrParentContainer(user, ev.Target) && !CanAccessViaStorage(user, ev.Target)) + if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target) && !CanAccessViaStorage(user, ev.Target)) { ev.Cancel(); return; @@ -103,7 +104,6 @@ namespace Content.Shared.Interaction if (!InRangeUnobstructed(user, ev.Target)) { 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) if (checkAccess && target != null - && !ContainerSystem.IsInSameOrParentContainer(user, target.Value) + && !_containerSystem.IsInSameOrParentContainer(user, target.Value) && !CanAccessViaStorage(user, target.Value)) return; @@ -242,7 +242,7 @@ namespace Content.Shared.Interaction : !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities // empty-hand interactions - if (hands.ActiveHandEntity is not EntityUid held) + if (hands.ActiveHandEntity is not { } held) { if (inRangeUnobstructed && target != null) InteractHand(user, target.Value); @@ -346,13 +346,16 @@ namespace Content.Shared.Interaction { var dir = other.Position - origin.Position; - if (dir.LengthSquared.Equals(0f)) return 0f; + if (dir.LengthSquared.Equals(0f)) + return 0f; predicate ??= _ => false; var ray = new CollisionRay(origin.Position, dir.Normalized, collisionMask); 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; } @@ -384,15 +387,18 @@ namespace Content.Shared.Interaction Ignored? predicate = null) { // 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 length = dir.Length; // 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; @@ -408,6 +414,21 @@ namespace Content.Shared.Interaction 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(other, out var otherXform)) + return false; + + return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate, + popup); + } + /// /// Checks that two entities are within a certain distance without any /// entity that matches the collision mask obstructing them. @@ -418,6 +439,7 @@ namespace Content.Shared.Interaction /// /// The first entity to use. /// Other entity to use. + /// The local rotation to use for the other entity. /// /// Maximum distance between the two entities. /// @@ -430,12 +452,15 @@ namespace Content.Shared.Interaction /// Whether or not to popup a feedback message on the origin entity for /// it to see. /// + /// The coordinates to use for the other entity. /// /// True if the two points are within a given range without being obstructed. /// public bool InRangeUnobstructed( EntityUid origin, EntityUid other, + EntityCoordinates otherCoordinates, + Angle otherAngle, float range = InteractionRange, CollisionGroup collisionMask = InRangeUnobstructedMask, Ignored? predicate = null, @@ -444,9 +469,13 @@ namespace Content.Shared.Interaction Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false); var inRange = true; MapCoordinates originPos = default; - MapCoordinates targetPos = default; + var targetPos = otherCoordinates.ToMap(EntityManager); 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 // that means we wouldn't be able to easily check overlap interactions. if (range > 0f && @@ -456,13 +485,17 @@ namespace Content.Shared.Interaction fixtureA.FixtureCount > 0 && TryComp(other, out var fixtureB) && fixtureB.FixtureCount > 0 && - TryComp(origin, out var xformA) && - TryComp(other, out var xformB)) + TryComp(origin, out var xformA)) { + 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. if (!_sharedBroadphaseSystem.TryGetNearest(origin, other, - out var pointA, out var pointB, out var distance, - xformA, xformB, fixtureA, fixtureB)) + out _, out _, out var distance, + xfA, xfB, fixtureA, fixtureB)) { 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. originPos = xformA.MapPosition; - (var targetWorld, targetRot) = xformB.GetWorldPositionRotation(); - targetPos = new MapCoordinates(targetWorld, xformB.MapID); - - range = (originPos.Position - targetWorld).Length; + range = (originPos.Position - targetPos.Position).Length; } } else { 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 @@ -542,8 +568,6 @@ namespace Content.Shared.Interaction { HashSet ignored = new(); - bool ignoreAnchored = false; - if (HasComp(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 @@ -554,6 +578,7 @@ namespace Content.Shared.Interaction { // wall-mount exemptions may be restricted to a specific angle range.da + bool ignoreAnchored; if (wallMount.Arc >= Math.Tau) ignoreAnchored = true; else @@ -665,7 +690,7 @@ namespace Content.Shared.Interaction bool canReach) { var ev = new BeforeRangedInteractEvent(user, used, target, clickLocation, canReach); - RaiseLocalEvent(used, ev, false); + RaiseLocalEvent(used, ev); return ev.Handled; } @@ -717,7 +742,7 @@ namespace Content.Shared.Interaction return; var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach); - RaiseLocalEvent(target.Value, afterInteractUsingEvent, false); + RaiseLocalEvent(target.Value, afterInteractUsingEvent); } #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. // 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; // Does the user have hands? diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index b3c6004f56..75fe903201 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Weapons.Melee.Events; using JetBrains.Annotations; using Robust.Shared.GameStates; using Robust.Shared.Map; +using Robust.Shared.Players; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -109,7 +110,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem if (weapon?.Owner != msg.Weapon) return; - AttemptAttack(args.SenderSession.AttachedEntity!.Value, weapon, msg); + AttemptAttack(args.SenderSession.AttachedEntity!.Value, weapon, msg, args.SenderSession); } private void OnStopHeavyAttack(StopHeavyAttackEvent msg, EntitySessionEventArgs args) @@ -147,7 +148,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem if (userWeapon != weapon) return; - AttemptAttack(args.SenderSession.AttachedEntity.Value, weapon, msg); + AttemptAttack(args.SenderSession.AttachedEntity.Value, weapon, msg, args.SenderSession); } private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args) @@ -162,7 +163,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem if (userWeapon == null) 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) @@ -219,7 +220,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem if (!TryComp(target, out var targetXform)) 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) @@ -227,13 +228,13 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem if (!TryComp(target, out var targetXform)) return; - AttemptAttack(user, weapon, new DisarmAttackEvent(target, targetXform.Coordinates)); + AttemptAttack(user, weapon, new DisarmAttackEvent(target, targetXform.Coordinates), null); } /// /// Called when a windup is finished and an attack is tried. /// - private void AttemptAttack(EntityUid user, MeleeWeaponComponent weapon, AttackEvent attack) + private void AttemptAttack(EntityUid user, MeleeWeaponComponent weapon, AttackEvent attack, ICommonSession? session) { var curTime = Timing.CurTime; @@ -258,15 +259,15 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem switch (attack) { case LightAttackEvent light: - DoLightAttack(user, light, weapon); + DoLightAttack(user, light, weapon, session); break; case DisarmAttackEvent disarm: - if (!DoDisarm(user, disarm, weapon)) + if (!DoDisarm(user, disarm, weapon, session)) return; break; case HeavyAttackEvent heavy: - DoHeavyAttack(user, heavy, weapon); + DoHeavyAttack(user, heavy, weapon, session); break; default: throw new NotImplementedException(); @@ -314,17 +315,17 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem 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) || user == ev.Target) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 7cd4282ba5..0cb379a132 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -3,6 +3,7 @@ abstract: true id: MobSiliconBase components: + - type: LagCompensation - type: Reactive groups: Acidic: [Touch] diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index c0e657c741..db3d6e48f9 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -4,6 +4,7 @@ id: SimpleSpaceMobBase # Mob without barotrauma, freezing and asphyxiation (for space carps!?) suffix: AI components: + - type: LagCompensation - type: Tag tags: - DoorBumpOpener diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index 5b64153576..bd89a53a2d 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -6,6 +6,7 @@ description: guardian save: false components: + - type: LagCompensation - type: GhostTakeoverAvailable allowMovement: true allowSpeech: true diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 7af56f8620..43f20f238e 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -5,6 +5,7 @@ id: BaseMobOrganic noSpawn: true components: + - type: LagCompensation - type: RangedDamageSound soundGroups: Brute: