From ac62d97cc90a77c47f898211d840be8afbe4e17a Mon Sep 17 00:00:00 2001 From: Spatison <137375981+Spatison@users.noreply.github.com> Date: Wed, 28 Aug 2024 03:18:09 +0300 Subject: [PATCH 1/3] add: grab --- Content.Server/Alert/Click/StopPulling.cs | 2 +- .../Body/Systems/RespiratorSystem.cs | 11 +- Content.Server/Hands/Systems/HandsSystem.cs | 24 +- .../Implants/SubdermalImplantSystem.cs | 2 +- .../Administration/AdminFrozenSystem.cs | 2 +- .../EntitySystems/AnchorableSystem.cs | 2 +- .../SharedHandsSystem.Interactions.cs | 20 +- .../Hands/EntitySystems/SharedHandsSystem.cs | 37 ++ Content.Shared/Hands/HandEvents.cs | 43 +- .../VirtualItem/SharedVirtualItemSystem.cs | 50 +- .../Pulling/Components/PullableComponent.cs | 20 + .../Pulling/Components/PullerComponent.cs | 45 ++ .../Movement/Pulling/Systems/PullingSystem.cs | 523 +++++++++++++++++- .../Systems/DeployableBarrierSystem.cs | 2 +- .../Systems/SharedPortalSystem.cs | 4 +- .../_White/Grab/GrabThrownComponent.cs | 16 + .../_White/Grab/GrabThrownSystem.cs | 102 ++++ Resources/Prototypes/Alerts/alerts.yml | 24 +- .../Interface/Alerts/pull.rsi/grab-choke.png | Bin 0 -> 1038 bytes .../Interface/Alerts/pull.rsi/grab-hard.png | Bin 0 -> 716 bytes .../Interface/Alerts/pull.rsi/grab-soft.png | Bin 0 -> 706 bytes .../Alerts/pull.rsi/grabbed-choke.png | Bin 0 -> 526 bytes .../Alerts/pull.rsi/grabbed-hard.png | Bin 0 -> 367 bytes .../Alerts/pull.rsi/grabbed-soft.png | Bin 0 -> 372 bytes .../Interface/Alerts/pull.rsi/meta.json | 45 ++ .../Interface/Alerts/pull.rsi/pulled.png | Bin 0 -> 696 bytes .../Interface/Alerts/pull.rsi/pulling.png | Bin 0 -> 525 bytes 27 files changed, 939 insertions(+), 35 deletions(-) create mode 100644 Content.Shared/_White/Grab/GrabThrownComponent.cs create mode 100644 Content.Shared/_White/Grab/GrabThrownSystem.cs create mode 100644 Resources/Textures/Interface/Alerts/pull.rsi/grab-choke.png create mode 100644 Resources/Textures/Interface/Alerts/pull.rsi/grab-hard.png create mode 100644 Resources/Textures/Interface/Alerts/pull.rsi/grab-soft.png create mode 100644 Resources/Textures/Interface/Alerts/pull.rsi/grabbed-choke.png create mode 100644 Resources/Textures/Interface/Alerts/pull.rsi/grabbed-hard.png create mode 100644 Resources/Textures/Interface/Alerts/pull.rsi/grabbed-soft.png create mode 100644 Resources/Textures/Interface/Alerts/pull.rsi/meta.json create mode 100644 Resources/Textures/Interface/Alerts/pull.rsi/pulled.png create mode 100644 Resources/Textures/Interface/Alerts/pull.rsi/pulling.png diff --git a/Content.Server/Alert/Click/StopPulling.cs b/Content.Server/Alert/Click/StopPulling.cs index 76f9569429f..3941ff6873f 100644 --- a/Content.Server/Alert/Click/StopPulling.cs +++ b/Content.Server/Alert/Click/StopPulling.cs @@ -20,7 +20,7 @@ public void AlertClicked(EntityUid player) if (entManager.TryGetComponent(player, out PullerComponent? puller) && entManager.TryGetComponent(puller.Pulling, out PullableComponent? pullableComp)) { - ps.TryStopPull(puller.Pulling.Value, pullableComp, user: player); + ps.TryLowerGrabStage(puller.Pulling.Value, player, true); } } } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 389e5fbab72..460cd234995 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -11,6 +11,8 @@ using Content.Shared.Database; using Content.Shared.Mobs.Systems; using Content.Shared.Mood; +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Movement.Pulling.Systems; using JetBrains.Annotations; using Robust.Shared.Timing; @@ -51,6 +53,13 @@ private void OnUnpaused(Entity ent, ref EntityUnpausedEvent ent.Comp.NextUpdate += args.PausedTime; } + public bool CanBreathe(EntityUid uid) + { + if (TryComp(uid, out var pullable) && pullable.GrabStage == GrabStage.Suffocate) + return false; + return true; + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -83,7 +92,7 @@ public override void Update(float frameTime) } } - if (respirator.Saturation < respirator.SuffocationThreshold) + if (respirator.Saturation < respirator.SuffocationThreshold || !CanBreathe(uid)) { if (_gameTiming.CurTime >= respirator.LastGaspPopupTime + respirator.GaspPopupCooldown) { diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index bd24ddab5d1..ae30b4f980a 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Body.Part; using Content.Shared.CombatMode; using Content.Shared.Explosion; +using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Input; @@ -88,7 +89,7 @@ private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent a // Break any pulls if (TryComp(uid, out PullerComponent? puller) && TryComp(puller.Pulling, out PullableComponent? pullable)) - _pullingSystem.TryStopPull(puller.Pulling.Value, pullable); + _pullingSystem.TryStopPull(puller.Pulling.Value, pullable, ignoreGrab: true); if (!_handsSystem.TryDrop(uid, component.ActiveHand!, null, checkActionBlocker: false)) return; @@ -168,6 +169,18 @@ private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates co if (playerSession?.AttachedEntity is not {Valid: true} player || !Exists(player)) return false; + if (TryGetActiveItem(player, out var item) && TryComp(item, out var virtComp)) + { + var userEv = new VirtualItemDropAttemptEvent(virtComp.BlockingEntity, player, item.Value, true); + RaiseLocalEvent(player, userEv); + + var targEv = new VirtualItemDropAttemptEvent(virtComp.BlockingEntity, player, item.Value, true); + RaiseLocalEvent(virtComp.BlockingEntity, targEv); + + if (userEv.Cancelled || targEv.Cancelled) + return false; + } + return ThrowHeldItem(player, coordinates); } @@ -211,6 +224,15 @@ hands.ActiveHandEntity is not { } throwEnt || var ev = new BeforeThrowEvent(throwEnt, direction, throwStrength, player); RaiseLocalEvent(player, ref ev); + if (TryComp(throwEnt, out var virt)) + { + var userEv = new VirtualItemThrownEvent(virt.BlockingEntity, player, throwEnt, direction); + RaiseLocalEvent(player, userEv); + + var targEv = new VirtualItemThrownEvent(virt.BlockingEntity, player, throwEnt, direction); + RaiseLocalEvent(virt.BlockingEntity, targEv); + } + if (ev.Cancelled) return true; diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs index e8af08b2ebb..46836814cfc 100644 --- a/Content.Server/Implants/SubdermalImplantSystem.cs +++ b/Content.Server/Implants/SubdermalImplantSystem.cs @@ -107,7 +107,7 @@ private void OnScramImplant(EntityUid uid, SubdermalImplantComponent component, // We need stop the user from being pulled so they don't just get "attached" with whoever is pulling them. // This can for example happen when the user is cuffed and being pulled. if (TryComp(ent, out var pull) && _pullingSystem.IsPulled(ent, pull)) - _pullingSystem.TryStopPull(ent, pull); + _pullingSystem.TryStopPull(ent, pull, ignoreGrab: true); var xform = Transform(ent); var targetCoords = SelectRandomTileInRange(xform, implant.TeleportRadius); diff --git a/Content.Shared/Administration/AdminFrozenSystem.cs b/Content.Shared/Administration/AdminFrozenSystem.cs index 4ec9600b0bd..30d11ecae9d 100644 --- a/Content.Shared/Administration/AdminFrozenSystem.cs +++ b/Content.Shared/Administration/AdminFrozenSystem.cs @@ -44,7 +44,7 @@ private void OnStartup(EntityUid uid, AdminFrozenComponent component, ComponentS { if (TryComp(uid, out var pullable)) { - _pulling.TryStopPull(uid, pullable); + _pulling.TryStopPull(uid, pullable, ignoreGrab: true); } UpdateCanMove(uid, component, args); diff --git a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs index c041cf1ba06..0ea3b0eb1f6 100644 --- a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -133,7 +133,7 @@ private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryA if (TryComp(uid, out var pullable) && pullable.Puller != null) { - _pulling.TryStopPull(uid, pullable); + _pulling.TryStopPull(uid, pullable, ignoreGrab: true); } // TODO: Anchoring snaps rn anyway! diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs index 32339eb03ac..a30c5ea4965 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs @@ -95,7 +95,25 @@ private void SwapHandsPressed(ICommonSession? session) private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid netEntity) { if (TryComp(session?.AttachedEntity, out HandsComponent? hands) && hands.ActiveHand != null) - TryDrop(session.AttachedEntity.Value, hands.ActiveHand, coords, handsComp: hands); + { + if (session != null) + { + var ent = session.AttachedEntity.Value; + + if (TryGetActiveItem(ent, out var item) && TryComp(item, out var virtComp)) + { + var userEv = new VirtualItemDropAttemptEvent(virtComp.BlockingEntity, ent, item.Value, false); + RaiseLocalEvent(ent, userEv); + + var targEv = new VirtualItemDropAttemptEvent(virtComp.BlockingEntity, ent, item.Value, false); + RaiseLocalEvent(virtComp.BlockingEntity, targEv); + + if (userEv.Cancelled || targEv.Cancelled) + return false; + } + TryDrop(ent, hands.ActiveHand, coords, handsComp: hands); + } + } // always send to server. return false; diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index b72a7c4eb3c..4f64714aabf 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -137,6 +137,43 @@ public bool TryGetEmptyHand(EntityUid uid, [NotNullWhen(true)] out Hand? emptyHa return false; } + public bool TryGetActiveHand(Entity entity, [NotNullWhen(true)] out Hand? hand) + { + if (!Resolve(entity, ref entity.Comp, false)) + { + hand = null; + return false; + } + + hand = entity.Comp.ActiveHand; + return hand != null; + } + + public bool TryGetActiveItem(Entity entity, [NotNullWhen(true)] out EntityUid? item) + { + if (!TryGetActiveHand(entity, out var hand)) + { + item = null; + return false; + } + + item = hand.HeldEntity; + return item != null; + } + + public Hand? GetActiveHand(Entity entity) + { + if (!Resolve(entity, ref entity.Comp)) + return null; + + return entity.Comp.ActiveHand; + } + + public EntityUid? GetActiveItem(Entity entity) + { + return GetActiveHand(entity)?.HeldEntity; + } + /// /// Enumerate over hands, starting with the currently active hand. /// diff --git a/Content.Shared/Hands/HandEvents.cs b/Content.Shared/Hands/HandEvents.cs index 0499c05f426..f900d92acd9 100644 --- a/Content.Shared/Hands/HandEvents.cs +++ b/Content.Shared/Hands/HandEvents.cs @@ -148,11 +148,52 @@ public sealed class VirtualItemDeletedEvent : EntityEventArgs { public EntityUid BlockingEntity; public EntityUid User; + public EntityUid VirtualItem; - public VirtualItemDeletedEvent(EntityUid blockingEntity, EntityUid user) + public VirtualItemDeletedEvent(EntityUid blockingEntity, EntityUid user, EntityUid virtualItem) { BlockingEntity = blockingEntity; User = user; + VirtualItem = virtualItem; + } + } + + /// + /// Raised directed on both the blocking entity and user when + /// a virtual hand item is thrown (at least attempted to). + /// + public sealed class VirtualItemThrownEvent : EntityEventArgs + { + public EntityUid BlockingEntity; + public EntityUid User; + public EntityUid VirtualItem; + public Vector2 Direction; + public VirtualItemThrownEvent(EntityUid blockingEntity, EntityUid user, EntityUid virtualItem, Vector2 direction) + { + BlockingEntity = blockingEntity; + User = user; + VirtualItem = virtualItem; + Direction = direction; + } + } + + /// + /// Raised directed on both the blocking entity and user when + /// user tries to drop it by keybind. + /// Cancellable. + /// + public sealed class VirtualItemDropAttemptEvent : CancellableEntityEventArgs + { + public EntityUid BlockingEntity; + public EntityUid User; + public EntityUid VirtualItem; + public bool Throw; + public VirtualItemDropAttemptEvent(EntityUid blockingEntity, EntityUid user, EntityUid virtualItem, bool thrown) + { + BlockingEntity = blockingEntity; + User = user; + VirtualItem = virtualItem; + Throw = thrown; } } diff --git a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs index e45530e4582..b13900e2ea1 100644 --- a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs +++ b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs @@ -1,9 +1,11 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.Hands; +using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Item; +using Content.Shared.Popups; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Prototypes; @@ -29,6 +31,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem [Dependency] private readonly SharedItemSystem _itemSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; [ValidatePrototypeId] private const string VirtualItem = "VirtualItem"; @@ -76,18 +79,49 @@ private void OnBeforeRangedInteract(Entity ent, ref Before /// /// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity - public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user) + public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, bool dropOthers = false) { - return TrySpawnVirtualItemInHand(blockingEnt, user, out _); + return TrySpawnVirtualItemInHand(blockingEnt, user, out _, dropOthers); } /// - public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem) + public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false, Hand? empty = null) { - if (!TrySpawnVirtualItem(blockingEnt, user, out virtualItem) || !_handsSystem.TryGetEmptyHand(user, out var hand)) + virtualItem = null; + if (empty == null && !_handsSystem.TryGetEmptyHand(user, out empty)) + { + if (!dropOthers) + return false; + + foreach (var hand in _handsSystem.EnumerateHands(user)) + { + if (hand.HeldEntity is not { } held) + continue; + + if (held == blockingEnt) + continue; + + if (HasComp(held)) + continue; + + if (!_handsSystem.TryDrop(user, hand)) + continue; + + if (!TerminatingOrDeleted(held)) + _popup.PopupClient(Loc.GetString("virtual-item-dropped-other", ("dropped", held)), user, user); + + empty = hand; + break; + } + } + + if (empty == null) + return false; + + if (!TrySpawnVirtualItem(blockingEnt, user, out virtualItem)) return false; - _handsSystem.DoPickup(user, hand, virtualItem.Value); + _handsSystem.DoPickup(user, empty, virtualItem.Value); return true; } @@ -188,7 +222,7 @@ public bool TrySpawnVirtualItem(EntityUid blockingEnt, EntityUid user, [NotNullW var pos = Transform(user).Coordinates; virtualItem = Spawn(VirtualItem, pos); - var virtualItemComp = Comp(virtualItem.Value); + var virtualItemComp = EnsureComp(virtualItem.Value); virtualItemComp.BlockingEntity = blockingEnt; Dirty(virtualItem.Value, virtualItemComp); return true; @@ -199,10 +233,10 @@ public bool TrySpawnVirtualItem(EntityUid blockingEnt, EntityUid user, [NotNullW /// public void DeleteVirtualItem(Entity item, EntityUid user) { - var userEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user); + var userEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user, item.Owner); RaiseLocalEvent(user, userEv); - var targEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user); + var targEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user, item.Owner); RaiseLocalEvent(item.Comp.BlockingEntity, targEv); if (TerminatingOrDeleted(item)) diff --git a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs index db889e7e3bd..75c6bd20671 100644 --- a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Movement.Pulling.Systems; using Robust.Shared.GameStates; namespace Content.Shared.Movement.Pulling.Components; @@ -36,4 +37,23 @@ public sealed partial class PullableComponent : Component [Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)] [AutoNetworkedField, DataField] public bool PrevFixedRotation; + + + [DataField] + public Dictionary PulledAlertAlertSeverity = new() + { + { GrabStage.No, 0 }, + { GrabStage.Soft, 1 }, + { GrabStage.Hard, 2 }, + { GrabStage.Suffocate, 3 }, + }; + + [AutoNetworkedField, DataField] + public GrabStage GrabStage = GrabStage.No; + + [AutoNetworkedField, DataField] + public float GrabEscapeChance = 1f; + + [AutoNetworkedField] + public TimeSpan NextEscapeAttempt = TimeSpan.Zero; } diff --git a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs index 1fc9b731bd5..db6d289ea41 100644 --- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs @@ -38,4 +38,49 @@ public sealed partial class PullerComponent : Component /// [DataField] public bool NeedsHands = true; + + [DataField] + public Dictionary PullingAlertSeverity = new() + { + { GrabStage.No, 0 }, + { GrabStage.Soft, 1 }, + { GrabStage.Hard, 2 }, + { GrabStage.Suffocate, 3 }, + }; + + [DataField, AutoNetworkedField] + public GrabStage GrabStage = GrabStage.No; + + [DataField, AutoNetworkedField] + public GrubStageDirection GrabStageDirection = GrubStageDirection.Increase; + + [AutoNetworkedField] + public TimeSpan NextStageChange; + + [DataField] + public TimeSpan StageChangeCooldown = TimeSpan.FromSeconds(1.5f); + + [DataField] + public Dictionary EscapeChances = new() + { + { GrabStage.No, 1f }, + { GrabStage.Soft, 0.7f }, + { GrabStage.Hard, 0.4f }, + { GrabStage.Suffocate, 0.1f }, + }; + + [DataField] + public float SuffocateGrabStaminaDamage = 10f; + + [DataField] + public float GrabThrowDamageModifier = 1f; + + [ViewVariables] + public List GrabVirtualItems = new(); + + [ViewVariables] + public Dictionary GrabVirtualItemStageCount = new() + { + { GrabStage.Suffocate, 1 }, + }; } diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index 3c265d5a027..ed532816529 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -1,28 +1,43 @@ using System.Numerics; +using Content.Shared._White.Grab; using Content.Shared.ActionBlocker; using Content.Shared.Administration.Logs; using Content.Shared.Alert; using Content.Shared.Buckle.Components; +using Content.Shared.CombatMode; +using Content.Shared.Damage; +using Content.Shared.Damage.Systems; using Content.Shared.Database; +using Content.Shared.Effects; using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; using Content.Shared.Input; using Content.Shared.Interaction; +using Content.Shared.Inventory.VirtualItem; +using Content.Shared.Item; +using Content.Shared.Mobs.Components; using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Events; using Content.Shared.Movement.Systems; +using Content.Shared.Popups; using Content.Shared.Pulling.Events; +using Content.Shared.Speech; using Content.Shared.Throwing; using Content.Shared.Verbs; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; using Robust.Shared.Map; +using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; using Robust.Shared.Player; +using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Shared.Movement.Pulling.Systems; @@ -44,6 +59,16 @@ public sealed class PullingSystem : EntitySystem [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedTransformSystem _xformSys = default!; [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly StaminaSystem _stamina = default!; + [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly GrabThrownSystem _grabThrown = default!; + [Dependency] private readonly SharedCombatModeSystem _combatMode = default!; public override void Initialize() { @@ -57,11 +82,15 @@ public override void Initialize() SubscribeLocalEvent(OnJointRemoved); SubscribeLocalEvent>(AddPullVerbs); SubscribeLocalEvent(OnPullableContainerInsert); + SubscribeLocalEvent(OnGrabbedMoveAttempt); + SubscribeLocalEvent(OnGrabbedSpeakAttempt); SubscribeLocalEvent(OnPullerContainerInsert); SubscribeLocalEvent(OnPullerUnpaused); SubscribeLocalEvent(OnVirtualItemDeleted); SubscribeLocalEvent(OnRefreshMovespeed); + SubscribeLocalEvent(OnVirtualItemThrown); + SubscribeLocalEvent(OnVirtualItemDropAttempt); CommandBinds.Builder .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(OnRequestMovePulledObject)) @@ -76,12 +105,17 @@ private void OnPullerContainerInsert(Entity ent, ref EntGotInse if (!TryComp(ent.Comp.Pulling.Value, out PullableComponent? pulling)) return; - TryStopPull(ent.Comp.Pulling.Value, pulling, ent.Owner); + foreach (var item in ent.Comp.GrabVirtualItems) + { + QueueDel(item); + } + + TryStopPull(ent.Comp.Pulling.Value, pulling, ent.Owner, true); } private void OnPullableContainerInsert(Entity ent, ref EntGotInsertedIntoContainerMessage args) { - TryStopPull(ent.Owner, ent.Comp); + TryStopPull(ent.Owner, ent.Comp, ignoreGrab: true); } public override void Shutdown() @@ -95,6 +129,41 @@ private void OnPullerUnpaused(EntityUid uid, PullerComponent component, ref Enti component.NextThrow += args.PausedTime; } + private void OnVirtualItemDropAttempt(EntityUid uid, PullerComponent component, VirtualItemDropAttemptEvent args) + { + if (component.Pulling == null) + return; + + if (component.Pulling != args.BlockingEntity) + return; + + if (_timing.CurTime < component.NextStageChange) + { + args.Cancel(); // VirtualItem is NOT being deleted + return; + } + + if (!args.Throw) + { + if (component.GrabStage > GrabStage.No) + { + if (EntityManager.TryGetComponent(args.BlockingEntity, out PullableComponent? comp)) + { + TryLowerGrabStage(component.Pulling.Value, uid); + args.Cancel(); // VirtualItem is NOT being deleted + } + } + } + else + { + if (component.GrabStage <= GrabStage.Soft) + { + TryLowerGrabStage(component.Pulling.Value, uid); + args.Cancel(); // VirtualItem is NOT being deleted + } + } + } + private void OnVirtualItemDeleted(EntityUid uid, PullerComponent component, VirtualItemDeletedEvent args) { // If client deletes the virtual hand then stop the pull. @@ -106,7 +175,66 @@ private void OnVirtualItemDeleted(EntityUid uid, PullerComponent component, Virt if (EntityManager.TryGetComponent(args.BlockingEntity, out PullableComponent? comp)) { - TryStopPull(args.BlockingEntity, comp, uid); + TryLowerGrabStage(component.Pulling.Value, uid); + } + } + + private void OnVirtualItemThrown(EntityUid uid, PullerComponent component, VirtualItemThrownEvent args) + { + if (component.Pulling == null) + return; + + if (component.Pulling != args.BlockingEntity) + return; + + if (EntityManager.TryGetComponent(args.BlockingEntity, out PullableComponent? comp)) + { + if (_combatMode.IsInCombatMode(uid) && + !HasComp(args.BlockingEntity) && + component.GrabStage > GrabStage.Soft) + { + var direction = args.Direction; + var vecBetween = (Transform(args.BlockingEntity).Coordinates.ToMapPos(EntityManager, _transform) - Transform(uid).WorldPosition); + + // Getting angle between us + var dirAngle = direction.ToWorldAngle().Degrees; + var betweenAngle = vecBetween.ToWorldAngle().Degrees; + + var angle = dirAngle - betweenAngle; + + if (angle < 0) + angle = -angle; + + var maxDistance = 3f; + var damageModifier = 1f; + + if (angle < 30) + { + damageModifier = 0.3f; + maxDistance = 1f; + } + else if (angle < 90) + { + damageModifier = 0.7f; + maxDistance = 1.5f; + } + else + maxDistance = 2.25f; + + var distance = Math.Clamp(args.Direction.Length(), 0.5f, maxDistance); + direction *= distance / args.Direction.Length(); + + + var damage = new DamageSpecifier(); + damage.DamageDict.Add("Blunt", 5); + damage *= damageModifier; + + TryStopPull(args.BlockingEntity, comp, uid, true); + _grabThrown.Throw(args.BlockingEntity, uid, direction * 2f, 120f, damage * component.GrabThrowDamageModifier, damage * component.GrabThrowDamageModifier); + _throwing.TryThrow(uid, -direction * 0.5f); + _audio.PlayPvs(new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"), uid); + component.NextStageChange.Add(TimeSpan.FromSeconds(2f)); // To avoid grab and throw spamming + } } } @@ -144,7 +272,49 @@ private void AddPullVerbs(EntityUid uid, PullableComponent component, GetVerbsEv private void OnRefreshMovespeed(EntityUid uid, PullerComponent component, RefreshMovementSpeedModifiersEvent args) { - args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier); + if (TryComp(component.Pulling, out var heldMoveSpeed) && component.Pulling.HasValue) + { + var (walkMod, sprintMod) = (args.WalkSpeedModifier, args.SprintSpeedModifier); + + switch (component.GrabStage) + { + case GrabStage.No: + args.ModifySpeed(walkMod, sprintMod); + break; + case GrabStage.Soft: + args.ModifySpeed(walkMod * 0.9f, sprintMod * 0.9f); + break; + case GrabStage.Hard: + args.ModifySpeed(walkMod * 0.7f, sprintMod * 0.7f); + break; + case GrabStage.Suffocate: + args.ModifySpeed(walkMod * 0.4f, sprintMod * 0.4f); + break; + default: + args.ModifySpeed(walkMod, sprintMod); + break; + } + return; + } + + switch (component.GrabStage) + { + case GrabStage.No: + args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier); + break; + case GrabStage.Soft: + args.ModifySpeed(component.WalkSpeedModifier * 0.9f, component.SprintSpeedModifier * 0.9f); + break; + case GrabStage.Hard: + args.ModifySpeed(component.WalkSpeedModifier * 0.7f, component.SprintSpeedModifier * 0.7f); + break; + case GrabStage.Suffocate: + args.ModifySpeed(component.WalkSpeedModifier * 0.4f, component.SprintSpeedModifier * 0.4f); + break; + default: + args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier); + break; + } } private void OnPullableMoveInput(EntityUid uid, PullableComponent component, ref MoveInputEvent args) @@ -204,6 +374,10 @@ private void StopPulling(EntityUid pullableUid, PullableComponent pullableComp) var oldPuller = pullableComp.Puller; pullableComp.PullJointId = null; pullableComp.Puller = null; + pullableComp.GrabStage = GrabStage.No; + pullableComp.GrabEscapeChance = 1f; + _blocker.UpdateCanMove(pullableUid); + Dirty(pullableUid, pullableComp); // No more joints with puller -> force stop pull. @@ -212,6 +386,15 @@ private void StopPulling(EntityUid pullableUid, PullableComponent pullableComp) var pullerUid = oldPuller.Value; _alertsSystem.ClearAlert(pullerUid, AlertType.Pulling); pullerComp.Pulling = null; + + pullerComp.GrabStage = GrabStage.No; + List virtItems = pullerComp.GrabVirtualItems; + foreach (var item in virtItems) + { + QueueDel(item); + } + pullerComp.GrabVirtualItems.Clear(); + Dirty(oldPuller.Value, pullerComp); // Messaging @@ -291,7 +474,7 @@ private void OnReleasePulledObject(ICommonSession? session) return; } - TryStopPull(pullerComp.Pulling.Value, pullableComp, user: player); + TryStopPull(pullerComp.Pulling.Value, pullableComp, user: player, true); } public bool CanPull(EntityUid puller, EntityUid pullableUid, PullerComponent? pullerComp = null) @@ -351,12 +534,16 @@ public bool CanPull(EntityUid puller, EntityUid pullableUid, PullerComponent? pu public bool TogglePull(EntityUid pullableUid, EntityUid pullerUid, PullableComponent pullable) { - if (pullable.Puller == pullerUid) - { - return TryStopPull(pullableUid, pullable); - } + if (pullable.Puller != pullerUid) + return TryStartPull(pullerUid, pullableUid, pullableComp: pullable); - return TryStartPull(pullerUid, pullableUid, pullableComp: pullable); + if (TryGrab((pullableUid, pullable), pullerUid)) + return true; + + if (!_combatMode.IsInCombatMode(pullableUid)) + return TryStopPull(pullableUid, pullable, ignoreGrab: true); + + return false; } public bool TogglePull(EntityUid pullerUid, PullerComponent puller) @@ -390,7 +577,7 @@ public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, // Ensure that the puller is not currently pulling anything. if (TryComp(pullerComp.Pulling, out var oldPullable) - && !TryStopPull(pullerComp.Pulling.Value, oldPullable, pullerUid)) + && !TryStopPull(pullerComp.Pulling.Value, oldPullable, pullerUid, true)) return false; // Stop anyone else pulling the entity we want to pull @@ -401,7 +588,37 @@ public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, return false; if (!TryStopPull(pullableUid, pullableComp, pullableComp.Puller)) + { + // Not succeed to retake grabbed entity + if (_netManager.IsServer) + { + _popup.PopupEntity(Loc.GetString("popup-grab-retake-fail", + ("puller", Identity.Entity(pullableComp.Puller.Value, EntityManager)), + ("pulled", Identity.Entity(pullableUid, EntityManager))), + pullerUid, pullerUid, PopupType.MediumCaution); + _popup.PopupEntity(Loc.GetString("popup-grab-retake-fail-puller", + ("puller", Identity.Entity(pullerUid, EntityManager)), + ("pulled", Identity.Entity(pullableUid, EntityManager))), + pullableComp.Puller.Value, pullableComp.Puller.Value, PopupType.MediumCaution); + } return false; + } + + else if (pullableComp.GrabStage != GrabStage.No) + { + // Successful retake + if (_netManager.IsServer) + { + _popup.PopupEntity(Loc.GetString("popup-grab-retake-success", + ("puller", Identity.Entity(pullableComp.Puller.Value, EntityManager)), + ("pulled", Identity.Entity(pullableUid, EntityManager))), + pullerUid, pullerUid, PopupType.MediumCaution); + _popup.PopupEntity(Loc.GetString("popup-grab-retake-success-puller", + ("puller", Identity.Entity(pullerUid, EntityManager)), + ("pulled", Identity.Entity(pullableUid, EntityManager))), + pullableComp.Puller.Value, pullableComp.Puller.Value, PopupType.MediumCaution); + } + } } var pullAttempt = new PullAttemptEvent(pullerUid, pullableUid); @@ -447,8 +664,8 @@ public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, // Messaging var message = new PullStartedMessage(pullerUid, pullableUid); - _alertsSystem.ShowAlert(pullerUid, AlertType.Pulling); - _alertsSystem.ShowAlert(pullableUid, AlertType.Pulled); + _alertsSystem.ShowAlert(pullerUid, AlertType.Pulling, 0); + _alertsSystem.ShowAlert(pullableUid, AlertType.Pulled, 0); RaiseLocalEvent(pullerUid, message); RaiseLocalEvent(pullableUid, message); @@ -458,10 +675,14 @@ public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(pullerUid):user} started pulling {ToPrettyString(pullableUid):target}"); + + if (_combatMode.IsInCombatMode(pullerUid)) + TryGrab(pullableUid, pullerUid); + return true; } - public bool TryStopPull(EntityUid pullableUid, PullableComponent pullable, EntityUid? user = null) + public bool TryStopPull(EntityUid pullableUid, PullableComponent pullable, EntityUid? user = null, bool ignoreGrab = false) { var pullerUidNull = pullable.Puller; @@ -474,6 +695,23 @@ public bool TryStopPull(EntityUid pullableUid, PullableComponent pullable, Entit if (msg.Cancelled) return false; + // There are some events that should ignore grab stages + if (!ignoreGrab) + { + if (!AttemptGrabRelease(pullableUid)) + { + if (_netManager.IsServer && user != null && user.Value == pullableUid) + _popup.PopupEntity(Loc.GetString("popup-grab-release-fail-self"), pullableUid, pullableUid, PopupType.SmallCaution); + return false; + } + + if (_netManager.IsServer && user != null && user.Value == pullableUid) + { + _popup.PopupEntity(Loc.GetString("popup-grab-release-success-self"), pullableUid, pullableUid, PopupType.SmallCaution); + _popup.PopupEntity(Loc.GetString("popup-grab-release-success-puller", ("target", Identity.Entity(pullableUid, EntityManager))), pullerUidNull.Value, pullerUidNull.Value, PopupType.MediumCaution); + } + } + // Stop pulling confirmed! if (!_timing.ApplyingState) { @@ -488,4 +726,261 @@ public bool TryStopPull(EntityUid pullableUid, PullableComponent pullable, Entit StopPulling(pullableUid, pullable); return true; } + + /// + /// Trying to grab the target + /// + /// Target that would be grabbed + /// Performer of the grab + /// If true, will ignore disabled combat mode + /// + /// + public bool TryGrab(Entity pullable, Entity puller, bool ignoreCombatMode = false) + { + if (!Resolve(pullable.Owner, ref pullable.Comp)) + return false; + + if (!Resolve(puller.Owner, ref puller.Comp)) + return false; + + if (pullable.Comp.Puller != puller.Owner || + puller.Comp.Pulling != pullable.Owner) + return false; + + if (puller.Comp.NextStageChange > _timing.CurTime) + return true; + + // You can't choke crates + if (!HasComp(pullable)) + return false; + + // Delay to avoid spamming + puller.Comp.NextStageChange = _timing.CurTime + puller.Comp.StageChangeCooldown; + Dirty(puller); + + // Don't grab without grab intent + if (!ignoreCombatMode) + { + if (!_combatMode.IsInCombatMode(puller.Owner)) + return false; + } + + // It's blocking stage update, maybe better UX? + if (puller.Comp.GrabStage == GrabStage.Suffocate) + { + _stamina.TakeStaminaDamage(pullable, puller.Comp.SuffocateGrabStaminaDamage); + + Dirty(pullable); + Dirty(puller); + return true; + } + + // Update stage + // TODO: Change grab stage direction + var nextStageAddition = puller.Comp.GrabStageDirection switch + { + GrubStageDirection.Increase => 1, + GrubStageDirection.Decrease => -1, + _ => throw new ArgumentOutOfRangeException(), + }; + + var newStage = puller.Comp.GrabStage + nextStageAddition; + + if (!TrySetGrabStages((puller.Owner, puller.Comp), (pullable.Owner, pullable.Comp), newStage)) + return false; + + _color.RaiseEffect(Color.Yellow, new List { pullable }, Filter.Pvs(pullable, entityManager: EntityManager)); + return true; + } + + private bool TrySetGrabStages(Entity puller, Entity pullable, GrabStage stage) + { + puller.Comp.GrabStage = stage; + pullable.Comp.GrabStage = stage; + + if (!TryUpdateGrabVirtualItems(puller, pullable)) + return false; + + var filter = Filter.Empty() + .AddPlayersByPvs(Transform(puller)) + .RemovePlayerByAttachedEntity(puller.Owner) + .RemovePlayerByAttachedEntity(pullable.Owner); + + var popupType = stage switch + { + GrabStage.No => PopupType.Small, + GrabStage.Soft => PopupType.Small, + GrabStage.Hard => PopupType.MediumCaution, + GrabStage.Suffocate => PopupType.LargeCaution, + _ => throw new ArgumentOutOfRangeException() + }; + + pullable.Comp.GrabEscapeChance = puller.Comp.EscapeChances[stage]; + + _alertsSystem.ShowAlert(puller, AlertType.Pulling, puller.Comp.PullingAlertSeverity[stage]); + _alertsSystem.ShowAlert(pullable, AlertType.Pulled, pullable.Comp.PulledAlertAlertSeverity[stage]); + + _blocker.UpdateCanMove(pullable); + _modifierSystem.RefreshMovementSpeedModifiers(puller); + + // I'm lazy to write client code + if (!_netManager.IsServer) + return true; + + _popup.PopupEntity(Loc.GetString($"popup-grab-{puller.Comp.GrabStage.ToString().ToLower()}-target", ("puller", Identity.Entity(puller, EntityManager))), pullable, pullable, popupType); + _popup.PopupEntity(Loc.GetString($"popup-grab-{puller.Comp.GrabStage.ToString().ToLower()}-self", ("target", Identity.Entity(pullable, EntityManager))), pullable, puller, PopupType.Medium); + _popup.PopupEntity(Loc.GetString($"popup-grab-{puller.Comp.GrabStage.ToString().ToLower()}-others", ("target", Identity.Entity(pullable, EntityManager)), ("puller", Identity.Entity(puller, EntityManager))), pullable, filter, true, popupType); + + _audio.PlayPvs(new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"), pullable); + + Dirty(pullable); + Dirty(puller); + + return true; + } + + private bool TryUpdateGrabVirtualItems(Entity puller, Entity pullable) + { + // Updating virtual items + var virtualItemsCount = puller.Comp.GrabVirtualItems.Count; + + var newVirtualItemsCount = puller.Comp.NeedsHands ? 0 : 1; + if (puller.Comp.GrabVirtualItemStageCount.TryGetValue(puller.Comp.GrabStage, out var count)) + newVirtualItemsCount += count; + + if (virtualItemsCount != newVirtualItemsCount) + { + var delta = newVirtualItemsCount - virtualItemsCount; + + // Adding new virtual items + if (delta > 0) + { + for (var i = 0; i < delta; i++) + { + if (!_virtualSystem.TrySpawnVirtualItemInHand(pullable, puller.Owner, out var item, true)) + { + // I'm lazy write client code + if (_netManager.IsServer) + _popup.PopupEntity(Loc.GetString("popup-grab-need-hand"), puller, puller, PopupType.Medium); + + return false; + } + + puller.Comp.GrabVirtualItems.Add(item.Value); + } + } + + if (delta < 0) + { + for (var i = 0; i < Math.Abs(delta); i++) + { + if (i >= puller.Comp.GrabVirtualItems.Count) + break; + + var item = puller.Comp.GrabVirtualItems[i]; + puller.Comp.GrabVirtualItems.Remove(item); + QueueDel(item); + } + } + } + + return true; + } + + /// + /// Attempts to release entity from grab + /// + /// Grabbed entity + /// + public bool AttemptGrabRelease(Entity pullable) + { + if (!Resolve(pullable.Owner, ref pullable.Comp)) + return false; + if (_timing.CurTime < pullable.Comp.NextEscapeAttempt) // No autoclickers! Mwa-ha-ha + { + return false; + } + + if (_random.Prob(pullable.Comp.GrabEscapeChance)) + return true; + + pullable.Comp.NextEscapeAttempt = _timing.CurTime.Add(TimeSpan.FromSeconds(1)); + Dirty(pullable.Owner, pullable.Comp); + return false; + } + + private void OnGrabbedMoveAttempt(EntityUid uid, PullableComponent component, UpdateCanMoveEvent args) + { + if (component.GrabStage == GrabStage.No) + return; + + args.Cancel(); + + } + + private void OnGrabbedSpeakAttempt(EntityUid uid, PullableComponent component, SpeakAttemptEvent args) + { + if (component.GrabStage != GrabStage.Suffocate) + return; + + _popup.PopupEntity(Loc.GetString("popup-grabbed-cant-speak"), uid, uid, PopupType.MediumCaution); // You cant speak while someone is choking you + + args.Cancel(); + } + + /// + /// Tries to lower grab stage for target or release it + /// + /// Grabbed entity + /// Performer + /// If true, will NOT release target if combat mode is off + /// + public bool TryLowerGrabStage(Entity pullable, Entity puller, bool ignoreCombatMode = false) + { + if (!Resolve(pullable.Owner, ref pullable.Comp)) + return false; + + if (!Resolve(puller.Owner, ref puller.Comp)) + return false; + + if (pullable.Comp.Puller != puller.Owner || + puller.Comp.Pulling != pullable.Owner) + return false; + + if (_timing.CurTime < puller.Comp.NextStageChange) + return true; + + pullable.Comp.NextEscapeAttempt = _timing.CurTime.Add(TimeSpan.FromSeconds(1f)); + Dirty(pullable); + + if (!ignoreCombatMode && _combatMode.IsInCombatMode(puller.Owner)) + { + TryStopPull(pullable, pullable.Comp, ignoreGrab: true); + return true; + } + + if (puller.Comp.GrabStage == GrabStage.No) + { + TryStopPull(pullable, pullable.Comp, ignoreGrab: true); + return true; + } + + var newStage = puller.Comp.GrabStage - 1; + TrySetGrabStages((puller.Owner, puller.Comp), (pullable.Owner, pullable.Comp), newStage); + return true; + } +} + +public enum GrabStage +{ + No = 0, + Soft = 1, + Hard = 2, + Suffocate = 3, +} + +public enum GrubStageDirection +{ + Increase, + Decrease, } diff --git a/Content.Shared/Security/Systems/DeployableBarrierSystem.cs b/Content.Shared/Security/Systems/DeployableBarrierSystem.cs index 622edc4b62e..5f4493d69f8 100644 --- a/Content.Shared/Security/Systems/DeployableBarrierSystem.cs +++ b/Content.Shared/Security/Systems/DeployableBarrierSystem.cs @@ -55,7 +55,7 @@ private void ToggleBarrierDeploy(EntityUid uid, bool isDeployed, DeployableBarri } if (TryComp(uid, out PullableComponent? pullable)) - _pulling.TryStopPull(uid, pullable); + _pulling.TryStopPull(uid, pullable, ignoreGrab: true); SharedPointLightComponent? pointLight = null; if (_pointLight.ResolveLight(uid, ref pointLight)) diff --git a/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs b/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs index 8d67aec518a..dae42f9a4b2 100644 --- a/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs +++ b/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs @@ -95,13 +95,13 @@ private void OnCollide(EntityUid uid, PortalComponent component, ref StartCollid // break pulls before portal enter so we dont break shit if (TryComp(subject, out var pullable) && pullable.BeingPulled) { - _pulling.TryStopPull(subject, pullable); + _pulling.TryStopPull(subject, pullable, ignoreGrab: true); } if (TryComp(subject, out var pullerComp) && TryComp(pullerComp.Pulling, out var subjectPulling)) { - _pulling.TryStopPull(subject, subjectPulling); + _pulling.TryStopPull(subject, subjectPulling, ignoreGrab: true); } // if they came from another portal, just return and wait for them to exit the portal diff --git a/Content.Shared/_White/Grab/GrabThrownComponent.cs b/Content.Shared/_White/Grab/GrabThrownComponent.cs new file mode 100644 index 00000000000..a7eadf7e927 --- /dev/null +++ b/Content.Shared/_White/Grab/GrabThrownComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; +using Content.Shared.Damage; + +namespace Content.Shared._White.Grab; + +[RegisterComponent, NetworkedComponent] +public sealed partial class GrabThrownComponent : Component +{ + public DamageSpecifier? DamageOnCollide; + + public DamageSpecifier? WallDamageOnCollide; + + public float? StaminaDamageOnCollide; + + public List IgnoreEntity = new(); +} diff --git a/Content.Shared/_White/Grab/GrabThrownSystem.cs b/Content.Shared/_White/Grab/GrabThrownSystem.cs new file mode 100644 index 00000000000..3ae770af16d --- /dev/null +++ b/Content.Shared/_White/Grab/GrabThrownSystem.cs @@ -0,0 +1,102 @@ +using Content.Shared.Damage.Systems; +using Content.Shared.Damage; +using Content.Shared.Effects; +using Content.Shared.Throwing; +using Robust.Shared.Network; +using Robust.Shared.Physics.Events; +using Robust.Shared.Player; +using System.Numerics; +using Content.Shared._White.Standing; + +namespace Content.Shared._White.Grab; + +public sealed class GrabThrownSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; + [Dependency] private readonly StaminaSystem _stamina = default!; + [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly INetManager _netMan = default!; + [Dependency] private readonly SharedLayingDownSystem _layingDown = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandleCollide); + SubscribeLocalEvent(OnStopThrow); + } + + private void HandleCollide(EntityUid uid, GrabThrownComponent component, ref StartCollideEvent args) + { + if (_netMan.IsClient) // To avoid effect spam + return; + + if (!HasComp(uid)) + { + RemComp(uid); + return; + } + + Log.Error($"{args.OtherEntity}"); + + if (component.IgnoreEntity.Contains(args.OtherEntity)) + return; + + if (!HasComp(uid)) + RemComp(uid); + + component.IgnoreEntity.Add(args.OtherEntity); + + var speed = args.OurBody.LinearVelocity.Length(); + + if (component.StaminaDamageOnCollide != null) + _stamina.TakeStaminaDamage(uid, component.StaminaDamageOnCollide.Value); + + var damageScale = speed; + + if (component.WallDamageOnCollide != null) + _damageable.TryChangeDamage(args.OtherEntity, component.WallDamageOnCollide * damageScale); + + _layingDown.TryLieDown(args.OtherEntity, behavior:DropHeldItemsBehavior.AlwaysDrop); + + _color.RaiseEffect(Color.Red, new List() { uid }, Filter.Pvs(uid, entityManager: EntityManager)); + } + + private void OnStopThrow(EntityUid uid, GrabThrownComponent comp, StopThrowEvent args) // We dont need this comp to exsist after fall + { + if (comp.DamageOnCollide != null) + _damageable.TryChangeDamage(uid, comp.DamageOnCollide); + + if (HasComp(uid)) + RemComp(uid); + } + + /// + /// Throwing entity to the direction and ensures GrabThrownComponent with params + /// + /// Entity to throw + /// Entity that throws + /// Direction + /// Stamina damage on collide + /// Damage to entity on collide + /// Damage to wall or anything that was hit by entity + public void Throw( + EntityUid uid, + EntityUid thrower, + Vector2 vector, + float? staminaDamage = null, + DamageSpecifier? damageToUid = null, + DamageSpecifier? damageToWall = null) + { + _throwing.TryThrow(uid, vector, 5f, animated: false); + + var comp = EnsureComp(uid); + comp.StaminaDamageOnCollide = staminaDamage; + comp.DamageOnCollide = damageToUid; + comp.WallDamageOnCollide = damageToWall; + comp.IgnoreEntity.Add(thrower); + + _layingDown.TryLieDown(uid, behavior:DropHeldItemsBehavior.AlwaysDrop); + } +} diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 47015bf21f5..3a163b35a5c 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -388,17 +388,37 @@ - type: alert id: Pulled - icons: [ /Textures/Interface/Alerts/Pull/pulled.png ] + icons: + - sprite: /Textures/Interface/Alerts/pull.rsi + state: pulled + - sprite: /Textures/Interface/Alerts/pull.rsi + state: grabbed-soft + - sprite: /Textures/Interface/Alerts/pull.rsi + state: grabbed-hard + - sprite: /Textures/Interface/Alerts/pull.rsi + state: grabbed-choke onClick: !type:StopBeingPulled { } name: alerts-pulled-name description: alerts-pulled-desc + minSeverity: 0 + maxSeverity: 3 - type: alert id: Pulling - icons: [ /Textures/Interface/Alerts/Pull/pulling.png ] + icons: + - sprite: /Textures/Interface/Alerts/pull.rsi + state: pulling + - sprite: /Textures/Interface/Alerts/pull.rsi + state: grab-soft + - sprite: /Textures/Interface/Alerts/pull.rsi + state: grab-hard + - sprite: /Textures/Interface/Alerts/pull.rsi + state: grab-choke onClick: !type:StopPulling { } name: alerts-pulling-name description: alerts-pulling-desc + minSeverity: 0 + maxSeverity: 3 - type: alert id: Bleed diff --git a/Resources/Textures/Interface/Alerts/pull.rsi/grab-choke.png b/Resources/Textures/Interface/Alerts/pull.rsi/grab-choke.png new file mode 100644 index 0000000000000000000000000000000000000000..085c7c389c88794038736d91f69ef53ac81e7969 GIT binary patch literal 1038 zcmV+p1o8WcP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1EWbqK~!i%?N?1` z6G0T-tdT0BMbkt?D7D&KFM9E!kVC2H!He|X!v;L$;Mt-GQoZU;h)D0bc@Tsi0-ihx zUK?sDh?q1=5HT&KalUci@UoqqnQeCCq5a_TX5Z}2H}8A%lXyos!VwNNa-rXcQt4t4 zHe=CheSPivUf6WTsQBXqkb^EhTdBdXJY1~sXMkdW<9kU&_`m#6RSeRH=f-2yc8!HY zJ0Us-nJbqr77o1+e&`1vmW=Xu9xT`3d5pKoM`QR7h6+&36@09$`LzNF#gu>dVyPlj ztWgYY7~;_w+7v^7^jlZZ`|w%wTUS6-Sttsl%J3N4rf*$g8Y6A~eCP+jmB%wzo2zMx zMZYyoA_}}eD*o7OP$1F4WkKR=P+%ndxkjTQ8|89YdV)|*PT>C1Q>o;ukrP`-k)Zt3nzgD1Ib=dU{G)7X16p08)|mJ`%@LBM>R}-EBd7xiq0vY__%% z@5i{ahxR@aABNv{2IvmK;PJ~hk{o*<__*1XUN;fM(4H<%cl?D| z$}c#LA}9f{97ugspH6-Pf2Mr~@_xUsVtf)XRAM)uk4-2FUVS?G3H&(;|Lp9nkzbtw zz>T@Nf1{;{&%WCYuFZ1VHkbY!)yH zpfaZ9uWoNEqvxnk((ejNer;_{89hgRl71VsCI2)wCM^!8KEU?94Kp({(&Aw118m=O zIIVeGHPO!#PFj5%Y}G_RPuR8kfRg|&73WD0Q+*v=D$bL3tv;X?0LhrDXsfX8dmE69 zsfxA=+rH;ex4@bC`P8oT86-U2}oWWwL=cD;7HZNCN~04AF@Zmq6r$)kV( zm~7gp*Xudce*q=Hd+|HKb+TDtVPT<<$%c<3!@w`W!w1j%3wp|FRM#EST>t<807*qo IM6N<$f<8s!l>h($ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/pull.rsi/grab-hard.png b/Resources/Textures/Interface/Alerts/pull.rsi/grab-hard.png new file mode 100644 index 0000000000000000000000000000000000000000..1e703705845540ff1b13fcdd70b9e1642143d0b2 GIT binary patch literal 716 zcmV;-0yF)IP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf0%1u+K~z{r?UcVu z12GiGb5$n?@mE0v5fx`QS6u|9yNg4S76b}tofQ;>inzKth-(KY!M~u0O2G=P zKS5W|d}Ch7<#N}YbrJnQc*$!o@B7|M!v6X5c+!SQr+ag98cDT!ci{RzISmmdKx~@b zIx4^(Eobxk8jue-{x4EQ2xP@K@nQ9$`)Ka!lUs*KNgIHdOxdZ$%>u0M)8}MOAHKst z1;}T^Du63i_H274&$B3y4|5FoO&{ju!|&#|HtJTu%nTV}P-S!<=5gQJXqGmBo;lWf zwt6;kKJ(is5s}xBQ}=o$An`RsCPKj6%7(~fe{)q`I|6NhMV7EUOZTfLiLJoe&S9aa zvt2CiAD`L!!mJX368Cp^FwgVYP4$uPdtw3BLkRS~A~=AL27%x}`V+S;NvBG;`3lT1ETksQ2$W{ zU=so#<^$bb?7H?KeDo%8>WT=o0NGQ2DL5GIYZC*3XdtjID8*A5;?)53`njBmfB^K6 zN^b)q2&zC!s!3RsACL(BrHsR@fUwA(PCh~FzSa-bX9V|sU-;pJh>??hC4%;gBX9;z zGnud58sPTrjlIk)iHf+q4N3rlL@^d3Y# zL;?23$K~t`tmlhGxeBj2$l57$s{ms&>)I6cU919Z8S7Xw{x(i6s7E6eQMW)Io`yNE yRM>H;WP9SR1O(vYMoAJp!1VC&k5u57!PqAx^sC)$14V@Z00008 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/pull.rsi/grab-soft.png b/Resources/Textures/Interface/Alerts/pull.rsi/grab-soft.png new file mode 100644 index 0000000000000000000000000000000000000000..03877d2c1f68754b5e566662c92249ed5e27b9b0 GIT binary patch literal 706 zcmV;z0zLhSP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf0#`{yK~z{r?UcJp z1Thqbv#Ww&A>I~55K(I@!56S_eF6oGMHIwFYj1_3wKgKG$acPgK7*h>!J@KwfmK&5 zY#sB*oN#6)Gn=^Eh<TVI5+xVDt)P{8sRDf(Y ztO3Y~jXv97UzAxC$%Zim_NWbGvf+Dlt&O@Bh-QY2AXFJ{!#HiNjaIEg@XRsi+45}C zY|*tzK_p&7N!{z^f%i2;CMtlX^&OST(e9?Xb_Ci0jVz&MmhM+glUjkTgOg%^PnViJ zy}pUIM;N0kWt5l5;RI*r^1LNZ^Uz;KcFD^OPNNr0!kx$I{5_62RlAZpW!?R0%eB{L3CfAnILGtG(uNn{1`%>sNvToc+B)J>dc6x!1;d(-qK>;2NEw9MJkID2zY+EoA4sef2lbaZsi zv9YPBsQmx`zmkPTR8$nG^;Oo_H9$(JB*-rqD2@sk9_`;R1eE10@Q5sCVBk9f!i-b3 z`J{mka`tp_4Ds-OJMnc{ivmvz<3@4T^4kB?bJI9DW^GG+|i*L=p@+%y$ zRQH#??tXnk%<*OYoR=+6&6-wV?{LOFaE-)FpIlK3on3uodyM~k1#4Z1&m_;M$vajWlVx8V1VGgD+a zZO^dD-m6^BHcDRcx9)z%In~wQm#Rd?3m!VY{Hc5c=cIQ5hbOz86zy79$GU^*fa!+` z#)={g>k_|6ConnQdusl0>k8MC`UmQMPCOf*DRLl5^Wvf~@0ntZ-bP26GR`i0AvodE zw%pE(PnjodK4ZN2w6X)=m9`W7wOIIJ;$;@!;rG#`tnbk3|gpP3Q7yI@;oBr;= zrDj%vzTK}p*E7gXdlCFIb7cif$<}3Cu8A;~mk4ioA1~^bEwg!+UJWpe89ZJ6T-G@y GGywoUklXzL literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/pull.rsi/grabbed-hard.png b/Resources/Textures/Interface/Alerts/pull.rsi/grabbed-hard.png new file mode 100644 index 0000000000000000000000000000000000000000..4d7d6a21443ddd4a27322bc4908030c0c8c139c4 GIT binary patch literal 367 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijS1AIbU6WSKkO`O*-d(-qK>s#h*X`H=zi;vF~2Z#Uv|A(uo0ackMehUOr z+$BMN!9YPMVDR#1`~(!{EbxddW?X?_wfUrhf(JZZ978Pp&-Moj9ai9QalAdF z;y?3r){rZHMwi#?IbZnN+48FSAB%$0$!&Qr=go?`W^kU7Gi3kO>%tpaf3jJuU0D6S zfbT(^;0Eofk=@!1Q-sRq6sxE?L>J79a%qro|I~Se;falR`7!B-6Zr)8-pldmThF85 zcuBUWtZ08Ivq78LOBZ$_j-R*kKV3Xl@E{^I?SaPZXOdo)g6cunCb`yLWNEps#h*X`H=zT2@v~M8yC9|2<7ifT~*5${T?c zcS(?6Fi;Q*7`*%$KLLd~3p^r=85sDEfH31!Z9ZwB;89N(#}Etuv;7yPniV)&9k=V; z|KG^YZn9x*UiptHHh&fb)?b*<;G&XU)BjY?XNl>VVh5+qgx~MQ3)e3zI=pwv-@alI}^dPT>hvQ(vwrZ}C=h_)@i5(ez#sldJ0G zsRvcQyPG-8Z&aK-!y(YA?6>c|W5zN!!nX3{yj}FC?b^3m|J4V@eH~^kY3bqJr{R0y z!n^zfFZR!G(6ny|Wv`jYd|c~>7W28!u55Q+FuwT9>~cPx%a!Eu%R9J=WRzZ&1Fcf`1QK8IJ0)WM{ zqS0DyD-RciFzS2XoeA6hH|AKPQA$l0zSU6m}s-w>g0{;b#c z?06tul{^c^SAj?50R+%9Uypz&^iemh5T-beRd$F0bVb1_my_eume7=x34ymhPtxlir?(t1CV7e;#-829*UjpVSrK_@YN+Bh9 z;`8N% z61AKPoKB|>^CC;AKD0P9u$bCO5RocEW>6QL1WW~l#P-nkzJ@x)kOCWNCxI^ypRMyn zme8&_VIEF;tEL1lvV_bO9Yg@=e{@_b==Gt<66(t$z>$V#VEyNm{Ld^Xx*B!Us&8ao ezls}f(D57UhsCckiQ=*V0000~XQCI&5Fa=<_yjabhaI<-O zwBzJg0~p8GroUT1;IFUyOSS6r@3xO|_|*W~_H@@i-{0&Ph=q}seG>j5E(gs)}HKq0V!pBFaX%gUdFcu5GB$6EdYF83&bJpX92K# z4|5sSQNP#3K`{W>+r{yT17$}p-)8~vr(S3gpb+ zfUF|9z{`!V0$>@C?@{3S6phAL0r)5^`>p9ezbT6fPM`S({oP+ie*(ZeQ+s$?6}p=6 P00000NkvXXu0mjfJ{9Ir literal 0 HcmV?d00001 From e075020e807fb42a80fee2300db5aa34128b9767 Mon Sep 17 00:00:00 2001 From: Lincoln McQueen Date: Wed, 28 Aug 2024 16:20:12 -0500 Subject: [PATCH 2/3] =?UTF-8?q?[Port]=20Lying=20Down=20System=20/=20=D0=A1?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D0=B0=20=D0=9B=D0=B5=D0=B6=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20(#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add: White lay down * fix: rotation * fix * fix: rotation * fix --- Content.Client/Buckle/BuckleSystem.cs | 1 - .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 9 + .../_White/Standing/LayingDownSystem.cs | 82 +++++++++ .../Standing/LayingDownComponent.cs | 14 -- Content.Server/Standing/LayingDownSystem.cs | 97 ++--------- .../Assorted/LayingDownModifierSystem.cs | 6 +- .../Standing/StandingStateComponent.cs | 45 +++-- .../Standing/StandingStateSystem.cs | 26 ++- Content.Shared/Stunnable/SharedStunSystem.cs | 21 ++- Content.Shared/_White/CVars.cs | 17 ++ .../_White/Standing/LayingDownComponent.cs | 25 +++ .../_White/Standing/SharedLayingDownSystem.cs | 162 ++++++++++++++++++ .../en-US/_white/escape-menu/options-menu.ftl | 3 + .../ru-RU/_white/escape-menu/options-menu.ftl | 3 + 14 files changed, 382 insertions(+), 129 deletions(-) create mode 100644 Content.Client/_White/Standing/LayingDownSystem.cs delete mode 100644 Content.Server/Standing/LayingDownComponent.cs create mode 100644 Content.Shared/_White/CVars.cs create mode 100644 Content.Shared/_White/Standing/LayingDownComponent.cs create mode 100644 Content.Shared/_White/Standing/SharedLayingDownSystem.cs create mode 100644 Resources/Locale/en-US/_white/escape-menu/options-menu.ftl create mode 100644 Resources/Locale/ru-RU/_white/escape-menu/options-menu.ftl diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index fea18e5cf3c..b899a881067 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -57,7 +57,6 @@ private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref Ap !buckled || args.Sprite == null) { - _rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation); return; } diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index 13e456985a7..02e9e99c6f5 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -103,6 +103,12 @@ private void HandleStaticStorageUI(BaseButton.ButtonToggledEventArgs args) _cfg.SaveToFile(); } + private void HandleToggleAutoGetUp(BaseButton.ButtonToggledEventArgs args) // WD EDIT + { + _cfg.SetCVar(WhiteCVars.AutoGetUp, args.Pressed); + _cfg.SaveToFile(); + } + public KeyRebindTab() { IoCManager.InjectDependencies(this); @@ -186,6 +192,9 @@ void AddCheckBox(string checkBoxName, bool currentState, Action(OnMovementInput); + + SubscribeNetworkEvent(OnCheckAutoGetUp); + } + + private void OnMovementInput(EntityUid uid, LayingDownComponent component, MoveEvent args) + { + if (!_timing.IsFirstTimePredicted) + return; + + if (!_standing.IsDown(uid)) + return; + + if (_buckle.IsBuckled(uid)) + return; + + if (_animation.HasRunningAnimation(uid, "rotate")) + return; + + if (!TryComp(uid, out var transform) + || !TryComp(uid, out var sprite) + || !TryComp(uid, out var rotationVisuals)) + { + return; + } + + var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation)); + + if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North) + { + rotationVisuals.HorizontalRotation = Angle.FromDegrees(270); + sprite.Rotation = Angle.FromDegrees(270); + return; + } + + rotationVisuals.HorizontalRotation = Angle.FromDegrees(90); + sprite.Rotation = Angle.FromDegrees(90); + } + + private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args) + { + if (!_timing.IsFirstTimePredicted) + return; + + var uid = GetEntity(ev.User); + + if (!TryComp(uid, out var transform) || !TryComp(uid, out var rotationVisuals)) + return; + + var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation)); + + if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North) + { + rotationVisuals.HorizontalRotation = Angle.FromDegrees(270); + return; + } + + rotationVisuals.HorizontalRotation = Angle.FromDegrees(90); + } +} diff --git a/Content.Server/Standing/LayingDownComponent.cs b/Content.Server/Standing/LayingDownComponent.cs deleted file mode 100644 index 7921749f148..00000000000 --- a/Content.Server/Standing/LayingDownComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Content.Server.Standing; - -[RegisterComponent] -public sealed partial class LayingDownComponent : Component -{ - [DataField] - public float DownedSpeedMultiplier = 0.15f; - - [DataField] - public TimeSpan Cooldown = TimeSpan.FromSeconds(2.5f); - - [DataField] - public TimeSpan NextToggleAttempt = TimeSpan.Zero; -} diff --git a/Content.Server/Standing/LayingDownSystem.cs b/Content.Server/Standing/LayingDownSystem.cs index 73a929fdfc4..eccbf0773b2 100644 --- a/Content.Server/Standing/LayingDownSystem.cs +++ b/Content.Server/Standing/LayingDownSystem.cs @@ -1,101 +1,28 @@ -using Content.Shared.ActionBlocker; -using Content.Shared.Input; -using Content.Shared.Movement.Systems; -using Content.Shared.Popups; -using Content.Shared.Standing; -using Robust.Shared.Input.Binding; -using Robust.Shared.Player; -using Robust.Shared.Timing; +using Content.Shared._White; +using Content.Shared._White.Standing; +using Robust.Shared.Configuration; namespace Content.Server.Standing; -/// Unfortunately cannot be shared because some standing conditions are server-side only -public sealed class LayingDownSystem : EntitySystem +public sealed class LayingDownSystem : SharedLayingDownSystem // WD EDIT { - [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly Shared.Standing.StandingStateSystem _standing = default!; // WHY IS THERE TWO DIFFERENT STANDING SYSTEMS?! - [Dependency] private readonly IGameTiming _timing = default!; - + [Dependency] private readonly INetConfigurationManager _cfg = default!; public override void Initialize() { - CommandBinds.Builder - .Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding, handle: false, outsidePrediction: false)) - .Register(); - - SubscribeLocalEvent(DoRefreshMovementSpeed); - SubscribeLocalEvent(DoRefreshMovementSpeed); - SubscribeLocalEvent(OnRefreshMovementSpeed); - SubscribeLocalEvent(OnParentChanged); - } - - public override void Shutdown() - { - base.Shutdown(); - - CommandBinds.Unregister(); - } + base.Initialize(); - private void DoRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, object args) - { - _movement.RefreshMovementSpeedModifiers(uid); + SubscribeNetworkEvent(OnCheckAutoGetUp); } - private void OnRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, RefreshMovementSpeedModifiersEvent args) + private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args) { - if (TryComp(uid, out var standingState) && standingState.Standing) - return; + var uid = GetEntity(ev.User); - args.ModifySpeed(component.DownedSpeedMultiplier, component.DownedSpeedMultiplier, bypassImmunity: true); - } - - private void OnParentChanged(EntityUid uid, LayingDownComponent component, EntParentChangedMessage args) - { - // If the entity is not on a grid, try to make it stand up to avoid issues - if (!TryComp(uid, out var standingState) - || standingState.Standing - || Transform(uid).GridUid != null) + if (!TryComp(uid, out LayingDownComponent? layingDown)) return; - _standing.Stand(uid, standingState); - } - - private void ToggleStanding(ICommonSession? session) - { - if (session is not { AttachedEntity: { Valid: true } uid } playerSession - || !Exists(uid) - || !TryComp(uid, out var standingState) - || !TryComp(uid, out var layingDown)) - return; - - // If successful, show popup to self and others. Otherwise, only to self. - if (ToggleStandingImpl(uid, standingState, layingDown, out var popupBranch)) - { - _popups.PopupEntity(Loc.GetString($"laying-comp-{popupBranch}-other", ("entity", uid)), uid, Filter.PvsExcept(uid), true); - layingDown.NextToggleAttempt = _timing.CurTime + layingDown.Cooldown; - } - - _popups.PopupEntity(Loc.GetString($"laying-comp-{popupBranch}-self", ("entity", uid)), uid, uid); - } - - private bool ToggleStandingImpl(EntityUid uid, StandingStateComponent standingState, LayingDownComponent layingDown, out string popupBranch) - { - var success = layingDown.NextToggleAttempt <= _timing.CurTime; - - if (_standing.IsDown(uid, standingState)) - { - success = success && _standing.Stand(uid, standingState, force: false); - popupBranch = success ? "stand-success" : "stand-fail"; - } - else - { - success = success && Transform(uid).GridUid != null; // Do not allow laying down when not on a surface. - success = success && _standing.Down(uid, standingState: standingState, playSound: true, dropHeldItems: false); - popupBranch = success ? "lay-success" : "lay-fail"; - } - - return success; + layingDown.AutoGetUp = _cfg.GetClientCVar(args.SenderSession.Channel, WhiteCVars.AutoGetUp); + Dirty(uid, layingDown); } } diff --git a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs index dc6dcd2de3b..7d2c16e3c44 100644 --- a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs +++ b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs @@ -1,5 +1,5 @@ using Content.Server.Traits.Assorted; -using Content.Server.Standing; +using Content.Shared._White.Standing; namespace Content.Shared.Traits.Assorted.Systems; @@ -16,7 +16,7 @@ private void OnStartup(EntityUid uid, LayingDownModifierComponent component, Com if (!TryComp(uid, out var layingDown)) return; - layingDown.Cooldown *= component.LayingDownCooldownMultiplier; - layingDown.DownedSpeedMultiplier *= component.DownedSpeedMultiplierMultiplier; + layingDown.StandingUpTime *= component.LayingDownCooldownMultiplier; + layingDown.SpeedModify *= component.DownedSpeedMultiplierMultiplier; } } diff --git a/Content.Shared/Standing/StandingStateComponent.cs b/Content.Shared/Standing/StandingStateComponent.cs index 5d7bb0a59fd..3a7cb2a008f 100644 --- a/Content.Shared/Standing/StandingStateComponent.cs +++ b/Content.Shared/Standing/StandingStateComponent.cs @@ -1,24 +1,35 @@ using Robust.Shared.Audio; using Robust.Shared.GameStates; -namespace Content.Shared.Standing +namespace Content.Shared.Standing; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class StandingStateComponent : Component { - [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] - [Access(typeof(StandingStateSystem))] - public sealed partial class StandingStateComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); + + // WD EDIT START + [DataField, AutoNetworkedField] + public StandingState CurrentState { get; set; } = StandingState.Standing; + // WD EDIT END - [DataField, AutoNetworkedField] - public bool Standing { get; set; } = true; + [DataField, AutoNetworkedField] + public bool Standing { get; set; } = true; - /// - /// List of fixtures that had their collision mask changed when the entity was downed. - /// Required for re-adding the collision mask. - /// - [DataField, AutoNetworkedField] - public List ChangedFixtures = new(); - } + /// + /// List of fixtures that had their collision mask changed when the entity was downed. + /// Required for re-adding the collision mask. + /// + [DataField, AutoNetworkedField] + public List ChangedFixtures = new(); +} +// WD EDIT START +public enum StandingState +{ + Lying, + GettingUp, + Standing, } +// WD EDIT END diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs index 517831b8a1b..212e1d06d10 100644 --- a/Content.Shared/Standing/StandingStateSystem.cs +++ b/Content.Shared/Standing/StandingStateSystem.cs @@ -1,7 +1,9 @@ +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; using Content.Shared.Hands.Components; +using Content.Shared.Movement.Systems; using Content.Shared.Physics; using Content.Shared.Rotation; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; @@ -13,6 +15,8 @@ public sealed class StandingStateSystem : EntitySystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; // WD EDIT + [Dependency] private readonly SharedBuckleSystem _buckle = default!; // WD EDIT // If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited. private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable; @@ -22,7 +26,7 @@ public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) if (!Resolve(uid, ref standingState, false)) return false; - return !standingState.Standing; + return standingState.CurrentState is StandingState.Lying or StandingState.GettingUp; } public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, @@ -37,7 +41,7 @@ public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true // Optional component. Resolve(uid, ref appearance, ref hands, false); - if (!standingState.Standing) + if (standingState.CurrentState is StandingState.Lying or StandingState.GettingUp) return true; // This is just to avoid most callers doing this manually saving boilerplate @@ -49,13 +53,16 @@ public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true RaiseLocalEvent(uid, new DropHandItemsEvent(), false); } + if (TryComp(uid, out BuckleComponent? buckle) && buckle.Buckled && !_buckle.TryUnbuckle(uid, uid, buckleComp: buckle)) // WD EDIT + return false; + var msg = new DownAttemptEvent(); RaiseLocalEvent(uid, msg, false); if (msg.Cancelled) return false; - standingState.Standing = false; + standingState.CurrentState = StandingState.Lying; Dirty(standingState); RaiseLocalEvent(uid, new DownedEvent(), false); @@ -82,9 +89,10 @@ public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true if (playSound) { - _audio.PlayPredicted(standingState.DownSound, uid, uid); + _audio.PlayPredicted(standingState.DownSound, uid, null); } + _movement.RefreshMovementSpeedModifiers(uid); // WD EDIT return true; } @@ -100,9 +108,12 @@ public bool Stand(EntityUid uid, // Optional component. Resolve(uid, ref appearance, false); - if (standingState.Standing) + if (standingState.CurrentState is StandingState.Standing) return true; + if (TryComp(uid, out BuckleComponent? buckle) && buckle.Buckled && !_buckle.TryUnbuckle(uid, uid, buckleComp: buckle)) // WD EDIT + return false; + if (!force) { var msg = new StandAttemptEvent(); @@ -112,7 +123,7 @@ public bool Stand(EntityUid uid, return false; } - standingState.Standing = true; + standingState.CurrentState = StandingState.Standing; Dirty(uid, standingState); RaiseLocalEvent(uid, new StoodEvent(), false); @@ -127,6 +138,7 @@ public bool Stand(EntityUid uid, } } standingState.ChangedFixtures.Clear(); + _movement.RefreshMovementSpeedModifiers(uid); // WD EDIT return true; } diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index c447f8c8bc7..3f6ef8999d7 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared._White.Standing; using Content.Shared.ActionBlocker; using Content.Shared.Administration.Logs; using Content.Shared.Audio; @@ -19,6 +20,7 @@ using Content.Shared.Throwing; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Player; @@ -32,6 +34,8 @@ public abstract class SharedStunSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly StandingStateSystem _standingState = default!; [Dependency] private readonly StatusEffectsSystem _statusEffect = default!; + [Dependency] private readonly SharedLayingDownSystem _layingDown = default!; // WD EDIT + [Dependency] private readonly SharedContainerSystem _container = default!; // WD EDIT /// /// Friction modifier for knocked down players. @@ -109,12 +113,25 @@ private void UpdateCanMove(EntityUid uid, StunnedComponent component, EntityEven private void OnKnockInit(EntityUid uid, KnockedDownComponent component, ComponentInit args) { - _standingState.Down(uid); + RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid))); // WD EDIT + _layingDown.TryLieDown(uid, null, null, DropHeldItemsBehavior.DropIfStanding); // WD EDIT } private void OnKnockShutdown(EntityUid uid, KnockedDownComponent component, ComponentShutdown args) { - _standingState.Stand(uid); + // WD EDIT START + if (!TryComp(uid, out StandingStateComponent? standing)) + return; + + if (TryComp(uid, out LayingDownComponent? layingDown)) + { + if (layingDown.AutoGetUp && !_container.IsEntityInContainer(uid)) + _layingDown.TryStandUp(uid, layingDown); + return; + } + + _standingState.Stand(uid, standing); + // WD EDIT END } private void OnStandAttempt(EntityUid uid, KnockedDownComponent component, StandAttemptEvent args) diff --git a/Content.Shared/_White/CVars.cs b/Content.Shared/_White/CVars.cs new file mode 100644 index 00000000000..3748706b39d --- /dev/null +++ b/Content.Shared/_White/CVars.cs @@ -0,0 +1,17 @@ +using Robust.Shared.Configuration; + +namespace Content.Shared._White; + +[CVarDefs] +public sealed class WhiteCVars +{ + #region Keybind + + public static readonly CVarDef AutoGetUp = + CVarDef.Create("white.auto_get_up", true, CVar.CLIENT | CVar.ARCHIVE | CVar.REPLICATED); + + public static readonly CVarDef HoldLookUp = + CVarDef.Create("white.hold_look_up", false, CVar.CLIENT | CVar.ARCHIVE); + + #endregion +} diff --git a/Content.Shared/_White/Standing/LayingDownComponent.cs b/Content.Shared/_White/Standing/LayingDownComponent.cs new file mode 100644 index 00000000000..6dce32ac241 --- /dev/null +++ b/Content.Shared/_White/Standing/LayingDownComponent.cs @@ -0,0 +1,25 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared._White.Standing; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class LayingDownComponent : Component +{ + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float StandingUpTime { get; set; } = 1f; + + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float SpeedModify { get; set; } = 0.4f; + + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public bool AutoGetUp; +} +[Serializable, NetSerializable] +public sealed class ChangeLayingDownEvent : CancellableEntityEventArgs; + +[Serializable, NetSerializable] +public sealed class CheckAutoGetUpEvent(NetEntity user) : CancellableEntityEventArgs +{ + public NetEntity User = user; +} diff --git a/Content.Shared/_White/Standing/SharedLayingDownSystem.cs b/Content.Shared/_White/Standing/SharedLayingDownSystem.cs new file mode 100644 index 00000000000..2406d19a37c --- /dev/null +++ b/Content.Shared/_White/Standing/SharedLayingDownSystem.cs @@ -0,0 +1,162 @@ +using Content.Shared.DoAfter; +using Content.Shared.Gravity; +using Content.Shared.Input; +using Content.Shared.Mobs.Systems; +using Content.Shared.Movement.Systems; +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Robust.Shared.Input.Binding; +using Robust.Shared.Player; +using Robust.Shared.Serialization; + +namespace Content.Shared._White.Standing; + +public abstract class SharedLayingDownSystem : EntitySystem +{ + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + + public override void Initialize() + { + CommandBinds.Builder + .Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding)) + .Register(); + + SubscribeNetworkEvent(OnChangeState); + + SubscribeLocalEvent(OnStandingUpDoAfter); + SubscribeLocalEvent(OnRefreshMovementSpeed); + SubscribeLocalEvent(OnParentChanged); + } + + public override void Shutdown() + { + base.Shutdown(); + + CommandBinds.Unregister(); + } + + private void ToggleStanding(ICommonSession? session) + { + if (session?.AttachedEntity == null || + !HasComp(session.AttachedEntity) || + _gravity.IsWeightless(session.AttachedEntity.Value)) + { + return; + } + + RaiseNetworkEvent(new ChangeLayingDownEvent()); + } + + private void OnChangeState(ChangeLayingDownEvent ev, EntitySessionEventArgs args) + { + if (!args.SenderSession.AttachedEntity.HasValue) + return; + + var uid = args.SenderSession.AttachedEntity.Value; + + // TODO: Wizard + //if (HasComp(uid)) + // return; + + if (!TryComp(uid, out StandingStateComponent? standing) || + !TryComp(uid, out LayingDownComponent? layingDown)) + { + return; + } + + RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid))); + + if (HasComp(uid) || !_mobState.IsAlive(uid)) + return; + + if (_standing.IsDown(uid, standing)) + TryStandUp(uid, layingDown, standing); + else + TryLieDown(uid, layingDown, standing); + } + + private void OnStandingUpDoAfter(EntityUid uid, StandingStateComponent component, StandingUpDoAfterEvent args) + { + if (args.Handled || args.Cancelled || HasComp(uid) || + _mobState.IsIncapacitated(uid) || !_standing.Stand(uid)) + { + component.CurrentState = StandingState.Lying; + } + + component.CurrentState = StandingState.Standing; + } + + private void OnRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (_standing.IsDown(uid)) + args.ModifySpeed(component.SpeedModify, component.SpeedModify); + else + args.ModifySpeed(1f, 1f); + } + + private void OnParentChanged(EntityUid uid, LayingDownComponent component, EntParentChangedMessage args) + { + // If the entity is not on a grid, try to make it stand up to avoid issues + if (!TryComp(uid, out var standingState) + || standingState.CurrentState is StandingState.Standing + || Transform(uid).GridUid != null) + { + return; + } + + _standing.Stand(uid, standingState); + } + + public bool TryStandUp(EntityUid uid, LayingDownComponent? layingDown = null, StandingStateComponent? standingState = null) + { + if (!Resolve(uid, ref standingState, false) || + !Resolve(uid, ref layingDown, false) || + standingState.CurrentState is not StandingState.Lying || + !_mobState.IsAlive(uid) || + TerminatingOrDeleted(uid)) + { + return false; + } + + var args = new DoAfterArgs(EntityManager, uid, layingDown.StandingUpTime, new StandingUpDoAfterEvent(), uid) + { + BreakOnHandChange = false, + RequireCanInteract = false + }; + + if (!_doAfter.TryStartDoAfter(args)) + return false; + + standingState.CurrentState = StandingState.GettingUp; + return true; + } + + public bool TryLieDown(EntityUid uid, LayingDownComponent? layingDown = null, StandingStateComponent? standingState = null, DropHeldItemsBehavior behavior = DropHeldItemsBehavior.NoDrop) + { + if (!Resolve(uid, ref standingState, false) || + !Resolve(uid, ref layingDown, false) || + standingState.CurrentState is not StandingState.Standing) + { + if (behavior == DropHeldItemsBehavior.AlwaysDrop) + RaiseLocalEvent(uid, new DropHandItemsEvent()); + + return false; + } + + _standing.Down(uid, true, behavior != DropHeldItemsBehavior.NoDrop, standingState); + return true; + } +} + +[Serializable, NetSerializable] +public sealed partial class StandingUpDoAfterEvent : SimpleDoAfterEvent; + +public enum DropHeldItemsBehavior : byte +{ + NoDrop, + DropIfStanding, + AlwaysDrop +} diff --git a/Resources/Locale/en-US/_white/escape-menu/options-menu.ftl b/Resources/Locale/en-US/_white/escape-menu/options-menu.ftl new file mode 100644 index 00000000000..c2ebf318444 --- /dev/null +++ b/Resources/Locale/en-US/_white/escape-menu/options-menu.ftl @@ -0,0 +1,3 @@ +ui-options-function-look-up = Look up/Take aim +ui-options-function-auto-get-up = Automatically get up after falling +ui-options-function-hold-look-up = Hold down the key to aim \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_white/escape-menu/options-menu.ftl b/Resources/Locale/ru-RU/_white/escape-menu/options-menu.ftl new file mode 100644 index 00000000000..eebde7a272a --- /dev/null +++ b/Resources/Locale/ru-RU/_white/escape-menu/options-menu.ftl @@ -0,0 +1,3 @@ +ui-options-function-look-up = Присмотреться/Прицелиться +ui-options-function-auto-get-up = Автоматически вставать при падении +ui-options-function-hold-look-up = Удерживать клавишу для прицеливания \ No newline at end of file From 3b97a83308f51e0259dde58f80b9d0ae52b4ff85 Mon Sep 17 00:00:00 2001 From: Lincoln McQueen Date: Wed, 28 Aug 2024 18:22:57 -0500 Subject: [PATCH 3/3] Fixed keyrebindtab --- Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index 02e9e99c6f5..fab0cbe1c00 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -1,5 +1,7 @@ using System.Numerics; using Content.Client.Stylesheets; +using Content.Client.Options.UI.Tabs; +using Content.Shared._White; using Content.Shared.CCVar; using Content.Shared.Input; using Robust.Client.AutoGenerated; @@ -192,9 +194,7 @@ void AddCheckBox(string checkBoxName, bool currentState, Action