From d5ae5658a12bbb3831c4153844758abd674d5e84 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Mon, 14 Nov 2022 20:30:30 +0100 Subject: [PATCH] ECS buckle (#12586) --- Content.Client/Buckle/BuckleComponent.cs | 64 +-- Content.Client/Buckle/BuckleSystem.cs | 49 ++ .../Tests/Buckle/BuckleTest.cs | 54 +- Content.Server/Alert/Click/Unbuckle.cs | 7 +- .../Buckle/Components/BuckleComponent.cs | 385 +------------ .../Buckle/Components/StrapComponent.cs | 16 +- Content.Server/Buckle/Systems/BuckleSystem.cs | 541 ++++++++++++++---- Content.Server/Buckle/Systems/StrapSystem.cs | 15 +- .../Polymorph/Systems/PolymorphableSystem.cs | 6 +- Content.Server/Vehicle/VehicleSystem.Rider.cs | 10 +- Content.Server/Vehicle/VehicleSystem.cs | 19 +- .../Components/SharedBuckleComponent.cs | 140 ++--- Content.Shared/Buckle/SharedBuckleSystem.cs | 222 ++++--- 13 files changed, 752 insertions(+), 776 deletions(-) diff --git a/Content.Client/Buckle/BuckleComponent.cs b/Content.Client/Buckle/BuckleComponent.cs index dd36dcf454..d1e224312b 100644 --- a/Content.Client/Buckle/BuckleComponent.cs +++ b/Content.Client/Buckle/BuckleComponent.cs @@ -1,7 +1,4 @@ -using Content.Shared.ActionBlocker; using Content.Shared.Buckle.Components; -using Content.Shared.Vehicle.Components; -using Robust.Client.GameObjects; namespace Content.Client.Buckle { @@ -9,65 +6,6 @@ namespace Content.Client.Buckle [ComponentReference(typeof(SharedBuckleComponent))] public sealed class BuckleComponent : SharedBuckleComponent { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IEntitySystemManager _sysMan = default!; - - private bool _buckled; - private int? _originalDrawDepth; - - public override bool Buckled => _buckled; - - public override bool TryBuckle(EntityUid user, EntityUid to) - { - // TODO: Prediction - return false; - } - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - if (curState is not BuckleComponentState buckle) - { - return; - } - - _buckled = buckle.Buckled; - LastEntityBuckledTo = buckle.LastEntityBuckledTo; - DontCollide = buckle.DontCollide; - - _sysMan.GetEntitySystem().UpdateCanMove(Owner); - - if (!_entMan.TryGetComponent(Owner, out SpriteComponent? ownerSprite)) - { - return; - } - - if (LastEntityBuckledTo != null && _entMan.HasComponent(LastEntityBuckledTo)) - { - return; - } - - // Adjust draw depth when the chair faces north so that the seat back is drawn over the player. - // Reset the draw depth when rotated in any other direction. - // TODO when ECSing, make this a visualizer - // This code was written before rotatable viewports were introduced, so hard-coding Direction.North - // and comparing it against LocalRotation now breaks this in other rotations. This is a FIXME, but - // better to get it working for most people before we look at a more permanent solution. - if (_buckled && - LastEntityBuckledTo != null && - EntMan.GetComponent(LastEntityBuckledTo.Value).LocalRotation.GetCardinalDir() == Direction.North && - EntMan.TryGetComponent(LastEntityBuckledTo, out var buckledSprite)) - { - _originalDrawDepth ??= ownerSprite.DrawDepth; - ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1; - return; - } - - // If here, we're not turning north and should restore the saved draw depth. - if (_originalDrawDepth.HasValue) - { - ownerSprite.DrawDepth = _originalDrawDepth.Value; - _originalDrawDepth = null; - } - } + public int? OriginalDrawDepth { get; set; } } } diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index 90f13f3854..baee2f31ae 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -1,18 +1,67 @@ using Content.Client.Buckle.Strap; +using Content.Shared.ActionBlocker; using Content.Shared.Buckle; using Content.Shared.Buckle.Components; +using Content.Shared.Vehicle.Components; +using Robust.Client.GameObjects; using Robust.Shared.GameStates; namespace Content.Client.Buckle { internal sealed class BuckleSystem : SharedBuckleSystem { + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + public override void Initialize() { base.Initialize(); + + SubscribeLocalEvent(OnBuckleHandleState); + SubscribeLocalEvent(OnStrapHandleState); } + private void OnBuckleHandleState(EntityUid uid, BuckleComponent buckle, ref ComponentHandleState args) + { + if (args.Current is not BuckleComponentState state) + return; + + buckle.Buckled = state.Buckled; + buckle.LastEntityBuckledTo = state.LastEntityBuckledTo; + buckle.DontCollide = state.DontCollide; + + _actionBlocker.UpdateCanMove(uid); + + if (!TryComp(uid, out SpriteComponent? ownerSprite)) + return; + + if (HasComp(buckle.LastEntityBuckledTo)) + return; + + // Adjust draw depth when the chair faces north so that the seat back is drawn over the player. + // Reset the draw depth when rotated in any other direction. + // TODO when ECSing, make this a visualizer + // This code was written before rotatable viewports were introduced, so hard-coding Direction.North + // and comparing it against LocalRotation now breaks this in other rotations. This is a FIXME, but + // better to get it working for most people before we look at a more permanent solution. + if (buckle.Buckled && + buckle.LastEntityBuckledTo != null && + Transform(buckle.LastEntityBuckledTo.Value).LocalRotation.GetCardinalDir() == Direction.North && + TryComp(buckle.LastEntityBuckledTo, out var buckledSprite)) + { + buckle.OriginalDrawDepth ??= ownerSprite.DrawDepth; + ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1; + return; + } + + // If here, we're not turning north and should restore the saved draw depth. + if (buckle.OriginalDrawDepth.HasValue) + { + ownerSprite.DrawDepth = buckle.OriginalDrawDepth.Value; + buckle.OriginalDrawDepth = null; + } + } + private void OnStrapHandleState(EntityUid uid, StrapComponent component, ref ComponentHandleState args) { if (args.Current is not StrapComponentState state) return; diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index 9fec6fe485..7dce7b339c 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -1,11 +1,11 @@ using System.Threading.Tasks; using Content.Server.Body.Systems; using Content.Server.Buckle.Components; +using Content.Server.Buckle.Systems; using Content.Server.Hands.Components; using Content.Shared.ActionBlocker; using Content.Shared.Body.Components; using Content.Shared.Body.Part; -using Content.Shared.Buckle.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Standing; using NUnit.Framework; @@ -57,9 +57,9 @@ namespace Content.IntegrationTests.Tests.Buckle var coordinates = testMap.GridCoords; var entityManager = server.ResolveDependency(); var actionBlocker = entityManager.EntitySysManager.GetEntitySystem(); + var buckleSystem = entityManager.EntitySysManager.GetEntitySystem(); var standingState = entityManager.EntitySysManager.GetEntitySystem(); - EntityUid human = default; EntityUid chair = default; BuckleComponent buckle = null; @@ -87,18 +87,17 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.Zero(strap.OccupiedSize); // Side effects of buckling - Assert.True(buckle.TryBuckle(human, chair)); + Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle)); Assert.NotNull(buckle.BuckledTo); Assert.True(buckle.Buckled); - Assert.True(((BuckleComponentState) buckle.GetComponentState()).Buckled); Assert.False(actionBlocker.CanMove(human)); Assert.False(actionBlocker.CanChangeDirection(human)); Assert.False(standingState.Down(human)); Assert.That( (entityManager.GetComponent(human).WorldPosition - entityManager.GetComponent(chair).WorldPosition).Length, - Is.LessThanOrEqualTo(buckle.BuckleOffset.Length)); + Is.LessThanOrEqualTo(0)); // Side effects of buckling for the strap Assert.That(strap.BuckledEntities, Does.Contain(human)); @@ -106,11 +105,12 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.Positive(strap.OccupiedSize); // Trying to buckle while already buckled fails - Assert.False(buckle.TryBuckle(human, chair)); + Assert.False(buckleSystem.TryBuckle(human, human, chair, buckle)); // Trying to unbuckle too quickly fails - Assert.False(buckle.TryUnbuckle(human)); - Assert.False(buckle.ToggleBuckle(human, chair)); + Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle)); + Assert.True(buckle.Buckled); + Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle)); Assert.True(buckle.Buckled); }); @@ -123,7 +123,7 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.True(buckle.Buckled); // Unbuckle - Assert.True(buckle.TryUnbuckle(human)); + Assert.True(buckleSystem.TryUnbuckle(human, human, buckle: buckle)); Assert.Null(buckle.BuckledTo); Assert.False(buckle.Buckled); Assert.True(actionBlocker.CanMove(human)); @@ -135,15 +135,15 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.Zero(strap.OccupiedSize); // Re-buckling has no cooldown - Assert.True(buckle.TryBuckle(human, chair)); + Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle)); Assert.True(buckle.Buckled); // On cooldown - Assert.False(buckle.TryUnbuckle(human)); + Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle)); Assert.True(buckle.Buckled); - Assert.False(buckle.ToggleBuckle(human, chair)); + Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle)); Assert.True(buckle.Buckled); - Assert.False(buckle.ToggleBuckle(human, chair)); + Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle)); Assert.True(buckle.Buckled); }); @@ -156,38 +156,38 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.True(buckle.Buckled); // Unbuckle - Assert.True(buckle.TryUnbuckle(human)); + Assert.True(buckleSystem.TryUnbuckle(human, human, buckle: buckle)); Assert.False(buckle.Buckled); // Move away from the chair entityManager.GetComponent(human).WorldPosition += (1000, 1000); // Out of range - Assert.False(buckle.TryBuckle(human, chair)); - Assert.False(buckle.TryUnbuckle(human)); - Assert.False(buckle.ToggleBuckle(human, chair)); + Assert.False(buckleSystem.TryBuckle(human, human, chair, buckle: buckle)); + Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle)); + Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle)); // Move near the chair entityManager.GetComponent(human).WorldPosition = entityManager.GetComponent(chair).WorldPosition + (0.5f, 0); // In range - Assert.True(buckle.TryBuckle(human, chair)); + Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle)); Assert.True(buckle.Buckled); - Assert.False(buckle.TryUnbuckle(human)); + Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle)); Assert.True(buckle.Buckled); - Assert.False(buckle.ToggleBuckle(human, chair)); + Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle)); Assert.True(buckle.Buckled); // Force unbuckle - Assert.True(buckle.TryUnbuckle(human, true)); + Assert.True(buckleSystem.TryUnbuckle(human, human, true, buckle: buckle)); Assert.False(buckle.Buckled); Assert.True(actionBlocker.CanMove(human)); Assert.True(actionBlocker.CanChangeDirection(human)); Assert.True(standingState.Down(human)); // Re-buckle - Assert.True(buckle.TryBuckle(human, chair)); + Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle)); // Move away from the chair entityManager.GetComponent(human).WorldPosition += (1, 0); @@ -225,6 +225,7 @@ namespace Content.IntegrationTests.Tests.Buckle var entityManager = server.ResolveDependency(); var handsSys = entityManager.EntitySysManager.GetEntitySystem(); + var buckleSystem = entityManager.EntitySysManager.GetEntitySystem(); await server.WaitAssertion(() => { @@ -238,7 +239,7 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.True(entityManager.TryGetComponent(human, out body)); // Buckle - Assert.True(buckle.TryBuckle(human, chair)); + Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle)); Assert.NotNull(buckle.BuckledTo); Assert.True(buckle.Buckled); @@ -287,7 +288,7 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.Null(hand.HeldEntity); } - buckle.TryUnbuckle(human, true); + buckleSystem.TryUnbuckle(human, human, true, buckle: buckle); }); await pairTracker.CleanReturnAsync(); @@ -303,6 +304,7 @@ namespace Content.IntegrationTests.Tests.Buckle var testMap = await PoolManager.CreateTestMap(pairTracker); var coordinates = testMap.GridCoords; var entityManager = server.ResolveDependency(); + var buckleSystem = entityManager.System(); EntityUid human = default; EntityUid chair = default; @@ -318,7 +320,7 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.True(entityManager.HasComponent(chair)); // Buckle - Assert.True(buckle.TryBuckle(human, chair)); + Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle)); Assert.NotNull(buckle.BuckledTo); Assert.True(buckle.Buckled); @@ -336,7 +338,7 @@ namespace Content.IntegrationTests.Tests.Buckle entityManager.GetComponent(human).WorldPosition -= (100, 0); // Buckle - Assert.True(buckle.TryBuckle(human, chair)); + Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle)); Assert.NotNull(buckle.BuckledTo); Assert.True(buckle.Buckled); }); diff --git a/Content.Server/Alert/Click/Unbuckle.cs b/Content.Server/Alert/Click/Unbuckle.cs index 6c3f27c508..8707b14ed3 100644 --- a/Content.Server/Alert/Click/Unbuckle.cs +++ b/Content.Server/Alert/Click/Unbuckle.cs @@ -1,4 +1,4 @@ -using Content.Server.Buckle.Components; +using Content.Server.Buckle.Systems; using Content.Shared.Alert; using JetBrains.Annotations; @@ -13,10 +13,7 @@ namespace Content.Server.Alert.Click { public void AlertClicked(EntityUid player) { - if (IoCManager.Resolve().TryGetComponent(player, out BuckleComponent? buckle)) - { - buckle.TryUnbuckle(player); - } + IoCManager.Resolve().System().TryUnbuckle(player, player); } } } diff --git a/Content.Server/Buckle/Components/BuckleComponent.cs b/Content.Server/Buckle/Components/BuckleComponent.cs index 2b7dd9db52..78ddca294b 100644 --- a/Content.Server/Buckle/Components/BuckleComponent.cs +++ b/Content.Server/Buckle/Components/BuckleComponent.cs @@ -1,365 +1,38 @@ -using Content.Server.Hands.Components; -using Content.Server.Pulling; -using Content.Shared.ActionBlocker; -using Content.Shared.Alert; -using Content.Shared.Bed.Sleep; using Content.Shared.Buckle.Components; -using Content.Shared.IdentityManagement; -using Content.Shared.Interaction; -using Content.Shared.MobState.Components; -using Content.Shared.MobState.EntitySystems; -using Content.Shared.Popups; -using Content.Shared.Pulling.Components; -using Content.Shared.Standing; -using Content.Shared.Stunnable; -using Content.Shared.Vehicle.Components; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.Player; -using Robust.Shared.Timing; -using System.Diagnostics.CodeAnalysis; -namespace Content.Server.Buckle.Components +namespace Content.Server.Buckle.Components; + +/// +/// Component that handles sitting entities into s. +/// +[RegisterComponent] +[ComponentReference(typeof(SharedBuckleComponent))] +public sealed class BuckleComponent : SharedBuckleComponent { /// - /// Component that handles sitting entities into s. + /// The amount of time that must pass for this entity to + /// be able to unbuckle after recently buckling. /// - [RegisterComponent] - [ComponentReference(typeof(SharedBuckleComponent))] - public sealed class BuckleComponent : SharedBuckleComponent - { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IEntitySystemManager _sysMan = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; + [DataField("delay")] + [ViewVariables] + public TimeSpan UnbuckleDelay = TimeSpan.FromSeconds(0.25f); - [DataField("size")] - private int _size = 100; + /// + /// The time that this entity buckled at. + /// + [ViewVariables] public TimeSpan BuckleTime; - /// - /// The amount of time that must pass for this entity to - /// be able to unbuckle after recently buckling. - /// - [DataField("delay")] - [ViewVariables] - private TimeSpan _unbuckleDelay = TimeSpan.FromSeconds(0.25f); + /// + /// The strap that this component is buckled to. + /// + [ViewVariables] + public StrapComponent? BuckledTo { get; set; } - /// - /// The time that this entity buckled at. - /// - [ViewVariables] - private TimeSpan _buckleTime; - - /// - /// The position offset that is being applied to this entity if buckled. - /// - public Vector2 BuckleOffset { get; private set; } - - private StrapComponent? _buckledTo; - - /// - /// The strap that this component is buckled to. - /// - [ViewVariables] - public StrapComponent? BuckledTo - { - get => _buckledTo; - private set - { - _buckledTo = value; - _buckleTime = _gameTiming.CurTime; - _sysMan.GetEntitySystem().UpdateCanMove(Owner); - Dirty(EntMan); - } - } - - [ViewVariables] - public override bool Buckled => BuckledTo != null; - - /// - /// The amount of space that this entity occupies in a - /// . - /// - [ViewVariables] - public int Size => _size; - - /// - /// Shows or hides the buckled status effect depending on if the - /// entity is buckled or not. - /// - private void UpdateBuckleStatus() - { - if (Buckled) - { - AlertType alertType = BuckledTo?.BuckledAlertType ?? AlertType.Buckled; - EntitySystem.Get().ShowAlert(Owner, alertType); - } - else - { - EntitySystem.Get().ClearAlertCategory(Owner, AlertCategory.Buckled); - } - } - - public bool CanBuckle(EntityUid user, EntityUid to, [NotNullWhen(true)] out StrapComponent? strap) - { - var popupSystem = EntitySystem.Get(); - strap = null; - - if (user == to) - { - return false; - } - - if (!EntMan.TryGetComponent(to, out strap)) - { - return false; - } - - var strapUid = strap.Owner; - bool Ignored(EntityUid entity) => entity == Owner || entity == user || entity == strapUid; - - if (!EntitySystem.Get().InRangeUnobstructed(Owner, strapUid, Range, predicate: Ignored, popup: true)) - { - return false; - } - - // If in a container - if (Owner.TryGetContainer(out var ownerContainer)) - { - // And not in the same container as the strap - if (!strap.Owner.TryGetContainer(out var strapContainer) || - ownerContainer != strapContainer) - { - return false; - } - } - - if (!EntMan.HasComponent(user)) - { - popupSystem.PopupEntity(Loc.GetString("buckle-component-no-hands-message"), user, Filter.Entities(user)); - return false; - } - - if (Buckled) - { - var message = Loc.GetString(Owner == user - ? "buckle-component-already-buckled-message" - : "buckle-component-other-already-buckled-message", ("owner", Identity.Entity(Owner, _entMan))); - popupSystem.PopupEntity(message, user, Filter.Entities(user)); - - return false; - } - - var parent = EntMan.GetComponent(to).Parent; - while (parent != null) - { - if (parent == EntMan.GetComponent(user)) - { - var message = Loc.GetString(Owner == user - ? "buckle-component-cannot-buckle-message" - : "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(Owner, _entMan))); - popupSystem.PopupEntity(message, user, Filter.Entities(user)); - - return false; - } - - parent = parent.Parent; - } - - if (!strap.HasSpace(this)) - { - var message = Loc.GetString(Owner == user - ? "buckle-component-cannot-fit-message" - : "buckle-component-other-cannot-fit-message", ("owner", Identity.Entity(Owner, _entMan))); - popupSystem.PopupEntity(message, user, Filter.Entities(user)); - - return false; - } - - return true; - } - - public override bool TryBuckle(EntityUid user, EntityUid to) - { - var popupSystem = EntitySystem.Get(); - if (!CanBuckle(user, to, out var strap)) - { - return false; - } - - SoundSystem.Play(strap.BuckleSound.GetSound(), Filter.Pvs(Owner), Owner); - - if (!strap.TryAdd(this)) - { - var message = Loc.GetString(Owner == user - ? "buckle-component-cannot-buckle-message" - : "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(Owner, _entMan))); - popupSystem.PopupEntity(message, user, Filter.Entities(user)); - return false; - } - - if(EntMan.TryGetComponent(Owner, out var appearance)) - appearance.SetData(BuckleVisuals.Buckled, true); - - ReAttach(strap); - - BuckledTo = strap; - LastEntityBuckledTo = BuckledTo.Owner; - DontCollide = true; - - UpdateBuckleStatus(); - - var ev = new BuckleChangeEvent() { Buckling = true, Strap = BuckledTo.Owner, BuckledEntity = Owner }; - EntMan.EventBus.RaiseLocalEvent(ev.BuckledEntity, ev, false); - EntMan.EventBus.RaiseLocalEvent(ev.Strap, ev, false); - - if (EntMan.TryGetComponent(Owner, out SharedPullableComponent? ownerPullable)) - { - if (ownerPullable.Puller != null) - { - EntitySystem.Get().TryStopPull(ownerPullable); - } - } - - if (EntMan.TryGetComponent(to, out SharedPullableComponent? toPullable)) - { - if (toPullable.Puller == Owner) - { - // can't pull it and buckle to it at the same time - EntitySystem.Get().TryStopPull(toPullable); - } - } - - return true; - } - - /// - /// Tries to unbuckle the Owner of this component from its current strap. - /// - /// The entity doing the unbuckling. - /// - /// Whether to force the unbuckling or not. Does not guarantee true to - /// be returned, but guarantees the owner to be unbuckled afterwards. - /// - /// - /// true if the owner was unbuckled, otherwise false even if the owner - /// was previously already unbuckled. - /// - public bool TryUnbuckle(EntityUid user, bool force = false) - { - if (BuckledTo == null) - { - return false; - } - - var oldBuckledTo = BuckledTo; - - if (!force) - { - if (_gameTiming.CurTime < _buckleTime + _unbuckleDelay) - { - return false; - } - - if (!EntitySystem.Get().InRangeUnobstructed(user, oldBuckledTo.Owner, Range, popup: true)) - { - return false; - } - - if (EntMan.TryGetComponent(Owner, out var sleeping) && Owner == user) - return false; - // If the strap is a vehicle and the rider is not the person unbuckling, return. - if (EntMan.TryGetComponent(oldBuckledTo.Owner, out var vehicle) && - vehicle.Rider != user) - return false; - } - - BuckledTo = null; - - var entManager = IoCManager.Resolve(); - var xform = entManager.GetComponent(Owner); - var oldBuckledXform = entManager.GetComponent(oldBuckledTo.Owner); - - if (xform.ParentUid == oldBuckledXform.Owner) - { - xform.AttachParentToContainerOrGrid(); - xform.WorldRotation = oldBuckledXform.WorldRotation; - - if (oldBuckledTo.UnbuckleOffset != Vector2.Zero) - xform.Coordinates = oldBuckledXform.Coordinates.Offset(oldBuckledTo.UnbuckleOffset); - } - - if(EntMan.TryGetComponent(Owner, out var appearance)) - appearance.SetData(BuckleVisuals.Buckled, false); - - if (EntMan.HasComponent(Owner) - | (EntMan.TryGetComponent(Owner, out var mobState) && mobState.IsIncapacitated())) - { - EntitySystem.Get().Down(Owner); - } - else - { - EntitySystem.Get().Stand(Owner); - } - - IoCManager.Resolve().GetEntitySystem() - .EnterState(mobState, mobState?.CurrentState); - - UpdateBuckleStatus(); - - oldBuckledTo.Remove(this); - SoundSystem.Play(oldBuckledTo.UnbuckleSound.GetSound(), Filter.Pvs(Owner), Owner); - - var ev = new BuckleChangeEvent() { Buckling = false, Strap = oldBuckledTo.Owner, BuckledEntity = Owner }; - EntMan.EventBus.RaiseLocalEvent(Owner, ev, false); - EntMan.EventBus.RaiseLocalEvent(oldBuckledTo.Owner, ev, false); - - return true; - } - - /// - /// Makes an entity toggle the buckling status of the owner to a - /// specific entity. - /// - /// The entity doing the buckling/unbuckling. - /// - /// The entity to toggle the buckle status of the owner to. - /// - /// - /// Whether to force the unbuckling or not, if it happens. Does not - /// guarantee true to be returned, but guarantees the owner to be - /// unbuckled afterwards. - /// - /// true if the buckling status was changed, false otherwise. - public bool ToggleBuckle(EntityUid user, EntityUid to, bool force = false) - { - if (BuckledTo?.Owner == to) - { - return TryUnbuckle(user, force); - } - - return TryBuckle(user, to); - } - - protected override void Startup() - { - base.Startup(); - UpdateBuckleStatus(); - } - - protected override void Shutdown() - { - BuckledTo?.Remove(this); - TryUnbuckle(Owner, true); - - _buckleTime = default; - UpdateBuckleStatus(); - - base.Shutdown(); - } - - public override ComponentState GetComponentState() - { - return new BuckleComponentState(Buckled, LastEntityBuckledTo, DontCollide); - } - } + /// + /// The amount of space that this entity occupies in a + /// . + /// + [DataField("size")] + [ViewVariables] + public int Size = 100; } diff --git a/Content.Server/Buckle/Components/StrapComponent.cs b/Content.Server/Buckle/Components/StrapComponent.cs index 5432c19ae6..05da267de1 100644 --- a/Content.Server/Buckle/Components/StrapComponent.cs +++ b/Content.Server/Buckle/Components/StrapComponent.cs @@ -1,10 +1,9 @@ using System.Linq; +using Content.Server.Buckle.Systems; using Content.Shared.Alert; using Content.Shared.Buckle.Components; using Content.Shared.DragDrop; using Robust.Shared.Audio; -using Robust.Shared.Serialization; - namespace Content.Server.Buckle.Components { @@ -150,14 +149,11 @@ namespace Content.Server.Buckle.Components public void RemoveAll() { - var entManager = IoCManager.Resolve(); + var buckleSystem = IoCManager.Resolve().System(); foreach (var entity in BuckledEntities.ToArray()) { - if (entManager.TryGetComponent(entity, out var buckle)) - { - buckle.TryUnbuckle(entity, true); - } + buckleSystem.TryUnbuckle(entity, entity, true); } BuckledEntities.Clear(); @@ -167,10 +163,8 @@ namespace Content.Server.Buckle.Components public override bool DragDropOn(DragDropEvent eventArgs) { - var entManager = IoCManager.Resolve(); - - if (!entManager.TryGetComponent(eventArgs.Dragged, out BuckleComponent? buckleComponent)) return false; - return buckleComponent.TryBuckle(eventArgs.User, Owner); + var buckleSystem = IoCManager.Resolve().System(); + return buckleSystem.TryBuckle(eventArgs.Dragged, eventArgs.User, Owner); } } } diff --git a/Content.Server/Buckle/Systems/BuckleSystem.cs b/Content.Server/Buckle/Systems/BuckleSystem.cs index d15bd0fdba..fee3ac2df0 100644 --- a/Content.Server/Buckle/Systems/BuckleSystem.cs +++ b/Content.Server/Buckle/Systems/BuckleSystem.cs @@ -1,130 +1,473 @@ +using System.Diagnostics.CodeAnalysis; using Content.Server.Buckle.Components; using Content.Server.Interaction; +using Content.Server.Popups; +using Content.Server.Pulling; using Content.Server.Storage.Components; +using Content.Shared.ActionBlocker; +using Content.Shared.Alert; +using Content.Shared.Bed.Sleep; using Content.Shared.Buckle; using Content.Shared.Buckle.Components; +using Content.Shared.DragDrop; +using Content.Shared.Hands.Components; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; +using Content.Shared.MobState.Components; +using Content.Shared.MobState.EntitySystems; +using Content.Shared.Pulling.Components; +using Content.Shared.Stunnable; +using Content.Shared.Vehicle.Components; using Content.Shared.Verbs; using JetBrains.Annotations; +using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Player; +using Robust.Shared.Timing; -namespace Content.Server.Buckle.Systems +namespace Content.Server.Buckle.Systems; + +[UsedImplicitly] +public sealed class BuckleSystem : SharedBuckleSystem { - [UsedImplicitly] - public sealed class BuckleSystem : SharedBuckleSystem + [Dependency] private readonly IGameTiming _gameTiming = default!; + + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly ContainerSystem _containers = default!; + [Dependency] private readonly InteractionSystem _interactions = default!; + [Dependency] private readonly SharedMobStateSystem _mobState = default!; + [Dependency] private readonly PopupSystem _popups = default!; + [Dependency] private readonly PullingSystem _pulling = default!; + [Dependency] private readonly Shared.Standing.StandingStateSystem _standing = default!; + + public override void Initialize() { - public override void Initialize() + base.Initialize(); + + UpdatesAfter.Add(typeof(InteractionSystem)); + UpdatesAfter.Add(typeof(InputSystem)); + + SubscribeLocalEvent(OnStrapGetState); + SubscribeLocalEvent(ContainerModifiedStrap); + SubscribeLocalEvent(ContainerModifiedStrap); + + SubscribeLocalEvent(OnBuckleStartup); + SubscribeLocalEvent(OnBuckleShutdown); + SubscribeLocalEvent(OnBuckleGetState); + SubscribeLocalEvent(MoveEvent); + SubscribeLocalEvent(HandleInteractHand); + SubscribeLocalEvent>(AddUnbuckleVerb); + SubscribeLocalEvent(OnEntityStorageInsertAttempt); + SubscribeLocalEvent(OnBuckleCanDrop); + SubscribeLocalEvent(OnBuckleDragDrop); + } + + private void OnStrapGetState(EntityUid uid, StrapComponent component, ref ComponentGetState args) + { + args.State = new StrapComponentState(component.Position, component.BuckleOffset, component.BuckledEntities, component.MaxBuckleDistance); + } + + private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || !component.Buckled) + return; + + InteractionVerb verb = new() { - base.Initialize(); + Act = () => TryUnbuckle(uid, args.User, buckle: component), + Text = Loc.GetString("verb-categories-unbuckle"), + IconTexture = "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png" + }; - UpdatesAfter.Add(typeof(InteractionSystem)); - UpdatesAfter.Add(typeof(InputSystem)); - - SubscribeLocalEvent(OnStrapGetState); - SubscribeLocalEvent(ContainerModifiedStrap); - SubscribeLocalEvent(ContainerModifiedStrap); - - SubscribeLocalEvent(MoveEvent); - SubscribeLocalEvent(HandleInteractHand); - SubscribeLocalEvent>(AddUnbuckleVerb); - SubscribeLocalEvent(OnEntityStorageInsertAttempt); + if (args.Target == args.User && args.Using == null) + { + // A user is left clicking themselves with an empty hand, while buckled. + // It is very likely they are trying to unbuckle themselves. + verb.Priority = 1; } - private void OnStrapGetState(EntityUid uid, StrapComponent component, ref ComponentGetState args) + args.Verbs.Add(verb); + } + + private void OnBuckleStartup(EntityUid uid, BuckleComponent component, ComponentStartup args) + { + UpdateBuckleStatus(uid, component); + } + + private void OnBuckleShutdown(EntityUid uid, BuckleComponent component, ComponentShutdown args) + { + component.BuckledTo?.Remove(component); + TryUnbuckle(uid, uid, true, component); + + component.BuckleTime = default; + } + + private void OnBuckleGetState(EntityUid uid, BuckleComponent component, ref ComponentGetState args) + { + args.State = new BuckleComponentState(component.Buckled, component.LastEntityBuckledTo, component.DontCollide); + } + + private void HandleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args) + { + args.Handled = TryUnbuckle(uid, args.User, buckle: component); + } + + private void MoveEvent(EntityUid uid, BuckleComponent buckle, ref MoveEvent ev) + { + var strap = buckle.BuckledTo; + + if (strap == null) { - args.State = new StrapComponentState(component.Position, component.BuckleOffset, component.BuckledEntities, component.MaxBuckleDistance); + return; } - private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetVerbsEvent args) + var strapPosition = Transform(strap.Owner).Coordinates; + + if (ev.NewPosition.InRange(EntityManager, strapPosition, strap.MaxBuckleDistance)) { - if (!args.CanAccess || !args.CanInteract || !component.Buckled) - return; - - InteractionVerb verb = new() - { - Act = () => component.TryUnbuckle(args.User), - Text = Loc.GetString("verb-categories-unbuckle"), - IconTexture = "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png" - }; - - if (args.Target == args.User && args.Using == null) - { - // A user is left clicking themselves with an empty hand, while buckled. - // It is very likely they are trying to unbuckle themselves. - verb.Priority = 1; - } - - args.Verbs.Add(verb); + return; } - private void HandleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args) + TryUnbuckle(uid, buckle.Owner, true, buckle); + } + + private void ContainerModifiedStrap(EntityUid uid, StrapComponent strap, ContainerModifiedMessage message) + { + if (GameTiming.ApplyingState) + return; + + foreach (var buckledEntity in strap.BuckledEntities) { - args.Handled = component.TryUnbuckle(args.User); - } - - private void MoveEvent(EntityUid uid, BuckleComponent buckle, ref MoveEvent ev) - { - var strap = buckle.BuckledTo; - - if (strap == null) + if (!TryComp(buckledEntity, out BuckleComponent? buckled)) { - return; + continue; } - var strapPosition = EntityManager.GetComponent(strap.Owner).Coordinates.Offset(buckle.BuckleOffset); - - if (ev.NewPosition.InRange(EntityManager, strapPosition, strap.MaxBuckleDistance)) - { - return; - } - - buckle.TryUnbuckle(buckle.Owner, true); - } - - private void ContainerModifiedStrap(EntityUid uid, StrapComponent strap, ContainerModifiedMessage message) - { - if (GameTiming.ApplyingState) - return; - - foreach (var buckledEntity in strap.BuckledEntities) - { - if (!EntityManager.TryGetComponent(buckledEntity, out BuckleComponent? buckled)) - { - continue; - } - - ContainerModifiedReAttach(buckled, strap); - } - } - - private void ContainerModifiedReAttach(BuckleComponent buckle, StrapComponent? strap) - { - if (strap == null) - { - return; - } - - var contained = buckle.Owner.TryGetContainer(out var ownContainer); - var strapContained = strap.Owner.TryGetContainer(out var strapContainer); - - if (contained != strapContained || ownContainer != strapContainer) - { - buckle.TryUnbuckle(buckle.Owner, true); - return; - } - - if (!contained) - { - buckle.ReAttach(strap); - } - } - - public void OnEntityStorageInsertAttempt(EntityUid uid, BuckleComponent comp, InsertIntoEntityStorageAttemptEvent args) - { - if (comp.Buckled) - args.Cancel(); + ContainerModifiedReAttach(buckledEntity, strap.Owner, buckled, strap); } } + + private void ContainerModifiedReAttach(EntityUid buckleId, EntityUid strapId, BuckleComponent? buckle = null, StrapComponent? strap = null) + { + if (!Resolve(buckleId, ref buckle, false) || + !Resolve(strapId, ref strap, false)) + { + return; + } + + var contained = _containers.TryGetContainingContainer(buckleId, out var ownContainer); + var strapContained = _containers.TryGetContainingContainer(strapId, out var strapContainer); + + if (contained != strapContained || ownContainer != strapContainer) + { + TryUnbuckle(buckleId, buckle.Owner, true, buckle); + return; + } + + if (!contained) + { + ReAttach(buckleId, strap, buckle); + } + } + + public void OnEntityStorageInsertAttempt(EntityUid uid, BuckleComponent comp, InsertIntoEntityStorageAttemptEvent args) + { + if (comp.Buckled) + args.Cancel(); + } + + private void OnBuckleCanDrop(EntityUid uid, BuckleComponent component, CanDropEvent args) + { + args.Handled = HasComp(args.Target); + } + + private void OnBuckleDragDrop(EntityUid uid, BuckleComponent component, DragDropEvent args) + { + args.Handled = TryBuckle(uid, args.User, args.Target, component); + } + + /// + /// Shows or hides the buckled status effect depending on if the + /// entity is buckled or not. + /// + private void UpdateBuckleStatus(EntityUid uid, BuckleComponent component) + { + if (component.Buckled) + { + var alertType = component.BuckledTo?.BuckledAlertType ?? AlertType.Buckled; + _alerts.ShowAlert(uid, alertType); + } + else + { + _alerts.ClearAlertCategory(uid, AlertCategory.Buckled); + } + } + + private void SetBuckledTo(BuckleComponent buckle, StrapComponent? strap) + { + buckle.BuckledTo = strap; + buckle.LastEntityBuckledTo = strap?.Owner; + + if (strap == null) + { + buckle.Buckled = false; + } + else + { + buckle.DontCollide = true; + buckle.Buckled = true; + buckle.BuckleTime = _gameTiming.CurTime; + } + + _actionBlocker.UpdateCanMove(buckle.Owner); + UpdateBuckleStatus(buckle.Owner, buckle); + Dirty(buckle); + } + + public bool CanBuckle( + EntityUid buckleId, + EntityUid user, + EntityUid to, + [NotNullWhen(true)] out StrapComponent? strap, + BuckleComponent? buckle = null) + { + strap = null; + + if (user == to || + !Resolve(buckleId, ref buckle, false) || + !Resolve(to, ref strap, false)) + { + return false; + } + + var strapUid = strap.Owner; + bool Ignored(EntityUid entity) => entity == buckleId || entity == user || entity == strapUid; + + if (!_interactions.InRangeUnobstructed(buckleId, strapUid, buckle.Range, predicate: Ignored, popup: true)) + { + return false; + } + + // If in a container + if (_containers.TryGetContainingContainer(buckleId, out var ownerContainer)) + { + // And not in the same container as the strap + if (!_containers.TryGetContainingContainer(strap.Owner, out var strapContainer) || + ownerContainer != strapContainer) + { + return false; + } + } + + if (!HasComp(user)) + { + _popups.PopupEntity(Loc.GetString("buckle-component-no-hands-message"), user, Filter.Entities(user)); + return false; + } + + if (buckle.Buckled) + { + var message = Loc.GetString(buckleId == user + ? "buckle-component-already-buckled-message" + : "buckle-component-other-already-buckled-message", + ("owner", Identity.Entity(buckleId, EntityManager))); + _popups.PopupEntity(message, user, Filter.Entities(user)); + + return false; + } + + var parent = Transform(to).ParentUid; + while (parent.IsValid()) + { + if (parent == user) + { + var message = Loc.GetString(buckleId == user + ? "buckle-component-cannot-buckle-message" + : "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleId, EntityManager))); + _popups.PopupEntity(message, user, Filter.Entities(user)); + + return false; + } + + parent = Transform(parent).ParentUid; + } + + if (!strap.HasSpace(buckle)) + { + var message = Loc.GetString(buckleId == user + ? "buckle-component-cannot-fit-message" + : "buckle-component-other-cannot-fit-message", ("owner", Identity.Entity(buckleId, EntityManager))); + _popups.PopupEntity(message, user, Filter.Entities(user)); + + return false; + } + + return true; + } + + public bool TryBuckle(EntityUid buckleId, EntityUid user, EntityUid to, BuckleComponent? buckle = null) + { + if (!Resolve(buckleId, ref buckle, false)) + return false; + + if (!CanBuckle(buckleId, user, to, out var strap, buckle)) + return false; + + _audio.Play(strap.BuckleSound, Filter.Pvs(buckleId), buckleId); + + if (!strap.TryAdd(buckle)) + { + var message = Loc.GetString(buckleId == user + ? "buckle-component-cannot-buckle-message" + : "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleId, EntityManager))); + _popups.PopupEntity(message, user, Filter.Entities(user)); + return false; + } + + if (TryComp(buckleId, out var appearance)) + _appearance.SetData(buckleId, BuckleVisuals.Buckled, true, appearance); + + ReAttach(buckleId, strap, buckle); + SetBuckledTo(buckle, strap); + + var ev = new BuckleChangeEvent { Buckling = true, Strap = strap.Owner, BuckledEntity = buckleId }; + RaiseLocalEvent(ev.BuckledEntity, ev); + RaiseLocalEvent(ev.Strap, ev); + + if (TryComp(buckleId, out SharedPullableComponent? ownerPullable)) + { + if (ownerPullable.Puller != null) + { + _pulling.TryStopPull(ownerPullable); + } + } + + if (TryComp(to, out SharedPullableComponent? toPullable)) + { + if (toPullable.Puller == buckleId) + { + // can't pull it and buckle to it at the same time + _pulling.TryStopPull(toPullable); + } + } + + return true; + } + + /// + /// Tries to unbuckle the Owner of this component from its current strap. + /// + /// The entity to unbuckle. + /// The entity doing the unbuckling. + /// + /// Whether to force the unbuckling or not. Does not guarantee true to + /// be returned, but guarantees the owner to be unbuckled afterwards. + /// + /// The buckle component of the entity to unbuckle. + /// + /// true if the owner was unbuckled, otherwise false even if the owner + /// was previously already unbuckled. + /// + public bool TryUnbuckle(EntityUid buckleId, EntityUid user, bool force = false, BuckleComponent? buckle = null) + { + if (!Resolve(buckleId, ref buckle, false) || + buckle.BuckledTo is not { } oldBuckledTo) + { + return false; + } + + if (!force) + { + if (_gameTiming.CurTime < buckle.BuckleTime + buckle.UnbuckleDelay) + return false; + + if (!_interactions.InRangeUnobstructed(user, oldBuckledTo.Owner, buckle.Range, popup: true)) + return false; + + if (HasComp(buckleId) && buckleId == user) + return false; + + // If the strap is a vehicle and the rider is not the person unbuckling, return. + if (TryComp(oldBuckledTo.Owner, out VehicleComponent? vehicle) && + vehicle.Rider != user) + return false; + } + + SetBuckledTo(buckle, null); + + var xform = Transform(buckleId); + var oldBuckledXform = Transform(oldBuckledTo.Owner); + + if (xform.ParentUid == oldBuckledXform.Owner) + { + _containers.AttachParentToContainerOrGrid(xform); + xform.WorldRotation = oldBuckledXform.WorldRotation; + + if (oldBuckledTo.UnbuckleOffset != Vector2.Zero) + xform.Coordinates = oldBuckledXform.Coordinates.Offset(oldBuckledTo.UnbuckleOffset); + } + + if (TryComp(buckleId, out AppearanceComponent? appearance)) + _appearance.SetData(buckleId, BuckleVisuals.Buckled, false, appearance); + + if (HasComp(buckleId) + | (TryComp(buckleId, out var mobState) && _mobState.IsIncapacitated(buckleId, mobState))) + { + _standing.Down(buckleId); + } + else + { + _standing.Stand(buckleId); + } + + _mobState.EnterState(mobState, mobState?.CurrentState); + + oldBuckledTo.Remove(buckle); + _audio.Play(oldBuckledTo.UnbuckleSound, Filter.Pvs(buckleId), buckleId); + + var ev = new BuckleChangeEvent { Buckling = false, Strap = oldBuckledTo.Owner, BuckledEntity = buckleId }; + RaiseLocalEvent(buckleId, ev); + RaiseLocalEvent(oldBuckledTo.Owner, ev); + + return true; + } + + /// + /// Makes an entity toggle the buckling status of the owner to a + /// specific entity. + /// + /// The entity to buckle/unbuckle from . + /// The entity doing the buckling/unbuckling. + /// + /// The entity to toggle the buckle status of the owner to. + /// + /// + /// Whether to force the unbuckling or not, if it happens. Does not + /// guarantee true to be returned, but guarantees the owner to be + /// unbuckled afterwards. + /// + /// The buckle component of the entity to buckle/unbuckle from . + /// true if the buckling status was changed, false otherwise. + public bool ToggleBuckle( + EntityUid buckleId, + EntityUid user, + EntityUid to, + bool force = false, + BuckleComponent? buckle = null) + { + if (!Resolve(buckleId, ref buckle, false)) + return false; + + if (buckle.BuckledTo?.Owner == to) + { + return TryUnbuckle(buckleId, user, force, buckle); + } + + return TryBuckle(buckleId, user, to, buckle); + } } diff --git a/Content.Server/Buckle/Systems/StrapSystem.cs b/Content.Server/Buckle/Systems/StrapSystem.cs index e9312ee558..6850941ff0 100644 --- a/Content.Server/Buckle/Systems/StrapSystem.cs +++ b/Content.Server/Buckle/Systems/StrapSystem.cs @@ -14,7 +14,8 @@ namespace Content.Server.Buckle.Systems [UsedImplicitly] internal sealed class StrapSystem : EntitySystem { - [Dependency] InteractionSystem _interactionSystem = default!; + [Dependency] private readonly BuckleSystem _buckle = default!; + [Dependency] private readonly InteractionSystem _interactionSystem = default!; public override void Initialize() { @@ -47,12 +48,10 @@ namespace Content.Server.Buckle.Systems private void OnInteractHand(EntityUid uid, StrapComponent component, InteractHandEvent args) { - if (args.Handled) return; - - if (!TryComp(args.User, out var buckle)) + if (args.Handled) return; - buckle.ToggleBuckle(args.User, uid); + _buckle.ToggleBuckle(args.User, args.User, uid); } // TODO ECS BUCKLE/STRAP These 'Strap' verbs are an incestuous mess of buckle component and strap component @@ -77,7 +76,7 @@ namespace Content.Server.Buckle.Systems InteractionVerb verb = new() { - Act = () => buckledComp.TryUnbuckle(args.User), + Act = () => _buckle.TryUnbuckle(entity, args.User, buckle: buckledComp), Category = VerbCategory.Unbuckle }; @@ -103,7 +102,7 @@ namespace Content.Server.Buckle.Systems { InteractionVerb verb = new() { - Act = () => buckle.TryBuckle(args.User, args.Target), + Act = () => _buckle.TryBuckle(args.User, args.User, args.Target, buckle), Category = VerbCategory.Buckle, Text = Loc.GetString("verb-self-target-pronoun") }; @@ -123,7 +122,7 @@ namespace Content.Server.Buckle.Systems InteractionVerb verb = new() { - Act = () => usingBuckle.TryBuckle(args.User, args.Target), + Act = () => _buckle.TryBuckle(@using, args.User, args.Target, usingBuckle), Category = VerbCategory.Buckle, Text = EntityManager.GetComponent(@using).EntityName, // just a held object, the user is probably just trying to sit down. diff --git a/Content.Server/Polymorph/Systems/PolymorphableSystem.cs b/Content.Server/Polymorph/Systems/PolymorphableSystem.cs index 56324bfcb5..a7c6914601 100644 --- a/Content.Server/Polymorph/Systems/PolymorphableSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphableSystem.cs @@ -1,5 +1,5 @@ using Content.Server.Actions; -using Content.Server.Buckle.Components; +using Content.Server.Buckle.Systems; using Content.Server.Humanoid; using Content.Server.Inventory; using Content.Server.Mind.Commands; @@ -23,6 +23,7 @@ namespace Content.Server.Polymorph.Systems private readonly ISawmill _saw = default!; [Dependency] private readonly ActionsSystem _actions = default!; + [Dependency] private readonly BuckleSystem _buckle = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IComponentFactory _compFact = default!; [Dependency] private readonly ServerInventorySystem _inventory = default!; @@ -91,8 +92,7 @@ namespace Content.Server.Polymorph.Systems return null; // mostly just for vehicles - if (TryComp(target, out var buckle)) - buckle.TryUnbuckle(target, true); + _buckle.TryUnbuckle(target, target, true); var targetTransformComp = Transform(target); diff --git a/Content.Server/Vehicle/VehicleSystem.Rider.cs b/Content.Server/Vehicle/VehicleSystem.Rider.cs index 30c6a0d465..b79ef3a047 100644 --- a/Content.Server/Vehicle/VehicleSystem.Rider.cs +++ b/Content.Server/Vehicle/VehicleSystem.Rider.cs @@ -1,8 +1,7 @@ -using Content.Server.Buckle.Components; -using Content.Shared.Vehicle.Components; -using Content.Shared.MobState; using Content.Server.Standing; using Content.Shared.Hands; +using Content.Shared.MobState; +using Content.Shared.Vehicle.Components; using Robust.Shared.GameStates; namespace Content.Server.Vehicle @@ -57,10 +56,7 @@ namespace Content.Server.Vehicle public void UnbuckleFromVehicle(EntityUid uid) { - if (!TryComp(uid, out var buckle)) - return; - - buckle.TryUnbuckle(uid, true); + _buckle.TryUnbuckle(uid, uid, true); } } } diff --git a/Content.Server/Vehicle/VehicleSystem.cs b/Content.Server/Vehicle/VehicleSystem.cs index 2d9f287819..e01c6993e0 100644 --- a/Content.Server/Vehicle/VehicleSystem.cs +++ b/Content.Server/Vehicle/VehicleSystem.cs @@ -1,26 +1,21 @@ -using Content.Server.Buckle.Components; -using Content.Shared.Vehicle.Components; -using Content.Shared.Vehicle; +using Content.Server.Buckle.Systems; +using Content.Server.Hands.Systems; +using Content.Server.Light.Components; +using Content.Shared.Actions; using Content.Shared.Buckle.Components; using Content.Shared.Movement.Components; -using Content.Shared.Containers.ItemSlots; -using Content.Shared.Actions; -using Content.Shared.Audio; -using Content.Server.Light.Components; -using Content.Server.Hands.Systems; -using Content.Shared.Tag; using Content.Shared.Movement.Systems; +using Content.Shared.Vehicle; +using Content.Shared.Vehicle.Components; using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; using Robust.Shared.Player; -using DrawDepth = Content.Shared.DrawDepth.DrawDepth; namespace Content.Server.Vehicle { public sealed partial class VehicleSystem : SharedVehicleSystem { + [Dependency] private readonly BuckleSystem _buckle = default!; [Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly SharedJointSystem _joints = default!; diff --git a/Content.Shared/Buckle/Components/SharedBuckleComponent.cs b/Content.Shared/Buckle/Components/SharedBuckleComponent.cs index 2c7d6a8802..c14af689dc 100644 --- a/Content.Shared/Buckle/Components/SharedBuckleComponent.cs +++ b/Content.Shared/Buckle/Components/SharedBuckleComponent.cs @@ -1,103 +1,55 @@ -using Content.Shared.DragDrop; using Content.Shared.Interaction; -using Content.Shared.Standing; using Robust.Shared.GameStates; -using Robust.Shared.Map; using Robust.Shared.Serialization; -namespace Content.Shared.Buckle.Components +namespace Content.Shared.Buckle.Components; + +[NetworkedComponent] +[Access(typeof(SharedBuckleSystem))] +public abstract class SharedBuckleComponent : Component { - [NetworkedComponent()] - public abstract class SharedBuckleComponent : Component, IDraggable - { - [Dependency] protected readonly IEntityManager EntMan = default!; + /// + /// The range from which this entity can buckle to a . + /// + [ViewVariables] + [DataField("range")] + public float Range { get; protected set; } = SharedInteractionSystem.InteractionRange / 1.4f; - /// - /// The range from which this entity can buckle to a . - /// - [ViewVariables] - [DataField("range")] - public float Range { get; protected set; } = SharedInteractionSystem.InteractionRange / 1.4f; + /// + /// True if the entity is buckled, false otherwise. + /// + public bool Buckled { get; set; } - /// - /// True if the entity is buckled, false otherwise. - /// - public abstract bool Buckled { get; } + public EntityUid? LastEntityBuckledTo { get; set; } - public EntityUid? LastEntityBuckledTo { get; set; } - - public bool DontCollide { get; set; } - - public abstract bool TryBuckle(EntityUid user, EntityUid to); - - bool IDraggable.CanDrop(CanDropEvent args) - { - return IoCManager.Resolve().HasComponent(args.Target); - } - - bool IDraggable.Drop(DragDropEvent args) - { - return TryBuckle(args.User, args.Target); - } - - /// - /// Reattaches this entity to the strap, modifying its position and rotation. - /// - /// The strap to reattach to. - public void ReAttach(SharedStrapComponent strap) - { - var ownTransform = EntMan.GetComponent(Owner); - var strapTransform = EntMan.GetComponent(strap.Owner); - - ownTransform.Coordinates = new EntityCoordinates(strapTransform.Owner, strap.BuckleOffset); - - // Buckle subscribes to move for so this might fail. - // TODO: Make buckle not do that. - if (ownTransform.ParentUid != strapTransform.Owner) - return; - - ownTransform.LocalRotation = Angle.Zero; - - switch (strap.Position) - { - case StrapPosition.None: - break; - case StrapPosition.Stand: - EntitySystem.Get().Stand(Owner); - break; - case StrapPosition.Down: - EntitySystem.Get().Down(Owner, false, false); - break; - } - } - } - - [Serializable, NetSerializable] - public sealed class BuckleComponentState : ComponentState - { - public BuckleComponentState(bool buckled, EntityUid? lastEntityBuckledTo, bool dontCollide) - { - Buckled = buckled; - LastEntityBuckledTo = lastEntityBuckledTo; - DontCollide = dontCollide; - } - - public bool Buckled { get; } - public EntityUid? LastEntityBuckledTo { get; } - public bool DontCollide { get; } - } - - public sealed class BuckleChangeEvent : EntityEventArgs - { - public EntityUid Strap; - - public EntityUid BuckledEntity; - public bool Buckling; - } - - [Serializable, NetSerializable] - public enum BuckleVisuals - { - Buckled - } + public bool DontCollide { get; set; } +} + +[Serializable, NetSerializable] +public sealed class BuckleComponentState : ComponentState +{ + public BuckleComponentState(bool buckled, EntityUid? lastEntityBuckledTo, bool dontCollide) + { + Buckled = buckled; + LastEntityBuckledTo = lastEntityBuckledTo; + DontCollide = dontCollide; + } + + public bool Buckled { get; } + public EntityUid? LastEntityBuckledTo { get; } + public bool DontCollide { get; } +} + +public sealed class BuckleChangeEvent : EntityEventArgs +{ + public EntityUid Strap; + + public EntityUid BuckledEntity; + public bool Buckling; +} + +[Serializable, NetSerializable] +public enum BuckleVisuals +{ + Buckled } diff --git a/Content.Shared/Buckle/SharedBuckleSystem.cs b/Content.Shared/Buckle/SharedBuckleSystem.cs index bf51e33eaf..9e4a00a6c9 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.cs @@ -1,122 +1,160 @@ using Content.Shared.Buckle.Components; using Content.Shared.Interaction.Events; -using Content.Shared.Movement; using Content.Shared.Movement.Events; using Content.Shared.Standing; using Content.Shared.Throwing; using Content.Shared.Vehicle.Components; -using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Map; using Robust.Shared.Physics.Events; using Robust.Shared.Timing; -namespace Content.Shared.Buckle +namespace Content.Shared.Buckle; + +public abstract class SharedBuckleSystem : EntitySystem { - public abstract class SharedBuckleSystem : EntitySystem + [Dependency] protected readonly IGameTiming GameTiming = default!; + + [Dependency] private readonly StandingStateSystem _standing = default!; + + public override void Initialize() { - [Dependency] protected readonly IGameTiming GameTiming = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(OnStrapRotate); + + SubscribeLocalEvent(PreventCollision); + SubscribeLocalEvent(HandleDown); + SubscribeLocalEvent(HandleStand); + SubscribeLocalEvent(HandleThrowPushback); + SubscribeLocalEvent(HandleMove); + SubscribeLocalEvent(OnBuckleChangeDirectionAttempt); + } + + private void OnStrapRotate(EntityUid uid, SharedStrapComponent component, ref MoveEvent args) + { + // TODO: This looks dirty af. + // On rotation of a strap, reattach all buckled entities. + // This fixes buckle offsets and draw depths. + // This is mega cursed. Please somebody save me from Mr Buckle's wild ride. + // Oh god I'm back here again. Send help. + + // Consider a chair that has a player strapped to it. Then the client receives a new server state, showing + // that the player entity has moved elsewhere, and the chair has rotated. If the client applies the player + // state, then the chairs transform comp state, and then the buckle state. The transform state will + // forcefully teleport the player back to the chair (client-side only). This causes even more issues if the + // chair was teleporting in from nullspace after having left PVS. + // + // One option is to just never trigger re-buckles during state application. + // another is to.. just not do this? Like wtf is this code. But I CBF with buckle atm. + + if (GameTiming.ApplyingState || args.NewRotation == args.OldRotation) + return; + + foreach (var buckledEntity in component.BuckledEntities) { - base.Initialize(); - SubscribeLocalEvent(OnStrapRotate); - - SubscribeLocalEvent(PreventCollision); - SubscribeLocalEvent(HandleDown); - SubscribeLocalEvent(HandleStand); - SubscribeLocalEvent(HandleThrowPushback); - SubscribeLocalEvent(HandleMove); - SubscribeLocalEvent(OnBuckleChangeDirectionAttempt); - } - - private void OnStrapRotate(EntityUid uid, SharedStrapComponent component, ref MoveEvent args) - { - // TODO: This looks dirty af. - // On rotation of a strap, reattach all buckled entities. - // This fixes buckle offsets and draw depths. - // This is mega cursed. Please somebody save me from Mr Buckle's wild ride. - // Oh god I'm back here again. Send help. - - // Consider a chair that has a player strapped to it. Then the client receives a new server state, showing - // that the player entity has moved elsewhere, and the chair has rotated. If the client applies the player - // state, then the chairs transform comp state, and then the buckle state. The transform state will - // forcefully teleport the player back to the chair (client-side only). This causes even more issues if the - // chair was teleporting in from nullspace after having left PVS. - // - // One option is to just never trigger re-buckles during state application. - // another is to.. just not do this? Like wtf is this code. But I CBF with buckle atm. - - if (GameTiming.ApplyingState || args.NewRotation == args.OldRotation) - return; - - foreach (var buckledEntity in component.BuckledEntities) + if (!EntityManager.TryGetComponent(buckledEntity, out SharedBuckleComponent? buckled)) { - if (!EntityManager.TryGetComponent(buckledEntity, out SharedBuckleComponent? buckled)) - { - continue; - } - - if (!buckled.Buckled || buckled.LastEntityBuckledTo != uid) - { - Logger.Error($"A moving strap entity {ToPrettyString(uid)} attempted to re-parent an entity that does not 'belong' to it {ToPrettyString(buckledEntity)}"); - continue; - } - - buckled.ReAttach(component); - Dirty(buckled); + continue; } - } - public bool IsBuckled(EntityUid uid, SharedBuckleComponent? component = null) - { - return Resolve(uid, ref component, false) && component.Buckled; - } - - private void OnBuckleChangeDirectionAttempt(EntityUid uid, SharedBuckleComponent component, ChangeDirectionAttemptEvent args) - { - if (component.Buckled) - args.Cancel(); - } - - private void HandleMove(EntityUid uid, SharedBuckleComponent component, UpdateCanMoveEvent args) - { - if (component.LifeStage > ComponentLifeStage.Running) - return; - - if (component.Buckled && - !HasComp(Transform(uid).ParentUid)) // buckle+vehicle shitcode - args.Cancel(); - } - - private void HandleStand(EntityUid uid, SharedBuckleComponent component, StandAttemptEvent args) - { - if (component.Buckled) + if (!buckled.Buckled || buckled.LastEntityBuckledTo != uid) { - args.Cancel(); + Logger.Error($"A moving strap entity {ToPrettyString(uid)} attempted to re-parent an entity that does not 'belong' to it {ToPrettyString(buckledEntity)}"); + continue; } - } - private void HandleDown(EntityUid uid, SharedBuckleComponent component, DownAttemptEvent args) - { - if (component.Buckled) - { - args.Cancel(); - } + ReAttach(buckledEntity, component, buckle: buckled); + Dirty(buckled); } + } - private void HandleThrowPushback(EntityUid uid, SharedBuckleComponent component, ThrowPushbackAttemptEvent args) + public bool IsBuckled(EntityUid uid, SharedBuckleComponent? component = null) + { + return Resolve(uid, ref component, false) && component.Buckled; + } + + private void OnBuckleChangeDirectionAttempt(EntityUid uid, SharedBuckleComponent component, ChangeDirectionAttemptEvent args) + { + if (component.Buckled) + args.Cancel(); + } + + private void HandleMove(EntityUid uid, SharedBuckleComponent component, UpdateCanMoveEvent args) + { + if (component.LifeStage > ComponentLifeStage.Running) + return; + + if (component.Buckled && + !HasComp(Transform(uid).ParentUid)) // buckle+vehicle shitcode + args.Cancel(); + } + + private void HandleStand(EntityUid uid, SharedBuckleComponent component, StandAttemptEvent args) + { + if (component.Buckled) { - if (!component.Buckled) return; args.Cancel(); } + } - private void PreventCollision(EntityUid uid, SharedBuckleComponent component, ref PreventCollideEvent args) + private void HandleDown(EntityUid uid, SharedBuckleComponent component, DownAttemptEvent args) + { + if (component.Buckled) { - if (args.BodyB.Owner != component.LastEntityBuckledTo) return; + args.Cancel(); + } + } - if (component.Buckled || component.DontCollide) - { - args.Cancelled = true; - } + private void HandleThrowPushback(EntityUid uid, SharedBuckleComponent component, ThrowPushbackAttemptEvent args) + { + if (!component.Buckled) return; + args.Cancel(); + } + + private void PreventCollision(EntityUid uid, SharedBuckleComponent component, ref PreventCollideEvent args) + { + if (args.BodyB.Owner != component.LastEntityBuckledTo) + return; + + if (component.Buckled || component.DontCollide) + { + args.Cancelled = true; + } + } + + /// + /// Reattaches this entity to the strap, modifying its position and rotation. + /// + /// The entity to reattach. + /// The strap to reattach to. + /// The buckle component of the entity to reattach. + public void ReAttach(EntityUid buckleId, SharedStrapComponent strap, SharedBuckleComponent? buckle = null) + { + if (!Resolve(buckleId, ref buckle, false)) + return; + + var ownTransform = Transform(buckleId); + var strapTransform = Transform(strap.Owner); + + ownTransform.Coordinates = new EntityCoordinates(strapTransform.Owner, strap.BuckleOffset); + + // Buckle subscribes to move for so this might fail. + // TODO: Make buckle not do that. + if (ownTransform.ParentUid != strapTransform.Owner) + return; + + ownTransform.LocalRotation = Angle.Zero; + + switch (strap.Position) + { + case StrapPosition.None: + break; + case StrapPosition.Stand: + _standing.Stand(buckleId); + break; + case StrapPosition.Down: + _standing.Down(buckleId, false, false); + break; } } }