]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Moves buckling and vehicles to shared, some cleanup (#15923)
authorAJCM-git <60196617+AJCM-git@users.noreply.github.com>
Mon, 1 May 2023 07:04:23 +0000 (03:04 -0400)
committerGitHub <noreply@github.com>
Mon, 1 May 2023 07:04:23 +0000 (17:04 +1000)
33 files changed:
Content.Client/Buckle/BuckleSystem.cs
Content.Client/Vehicle/VehicleSystem.cs
Content.Client/Vehicle/VehicleVisualsComponent.cs [deleted file]
Content.Client/Vehicle/VehicleVisualsSystem.cs [deleted file]
Content.IntegrationTests/Tests/Buckle/BuckleTest.cs
Content.Server/Alert/Click/Unbuckle.cs
Content.Server/Bed/BedSystem.cs
Content.Server/Buckle/Systems/BuckleSystem.Buckle.cs [deleted file]
Content.Server/Buckle/Systems/BuckleSystem.Strap.cs [deleted file]
Content.Server/Buckle/Systems/BuckleSystem.cs
Content.Server/Climbing/ClimbSystem.cs
Content.Server/Disease/Cures/DiseaseBedrestCure.cs
Content.Server/Foldable/FoldableSystem.cs
Content.Server/Light/Components/UnpoweredFlashlightComponent.cs [deleted file]
Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs
Content.Server/NPC/HTN/Preconditions/BuckledPrecondition.cs
Content.Server/PDA/PDASystem.cs
Content.Server/Polymorph/Systems/PolymorphSystem.cs
Content.Server/Toilet/ToiletSystem.cs
Content.Server/Vehicle/VehicleSystem.Rider.cs [deleted file]
Content.Server/Vehicle/VehicleSystem.cs
Content.Shared/Buckle/Components/BuckleComponent.cs
Content.Shared/Buckle/Components/StrapComponent.cs
Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs
Content.Shared/Buckle/SharedBuckleSystem.Strap.cs
Content.Shared/Buckle/SharedBuckleSystem.cs
Content.Shared/Light/Component/UnpoweredFlashlightComponent.cs [new file with mode: 0644]
Content.Shared/Pulling/Systems/SharedPullingSystem.Actions.cs
Content.Shared/Vehicle/Components/RiderComponent.cs
Content.Shared/Vehicle/Components/VehicleComponent.cs
Content.Shared/Vehicle/SharedVehicleSystem.Rider.cs
Content.Shared/Vehicle/SharedVehicleSystem.cs
Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml

index 9d2f72b7cfa83b73a085e72b3285f26da9f338c6..70d25488badeac6f53e1eb5c30be05b7b5d62a81 100644 (file)
@@ -1,86 +1,82 @@
 using Content.Client.Rotation;
-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!;
-        [Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
-        [Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
+namespace Content.Client.Buckle;
 
-        public override void Initialize()
-        {
-            base.Initialize();
+internal sealed class BuckleSystem : SharedBuckleSystem
+{
+    [Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
 
-            SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnBuckleHandleState);
-            SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
-        }
+    public override void Initialize()
+    {
+        base.Initialize();
 
-        private void OnBuckleHandleState(EntityUid uid, BuckleComponent buckle, ref ComponentHandleState args)
-        {
-            if (args.Current is not BuckleComponentState state)
-                return;
+        SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnBuckleHandleState);
+        SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
+    }
 
-            buckle.Buckled = state.Buckled;
-            buckle.LastEntityBuckledTo = state.LastEntityBuckledTo;
-            buckle.DontCollide = state.DontCollide;
+    private void OnBuckleHandleState(EntityUid uid, BuckleComponent component, ref ComponentHandleState args)
+    {
+        if (args.Current is not BuckleComponentState state)
+            return;
 
-            _actionBlocker.UpdateCanMove(uid);
+        component.Buckled = state.Buckled;
+        component.BuckledTo = state.BuckledTo;
+        component.LastEntityBuckledTo = state.LastEntityBuckledTo;
+        component.DontCollide = state.DontCollide;
 
-            if (!TryComp(uid, out SpriteComponent? ownerSprite))
-                return;
+        ActionBlockerSystem.UpdateCanMove(uid);
 
-            if (HasComp<VehicleComponent>(buckle.LastEntityBuckledTo))
-                return;
+        if (!TryComp<SpriteComponent>(uid, out var ownerSprite))
+            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<SpriteComponent>(buckle.LastEntityBuckledTo, out var buckledSprite))
-            {
-                buckle.OriginalDrawDepth ??= ownerSprite.DrawDepth;
-                ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1;
-                return;
-            }
+        if (HasComp<VehicleComponent>(component.LastEntityBuckledTo))
+            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;
-            }
+        // 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 (component is { Buckled: true, LastEntityBuckledTo: { } } &&
+            Transform(component.LastEntityBuckledTo.Value).LocalRotation.GetCardinalDir() == Direction.North &&
+            TryComp<SpriteComponent>(component.LastEntityBuckledTo, out var buckledSprite))
+        {
+            component.OriginalDrawDepth ??= ownerSprite.DrawDepth;
+            ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1;
+            return;
         }
 
-        private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
+        // If here, we're not turning north and should restore the saved draw depth.
+        if (component.OriginalDrawDepth.HasValue)
         {
-            if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
-                return;
+            ownerSprite.DrawDepth = component.OriginalDrawDepth.Value;
+            component.OriginalDrawDepth = null;
+        }
+    }
 
-            if (!_appearanceSystem.TryGetData<int>(uid, StrapVisuals.RotationAngle, out var angle, args.Component) ||
-                !_appearanceSystem.TryGetData<bool>(uid, BuckleVisuals.Buckled, out var buckled, args.Component) ||
-                !buckled ||
-                args.Sprite == null)
-            {
-                _rotationVisualizerSystem.SetHorizontalAngle(uid, RotationVisualsComponent.DefaultRotation, rotVisuals);
-                return;
-            }
+    private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
+    {
+        if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
+            return;
 
-            // Animate strapping yourself to something at a given angle
-            _rotationVisualizerSystem.SetHorizontalAngle(uid, Angle.FromDegrees(angle), rotVisuals);
-            // TODO: Dump this when buckle is better
-            _rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f);
+        if (!AppearanceSystem.TryGetData<int>(uid, StrapVisuals.RotationAngle, out var angle, args.Component) ||
+            !AppearanceSystem.TryGetData<bool>(uid, BuckleVisuals.Buckled, out var buckled, args.Component) ||
+            !buckled ||
+            args.Sprite == null)
+        {
+            _rotationVisualizerSystem.SetHorizontalAngle(uid, RotationVisualsComponent.DefaultRotation, rotVisuals);
+            return;
         }
+
+        // Animate strapping yourself to something at a given angle
+        _rotationVisualizerSystem.SetHorizontalAngle(uid, Angle.FromDegrees(angle), rotVisuals);
+        // TODO: Dump this when buckle is better
+        _rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f);
     }
 }
index a27e4b7da199e66dffde8c33ad053af6ae23932d..007ce34ff0e78a41382c748a8f8169af0bf0243c 100644 (file)
@@ -3,41 +3,68 @@ using Content.Shared.Vehicle.Components;
 using Robust.Client.GameObjects;
 using Robust.Shared.GameStates;
 
-namespace Content.Client.Vehicle
+namespace Content.Client.Vehicle;
+
+public sealed class VehicleSystem : SharedVehicleSystem
 {
-    public sealed class VehicleSystem : SharedVehicleSystem
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<RiderComponent, ComponentStartup>(OnRiderStartup);
+        SubscribeLocalEvent<RiderComponent, ComponentShutdown>(OnRiderShutdown);
+        SubscribeLocalEvent<RiderComponent, ComponentHandleState>(OnRiderHandleState);
+        SubscribeLocalEvent<VehicleComponent, AppearanceChangeEvent>(OnVehicleAppearanceChange);
+    }
+
+    private void OnRiderStartup(EntityUid uid, RiderComponent component, ComponentStartup args)
+    {
+        // Center the player's eye on the vehicle
+        if (TryComp(uid, out EyeComponent? eyeComp))
+            eyeComp.Target ??= component.Vehicle;
+    }
+
+    private void OnRiderShutdown(EntityUid uid, RiderComponent component, ComponentShutdown args)
+    {
+        // reset the riders eye centering.
+        if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
+            eyeComp.Target = null;
+    }
+
+    private void OnRiderHandleState(EntityUid uid, RiderComponent component, ref ComponentHandleState args)
     {
-        public override void Initialize()
-        {
-            base.Initialize();
-            SubscribeLocalEvent<RiderComponent, ComponentStartup>(OnRiderStartup);
-            SubscribeLocalEvent<RiderComponent, ComponentShutdown>(OnRiderShutdown);
-            SubscribeLocalEvent<RiderComponent, ComponentHandleState>(OnRiderHandleState);
-        }
-
-        private void OnRiderStartup(EntityUid uid, RiderComponent component, ComponentStartup args)
-        {
-            // Center the player's eye on the vehicle
-            if (TryComp(uid, out EyeComponent? eyeComp))
-                eyeComp.Target ??= component.Vehicle;
-        }
-
-        private void OnRiderShutdown(EntityUid uid, RiderComponent component, ComponentShutdown args)
-        {
-            // reset the riders eye centering.
-            if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
-                eyeComp.Target = null;
-        }
-
-        private void OnRiderHandleState(EntityUid uid, RiderComponent component, ref ComponentHandleState args)
-        {
-            if (args.Current is not RiderComponentState state)
-                return;
-
-            if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
-                eyeComp.Target = state.Entity;
-
-            component.Vehicle = state.Entity;
-        }
+        if (args.Current is not RiderComponentState state)
+            return;
+
+        if (TryComp(uid, out EyeComponent? eyeComp) && eyeComp.Target == component.Vehicle)
+            eyeComp.Target = state.Entity;
+
+        component.Vehicle = state.Entity;
     }
+
+    private void OnVehicleAppearanceChange(EntityUid uid, VehicleComponent component, ref AppearanceChangeEvent args)
+    {
+        if (args.Sprite == null)
+            return;
+
+        if (component.HideRider
+            && Appearance.TryGetData<bool>(uid, VehicleVisuals.HideRider, out var hide, args.Component)
+            && TryComp<SpriteComponent>(component.LastRider, out var riderSprite))
+            riderSprite.Visible = !hide;
+
+        // First check is for the sprite itself
+        if (Appearance.TryGetData<int>(uid, VehicleVisuals.DrawDepth, out var drawDepth, args.Component))
+            args.Sprite.DrawDepth = drawDepth;
+
+        // Set vehicle layer to animated or not (i.e. are the wheels turning or not)
+        if (component.AutoAnimate
+            && Appearance.TryGetData<bool>(uid, VehicleVisuals.AutoAnimate, out var autoAnimate, args.Component))
+            args.Sprite.LayerSetAutoAnimated(VehicleVisualLayers.AutoAnimate, autoAnimate);
+    }
+}
+
+public enum VehicleVisualLayers : byte
+{
+    /// Layer for the vehicle's wheels
+    AutoAnimate,
 }
diff --git a/Content.Client/Vehicle/VehicleVisualsComponent.cs b/Content.Client/Vehicle/VehicleVisualsComponent.cs
deleted file mode 100644 (file)
index 459cf3f..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Client.Vehicle;
-
-/// <summary>
-/// Controls visuals for vehicles
-/// </summary>
-[RegisterComponent]
-public sealed class VehicleVisualsComponent : Component
-{
-    public int DrawDepth = 0;
-}
diff --git a/Content.Client/Vehicle/VehicleVisualsSystem.cs b/Content.Client/Vehicle/VehicleVisualsSystem.cs
deleted file mode 100644 (file)
index 9fbbebc..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-using Robust.Client.GameObjects;
-using Content.Shared.Vehicle;
-
-namespace Content.Client.Vehicle
-{
-    /// <summary>
-    /// Controls client-side visuals for
-    /// vehicles
-    /// </summary>
-    public sealed class VehicleVisualsSystem : VisualizerSystem<VehicleVisualsComponent>
-    {
-        protected override void OnAppearanceChange(EntityUid uid, VehicleVisualsComponent component, ref AppearanceChangeEvent args)
-        {
-            if (args.Sprite == null)
-                return;
-
-            // First check is for the sprite itself
-            if (AppearanceSystem.TryGetData<int>(uid, VehicleVisuals.DrawDepth, out var drawDepth, args.Component))
-            {
-                args.Sprite.DrawDepth = drawDepth;
-            }
-
-            // Set vehicle layer to animated or not (i.e. are the wheels turning or not)
-            if (AppearanceSystem.TryGetData<bool>(uid, VehicleVisuals.AutoAnimate, out var autoAnimate, args.Component))
-            {
-                args.Sprite.LayerSetAutoAnimated(VehicleVisualLayers.AutoAnimate, autoAnimate);
-            }
-        }
-    }
-}
-public enum VehicleVisualLayers : byte
-{
-    /// Layer for the vehicle's wheels
-    AutoAnimate,
-}
index 848be3a448118fd710a6ed6fe15755c95b2068cc..f2f278b4aedec28ac8b33029c8728e8b6fca2af9 100644 (file)
@@ -1,6 +1,6 @@
 using System.Threading.Tasks;
 using Content.Server.Body.Systems;
-using Content.Server.Buckle.Systems;
+using Content.Shared.Buckle;
 using Content.Shared.ActionBlocker;
 using Content.Shared.Body.Components;
 using Content.Shared.Body.Part;
@@ -57,7 +57,7 @@ namespace Content.IntegrationTests.Tests.Buckle
             var coordinates = testMap.GridCoords;
             var entityManager = server.ResolveDependency<IEntityManager>();
             var actionBlocker = entityManager.EntitySysManager.GetEntitySystem<ActionBlockerSystem>();
-            var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<BuckleSystem>();
+            var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<SharedBuckleSystem>();
             var standingState = entityManager.EntitySysManager.GetEntitySystem<StandingStateSystem>();
 
             EntityUid human = default;
@@ -108,7 +108,7 @@ namespace Content.IntegrationTests.Tests.Buckle
                 Assert.False(buckleSystem.TryBuckle(human, human, chair, buckle));
 
                 // Trying to unbuckle too quickly fails
-                Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+                Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: 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(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+                Assert.True(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
                 Assert.Null(buckle.BuckledTo);
                 Assert.False(buckle.Buckled);
                 Assert.True(actionBlocker.CanMove(human));
@@ -135,11 +135,11 @@ namespace Content.IntegrationTests.Tests.Buckle
                 Assert.Zero(strap.OccupiedSize);
 
                 // Re-buckling has no cooldown
-                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
+                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
                 Assert.True(buckle.Buckled);
 
                 // On cooldown
-                Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+                Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
                 Assert.True(buckle.Buckled);
                 Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
                 Assert.True(buckle.Buckled);
@@ -156,15 +156,15 @@ namespace Content.IntegrationTests.Tests.Buckle
                 Assert.True(buckle.Buckled);
 
                 // Unbuckle
-                Assert.True(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+                Assert.True(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
                 Assert.False(buckle.Buckled);
 
                 // Move away from the chair
                 entityManager.GetComponent<TransformComponent>(human).WorldPosition += (1000, 1000);
 
                 // Out of range
-                Assert.False(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
-                Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+                Assert.False(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
+                Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
                 Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
 
                 // Move near the chair
@@ -172,22 +172,22 @@ namespace Content.IntegrationTests.Tests.Buckle
                     entityManager.GetComponent<TransformComponent>(chair).WorldPosition + (0.5f, 0);
 
                 // In range
-                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
+                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
                 Assert.True(buckle.Buckled);
-                Assert.False(buckleSystem.TryUnbuckle(human, human, buckle: buckle));
+                Assert.False(buckleSystem.TryUnbuckle(human, human, buckleComp: buckle));
                 Assert.True(buckle.Buckled);
                 Assert.False(buckleSystem.ToggleBuckle(human, human, chair, buckle: buckle));
                 Assert.True(buckle.Buckled);
 
                 // Force unbuckle
-                Assert.True(buckleSystem.TryUnbuckle(human, human, true, buckle: buckle));
+                Assert.True(buckleSystem.TryUnbuckle(human, human, true, buckleComp: buckle));
                 Assert.False(buckle.Buckled);
                 Assert.True(actionBlocker.CanMove(human));
                 Assert.True(actionBlocker.CanChangeDirection(human));
                 Assert.True(standingState.Down(human));
 
                 // Re-buckle
-                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
+                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
 
                 // Move away from the chair
                 entityManager.GetComponent<TransformComponent>(human).WorldPosition += (1, 0);
@@ -225,7 +225,7 @@ namespace Content.IntegrationTests.Tests.Buckle
 
             var entityManager = server.ResolveDependency<IEntityManager>();
             var handsSys = entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>();
-            var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<BuckleSystem>();
+            var buckleSystem = entityManager.EntitySysManager.GetEntitySystem<SharedBuckleSystem>();
 
             await server.WaitAssertion(() =>
             {
@@ -239,7 +239,7 @@ namespace Content.IntegrationTests.Tests.Buckle
                 Assert.True(entityManager.TryGetComponent(human, out body));
 
                 // Buckle
-                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
+                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
                 Assert.NotNull(buckle.BuckledTo);
                 Assert.True(buckle.Buckled);
 
@@ -288,7 +288,7 @@ namespace Content.IntegrationTests.Tests.Buckle
                     Assert.Null(hand.HeldEntity);
                 }
 
-                buckleSystem.TryUnbuckle(human, human, true, buckle: buckle);
+                buckleSystem.TryUnbuckle(human, human, true, buckleComp: buckle);
             });
 
             await pairTracker.CleanReturnAsync();
@@ -304,7 +304,7 @@ namespace Content.IntegrationTests.Tests.Buckle
             var testMap = await PoolManager.CreateTestMap(pairTracker);
             var coordinates = testMap.GridCoords;
             var entityManager = server.ResolveDependency<IEntityManager>();
-            var buckleSystem = entityManager.System<BuckleSystem>();
+            var buckleSystem = entityManager.System<SharedBuckleSystem>();
 
             EntityUid human = default;
             EntityUid chair = default;
@@ -320,7 +320,7 @@ namespace Content.IntegrationTests.Tests.Buckle
                 Assert.True(entityManager.HasComponent<StrapComponent>(chair));
 
                 // Buckle
-                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
+                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
                 Assert.NotNull(buckle.BuckledTo);
                 Assert.True(buckle.Buckled);
 
@@ -338,7 +338,7 @@ namespace Content.IntegrationTests.Tests.Buckle
                 entityManager.GetComponent<TransformComponent>(human).WorldPosition -= (100, 0);
 
                 // Buckle
-                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckle: buckle));
+                Assert.True(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
                 Assert.NotNull(buckle.BuckledTo);
                 Assert.True(buckle.Buckled);
             });
index 8707b14ed30a624d07b472a743bea451b671092f..b0a4e190994a3aa6c26afaca168e957336b8471e 100644 (file)
@@ -1,5 +1,5 @@
-using Content.Server.Buckle.Systems;
 using Content.Shared.Alert;
+using Content.Shared.Buckle;
 using JetBrains.Annotations;
 
 namespace Content.Server.Alert.Click
@@ -13,7 +13,7 @@ namespace Content.Server.Alert.Click
     {
         public void AlertClicked(EntityUid player)
         {
-            IoCManager.Resolve<IEntityManager>().System<BuckleSystem>().TryUnbuckle(player, player);
+            IoCManager.Resolve<IEntityManager>().System<SharedBuckleSystem>().TryUnbuckle(player, player);
         }
     }
 }
index 01346fd19ac174b9e96bd0b56a563d4a6629af28..9791eb26212b22f44cd7d2766242a12f82b940f2 100644 (file)
@@ -40,7 +40,7 @@ namespace Content.Server.Bed
             SubscribeLocalEvent<StasisBedComponent, UpgradeExamineEvent>(OnUpgradeExamine);
         }
 
-        private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, BuckleChangeEvent args)
+        private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, ref BuckleChangeEvent args)
         {
             _prototypeManager.TryIndex<InstantActionPrototype>("Sleep", out var sleepAction);
             if (args.Buckling)
@@ -92,7 +92,7 @@ namespace Content.Server.Bed
             _appearance.SetData(uid, StasisBedVisuals.IsOn, isOn);
         }
 
-        private void OnBuckleChange(EntityUid uid, StasisBedComponent component, BuckleChangeEvent args)
+        private void OnBuckleChange(EntityUid uid, StasisBedComponent component, ref BuckleChangeEvent args)
         {
             // In testing this also received an unbuckle event when the bed is destroyed
             // So don't worry about that
diff --git a/Content.Server/Buckle/Systems/BuckleSystem.Buckle.cs b/Content.Server/Buckle/Systems/BuckleSystem.Buckle.cs
deleted file mode 100644 (file)
index eb62e90..0000000
+++ /dev/null
@@ -1,419 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using Content.Server.Administration.Logs;
-using Content.Shared.Alert;
-using Content.Shared.Bed.Sleep;
-using Content.Shared.Buckle.Components;
-using Content.Shared.Database;
-using Content.Shared.DragDrop;
-using Content.Shared.Hands.Components;
-using Content.Shared.IdentityManagement;
-using Content.Shared.Interaction;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Pulling.Components;
-using Content.Shared.Storage.Components;
-using Content.Shared.Stunnable;
-using Content.Shared.Vehicle.Components;
-using Content.Shared.Verbs;
-using Robust.Shared.GameStates;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Buckle.Systems;
-
-public sealed partial class BuckleSystem
-{
-    [Dependency] private readonly IAdminLogManager _adminLogger = default!;
-
-    private void InitializeBuckle()
-    {
-        SubscribeLocalEvent<BuckleComponent, ComponentStartup>(OnBuckleStartup);
-        SubscribeLocalEvent<BuckleComponent, ComponentShutdown>(OnBuckleShutdown);
-        SubscribeLocalEvent<BuckleComponent, ComponentGetState>(OnBuckleGetState);
-        SubscribeLocalEvent<BuckleComponent, MoveEvent>(MoveEvent);
-        SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(HandleInteractHand);
-        SubscribeLocalEvent<BuckleComponent, GetVerbsEvent<InteractionVerb>>(AddUnbuckleVerb);
-        SubscribeLocalEvent<BuckleComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
-        SubscribeLocalEvent<BuckleComponent, CanDropDraggedEvent>(OnBuckleCanDrop);
-        SubscribeLocalEvent<BuckleComponent, DragDropDraggedEvent>(OnBuckleDragDrop);
-    }
-
-    private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetVerbsEvent<InteractionVerb> args)
-    {
-        if (!args.CanAccess || !args.CanInteract || !component.Buckled)
-            return;
-
-        InteractionVerb verb = new()
-        {
-            Act = () => TryUnbuckle(uid, args.User, buckle: component),
-            Text = Loc.GetString("verb-categories-unbuckle"),
-            Icon = new SpriteSpecifier.Texture(new ("/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);
-    }
-
-    private void OnBuckleStartup(EntityUid uid, BuckleComponent component, ComponentStartup args)
-    {
-        UpdateBuckleStatus(uid, component);
-    }
-
-    private void OnBuckleShutdown(EntityUid uid, BuckleComponent component, ComponentShutdown args)
-    {
-        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)
-    {
-        if (TryUnbuckle(uid, args.User, buckle: component))
-            args.Handled = true;
-    }
-
-    private void MoveEvent(EntityUid uid, BuckleComponent buckle, ref MoveEvent ev)
-    {
-        var strap = buckle.BuckledTo;
-
-        if (strap == null)
-        {
-            return;
-        }
-
-        var strapPosition = Transform(strap.Owner).Coordinates;
-
-        if (ev.NewPosition.InRange(EntityManager, strapPosition, strap.MaxBuckleDistance))
-        {
-            return;
-        }
-
-        TryUnbuckle(uid, buckle.Owner, true, buckle);
-    }
-
-    private void OnEntityStorageInsertAttempt(EntityUid uid, BuckleComponent comp, ref InsertIntoEntityStorageAttemptEvent args)
-    {
-        if (comp.Buckled)
-            args.Cancelled = true;
-    }
-
-    private void OnBuckleCanDrop(EntityUid uid, BuckleComponent component, ref CanDropDraggedEvent args)
-    {
-        args.Handled = HasComp<StrapComponent>(args.Target);
-    }
-
-    private void OnBuckleDragDrop(EntityUid uid, BuckleComponent component, ref DragDropDraggedEvent args)
-    {
-        args.Handled = TryBuckle(uid, args.User, args.Target, component);
-    }
-
-    /// <summary>
-    ///     Shows or hides the buckled status effect depending on if the
-    ///     entity is buckled or not.
-    /// </summary>
-    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);
-    }
-
-    private 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<HandsComponent>(user))
-        {
-            _popups.PopupEntity(Loc.GetString("buckle-component-no-hands-message"), user, 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, 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, user);
-
-                return false;
-            }
-
-            parent = Transform(parent).ParentUid;
-        }
-
-        if (!StrapHasSpace(to, buckle, strap))
-        {
-            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, 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.PlayPvs(strap.BuckleSound, buckleId);
-
-        if (!StrapTryAdd(to, buckle, strap: strap))
-        {
-            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, user);
-            return false;
-        }
-
-        if (TryComp<AppearanceComponent>(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);
-            }
-        }
-
-        // Logging
-        if (user != buckleId)
-            _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} buckled {ToPrettyString(buckleId)} to {ToPrettyString(to)}");
-        else
-            _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} buckled themselves to {ToPrettyString(to)}");
-
-        return true;
-    }
-
-    /// <summary>
-    ///     Tries to unbuckle the Owner of this component from its current strap.
-    /// </summary>
-    /// <param name="buckleId">The entity to unbuckle.</param>
-    /// <param name="user">The entity doing the unbuckling.</param>
-    /// <param name="force">
-    ///     Whether to force the unbuckling or not. Does not guarantee true to
-    ///     be returned, but guarantees the owner to be unbuckled afterwards.
-    /// </param>
-    /// <param name="buckle">The buckle component of the entity to unbuckle.</param>
-    /// <returns>
-    ///     true if the owner was unbuckled, otherwise false even if the owner
-    ///     was previously already unbuckled.
-    /// </returns>
-    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<SleepingComponent>(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;
-        }
-
-        // Logging
-        if (user != buckleId)
-            _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} unbuckled {ToPrettyString(buckleId)} from {ToPrettyString(oldBuckledTo.Owner)}");
-        else
-            _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):player} unbuckled themselves from {ToPrettyString(oldBuckledTo.Owner)}");
-
-        SetBuckledTo(buckle, null);
-
-        var xform = Transform(buckleId);
-        var oldBuckledXform = Transform(oldBuckledTo.Owner);
-
-        if (xform.ParentUid == oldBuckledXform.Owner && !Terminating(xform.ParentUid))
-        {
-            _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 ((TryComp<MobStateComponent>(buckleId, out var mobState) && _mobState.IsIncapacitated(buckleId, mobState)) ||
-            HasComp<KnockedDownComponent>(buckleId))
-        {
-            _standing.Down(buckleId);
-        }
-        else
-        {
-            _standing.Stand(buckleId);
-        }
-
-        if (_mobState.IsIncapacitated(buckleId, mobState))
-        {
-            _standing.Down(buckleId);
-        }
-        // Sync StrapComponent data
-        _appearance.SetData(oldBuckledTo.Owner, StrapVisuals.State, false);
-        if (oldBuckledTo.BuckledEntities.Remove(buckleId))
-        {
-            oldBuckledTo.OccupiedSize -= buckle.Size;
-            Dirty(oldBuckledTo);
-        }
-
-        _audio.PlayPvs(oldBuckledTo.UnbuckleSound, buckleId);
-
-        var ev = new BuckleChangeEvent { Buckling = false, Strap = oldBuckledTo.Owner, BuckledEntity = buckleId };
-        RaiseLocalEvent(buckleId, ev);
-        RaiseLocalEvent(oldBuckledTo.Owner, ev);
-
-        return true;
-    }
-
-    /// <summary>
-    ///     Makes an entity toggle the buckling status of the owner to a
-    ///     specific entity.
-    /// </summary>
-    /// <param name="buckleId">The entity to buckle/unbuckle from <see cref="to"/>.</param>
-    /// <param name="user">The entity doing the buckling/unbuckling.</param>
-    /// <param name="to">
-    ///     The entity to toggle the buckle status of the owner to.
-    /// </param>
-    /// <param name="force">
-    ///     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.
-    /// </param>
-    /// <param name="buckle">The buckle component of the entity to buckle/unbuckle from <see cref="to"/>.</param>
-    /// <returns>true if the buckling status was changed, false otherwise.</returns>
-    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/BuckleSystem.Strap.cs b/Content.Server/Buckle/Systems/BuckleSystem.Strap.cs
deleted file mode 100644 (file)
index 1ca2acd..0000000
+++ /dev/null
@@ -1,241 +0,0 @@
-using System.Linq;
-using Content.Server.Construction.Completions;
-using Content.Shared.Buckle.Components;
-using Content.Shared.Destructible;
-using Content.Shared.DragDrop;
-using Content.Shared.Interaction;
-using Content.Shared.Storage;
-using Content.Shared.Verbs;
-using Robust.Server.GameObjects;
-using Robust.Shared.Containers;
-using Robust.Shared.GameStates;
-
-namespace Content.Server.Buckle.Systems;
-
-public sealed partial class BuckleSystem
-{
-    private void InitializeStrap()
-    {
-        SubscribeLocalEvent<StrapComponent, ComponentShutdown>(OnStrapShutdown);
-        SubscribeLocalEvent<StrapComponent, ComponentRemove>((_, c, _) => StrapRemoveAll(c));
-        SubscribeLocalEvent<StrapComponent, ComponentGetState>(OnStrapGetState);
-        SubscribeLocalEvent<StrapComponent, EntInsertedIntoContainerMessage>(ContainerModifiedStrap);
-        SubscribeLocalEvent<StrapComponent, EntRemovedFromContainerMessage>(ContainerModifiedStrap);
-        SubscribeLocalEvent<StrapComponent, GetVerbsEvent<InteractionVerb>>(AddStrapVerbs);
-        SubscribeLocalEvent<StrapComponent, ContainerGettingInsertedAttemptEvent>(OnStrapInsertAttempt);
-        SubscribeLocalEvent<StrapComponent, InteractHandEvent>(OnStrapInteractHand);
-        SubscribeLocalEvent<StrapComponent, DestructionEventArgs>((_,c,_) => StrapRemoveAll(c));
-        SubscribeLocalEvent<StrapComponent, BreakageEventArgs>((_, c, _) => StrapRemoveAll(c));
-        SubscribeLocalEvent<StrapComponent, ConstructionBeforeDeleteEvent>((_, c, _) => StrapRemoveAll(c));
-        SubscribeLocalEvent<StrapComponent, DragDropTargetEvent>(OnStrapDragDrop);
-    }
-
-    private void OnStrapGetState(EntityUid uid, StrapComponent component, ref ComponentGetState args)
-    {
-        args.State = new StrapComponentState(component.Position, component.BuckleOffset, component.BuckledEntities, component.MaxBuckleDistance);
-    }
-
-    private void ContainerModifiedStrap(EntityUid uid, StrapComponent strap, ContainerModifiedMessage message)
-    {
-        if (GameTiming.ApplyingState)
-            return;
-
-        foreach (var buckledEntity in strap.BuckledEntities)
-        {
-            if (!TryComp(buckledEntity, out BuckleComponent? buckled))
-            {
-                continue;
-            }
-
-            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);
-        }
-    }
-
-    private void OnStrapShutdown(EntityUid uid, StrapComponent component, ComponentShutdown args)
-    {
-        if (LifeStage(uid) > EntityLifeStage.MapInitialized)
-            return;
-
-        StrapRemoveAll(component);
-    }
-
-    private void OnStrapInsertAttempt(EntityUid uid, StrapComponent component, ContainerGettingInsertedAttemptEvent args)
-    {
-        // If someone is attempting to put this item inside of a backpack, ensure that it has no entities strapped to it.
-        if (HasComp<SharedStorageComponent>(args.Container.Owner) && component.BuckledEntities.Count != 0)
-            args.Cancel();
-    }
-
-    private void OnStrapInteractHand(EntityUid uid, StrapComponent component, InteractHandEvent args)
-    {
-        if (args.Handled)
-            return;
-
-        ToggleBuckle(args.User, args.User, uid);
-    }
-
-    private void AddStrapVerbs(EntityUid uid, StrapComponent strap, GetVerbsEvent<InteractionVerb> args)
-    {
-        if (args.Hands == null || !args.CanAccess || !args.CanInteract || !strap.Enabled)
-            return;
-
-        // Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this
-        // range can be set per-component, so we have to check a modified InRangeUnobstructed for every verb.
-
-        // Add unstrap verbs for every strapped entity.
-        foreach (var entity in strap.BuckledEntities)
-        {
-            var buckledComp = Comp<BuckleComponent>(entity);
-
-            if (!_interactions.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range))
-                continue;
-
-            InteractionVerb verb = new()
-            {
-                Act = () => TryUnbuckle(entity, args.User, buckle: buckledComp),
-                Category = VerbCategory.Unbuckle
-            };
-
-            if (entity == args.User)
-                verb.Text = Loc.GetString("verb-self-target-pronoun");
-            else
-                verb.Text = Comp<MetaDataComponent>(entity).EntityName;
-
-            // In the event that you have more than once entity with the same name strapped to the same object,
-            // these two verbs will be identical according to Verb.CompareTo, and only one with actually be added to
-            // the verb list. However this should rarely ever be a problem. If it ever is, it could be fixed by
-            // appending an integer to verb.Text to distinguish the verbs.
-
-            args.Verbs.Add(verb);
-        }
-
-        // Add a verb to buckle the user.
-        if (TryComp(args.User, out BuckleComponent? buckle) &&
-            buckle.BuckledTo != strap &&
-            args.User != strap.Owner &&
-            StrapHasSpace(uid, buckle, strap) &&
-            _interactions.InRangeUnobstructed(args.User, args.Target, range: buckle.Range))
-        {
-            InteractionVerb verb = new()
-            {
-                Act = () => TryBuckle(args.User, args.User, args.Target, buckle),
-                Category = VerbCategory.Buckle,
-                Text = Loc.GetString("verb-self-target-pronoun")
-            };
-            args.Verbs.Add(verb);
-        }
-
-        // If the user is currently holding/pulling an entity that can be buckled, add a verb for that.
-        if (args.Using is {Valid: true} @using &&
-            TryComp(@using, out BuckleComponent? usingBuckle) &&
-            StrapHasSpace(uid, usingBuckle, strap) &&
-            _interactions.InRangeUnobstructed(@using, args.Target, range: usingBuckle.Range))
-        {
-            // Check that the entity is unobstructed from the target (ignoring the user).
-            bool Ignored(EntityUid entity) => entity == args.User || entity == args.Target || entity == @using;
-            if (!_interactions.InRangeUnobstructed(@using, args.Target, usingBuckle.Range, predicate: Ignored))
-                return;
-
-            InteractionVerb verb = new()
-            {
-                Act = () => TryBuckle(@using, args.User, args.Target, usingBuckle),
-                Category = VerbCategory.Buckle,
-                Text = Comp<MetaDataComponent>(@using).EntityName,
-                // just a held object, the user is probably just trying to sit down.
-                // If the used entity is a person being pulled, prioritize this verb. Conversely, if it is
-                Priority = HasComp<ActorComponent>(@using) ? 1 : -1
-            };
-
-            args.Verbs.Add(verb);
-        }
-    }
-
-    private void StrapRemoveAll(StrapComponent strap)
-    {
-        foreach (var entity in strap.BuckledEntities.ToArray())
-        {
-            TryUnbuckle(entity, entity, true);
-        }
-
-        strap.BuckledEntities.Clear();
-        strap.OccupiedSize = 0;
-        Dirty(strap);
-    }
-
-    private void OnStrapDragDrop(EntityUid uid, StrapComponent component, ref DragDropTargetEvent args)
-    {
-        if (!StrapCanDragDropOn(uid, args.User, uid, args.Dragged, component))
-            return;
-
-        args.Handled = TryBuckle(args.Dragged, args.User, uid);
-    }
-
-    private bool StrapHasSpace(EntityUid strapId, BuckleComponent buckle, StrapComponent? strap = null)
-    {
-        if (!Resolve(strapId, ref strap, false))
-            return false;
-
-        return strap.OccupiedSize + buckle.Size <= strap.Size;
-    }
-
-    private bool StrapTryAdd(EntityUid strapId, BuckleComponent buckle, bool force = false, StrapComponent? strap = null)
-    {
-        if (!Resolve(strapId, ref strap, false) ||
-            !strap.Enabled)
-        {
-            return false;
-        }
-
-        if (!force && !StrapHasSpace(strapId, buckle, strap))
-            return false;
-
-        if (!strap.BuckledEntities.Add(buckle.Owner))
-            return false;
-
-        strap.OccupiedSize += buckle.Size;
-
-        _appearance.SetData(buckle.Owner, StrapVisuals.RotationAngle, strap.Rotation);
-
-        _appearance.SetData(strap.Owner, StrapVisuals.State, true);
-
-        Dirty(strap);
-        return true;
-    }
-
-    public void StrapSetEnabled(EntityUid strapId, bool enabled, StrapComponent? strap = null)
-    {
-        if (!Resolve(strapId, ref strap, false) ||
-            strap.Enabled == enabled)
-        {
-            return;
-        }
-
-        strap.Enabled = enabled;
-
-        if (!enabled)
-            StrapRemoveAll(strap);
-    }
-}
index f550208e967fce6a4e4f1860c371bab4c8e22819..50de302fb0e4af3bb44f689fff52c87d50cf1344 100644 (file)
@@ -1,41 +1,7 @@
-using Content.Server.Interaction;
-using Content.Server.Popups;
-using Content.Server.Pulling;
-using Content.Shared.ActionBlocker;
-using Content.Shared.Alert;
 using Content.Shared.Buckle;
-using Content.Shared.Mobs.Systems;
-using JetBrains.Annotations;
-using Robust.Server.Containers;
-using Robust.Server.GameObjects;
-using Robust.Shared.Timing;
 
 namespace Content.Server.Buckle.Systems;
 
-[UsedImplicitly]
-public sealed partial class BuckleSystem : SharedBuckleSystem
+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 MobStateSystem _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()
-    {
-        base.Initialize();
-
-        UpdatesAfter.Add(typeof(InteractionSystem));
-        UpdatesAfter.Add(typeof(InputSystem));
-
-        InitializeBuckle();
-        InitializeStrap();
-    }
 }
index 65759e2a3bda92ee4e3aba767080307178bd7f56..6f1a4ca8e3774a930418d4c80afab37c6e1d3a41 100644 (file)
@@ -342,7 +342,7 @@ public sealed class ClimbSystem : SharedClimbSystem
         Climb(uid, uid, uid, climbable, true, component);
     }
 
-    private void OnBuckleChange(EntityUid uid, ClimbingComponent component, BuckleChangeEvent args)
+    private void OnBuckleChange(EntityUid uid, ClimbingComponent component, ref BuckleChangeEvent args)
     {
         if (!args.Buckling)
             return;
index e5abe041dc07f0d61d5a20da10a5d476346f321c..8e7177f81a2f6bc7f6305ae7e07afb9a9bc02ec3 100644 (file)
@@ -25,7 +25,7 @@ namespace Content.Server.Disease.Cures
         public override bool Cure(DiseaseEffectArgs args)
         {
             if (!args.EntityManager.TryGetComponent<BuckleComponent>(args.DiseasedEntity, out var buckle) ||
-                !args.EntityManager.HasComponent<HealOnBuckleComponent>(buckle.BuckledTo?.Owner))
+                !args.EntityManager.HasComponent<HealOnBuckleComponent>(buckle.BuckledTo))
                 return false;
 
             var ticks = 1;
index 04a967f6500c503b5505184646dd772ec07f3bd2..e21ac657278e715116794fd42e20a7a42b66fc55 100644 (file)
@@ -1,6 +1,6 @@
 using System.Linq;
-using Content.Server.Buckle.Systems;
 using Content.Server.Storage.Components;
+using Content.Shared.Buckle;
 using Content.Shared.Buckle.Components;
 using Content.Shared.Foldable;
 using Content.Shared.Verbs;
@@ -13,7 +13,7 @@ namespace Content.Server.Foldable
     [UsedImplicitly]
     public sealed class FoldableSystem : SharedFoldableSystem
     {
-        [Dependency] private readonly BuckleSystem _buckle = default!;
+        [Dependency] private readonly SharedBuckleSystem _buckle = default!;
         [Dependency] private readonly SharedContainerSystem _container = default!;
 
         public override void Initialize()
diff --git a/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs b/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs
deleted file mode 100644 (file)
index 83ab63f..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-using Content.Shared.Actions.ActionTypes;
-using Content.Shared.Light;
-using Robust.Shared.Audio;
-
-namespace Content.Server.Light.Components
-{
-    /// <summary>
-    ///     This is simplified version of <see cref="HandheldLightComponent"/>.
-    ///     It doesn't consume any power and can be toggle only by verb.
-    /// </summary>
-    [RegisterComponent]
-    public sealed class UnpoweredFlashlightComponent : Component
-    {
-        [DataField("toggleFlashlightSound")]
-        public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
-
-        [ViewVariables] public bool LightOn = false;
-
-        [DataField("toggleAction", required: true)]
-        public InstantAction ToggleAction = new();
-    }
-}
index 7bb1ec52eb0a5b3180a15aff25ddd6598d4e62c7..558ed39a2cdff8d47c571d6f4e049aaa9b355e0c 100644 (file)
@@ -3,6 +3,7 @@ using Content.Server.Light.Events;
 using Content.Server.Mind.Components;
 using Content.Shared.Actions;
 using Content.Shared.Light;
+using Content.Shared.Light.Component;
 using Content.Shared.Toggleable;
 using Content.Shared.Verbs;
 using Robust.Server.GameObjects;
index e53ee579facdf1a623a7086f3a1771a4db1ae1d0..b7a07572a69f42bab4424f694c4ea2bfd5e906fa 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Server.Buckle.Systems;
+using Content.Shared.Buckle;
 
 namespace Content.Server.NPC.HTN.Preconditions;
 
@@ -7,14 +7,14 @@ namespace Content.Server.NPC.HTN.Preconditions;
 /// </summary>
 public sealed class BuckledPrecondition : HTNPrecondition
 {
-    private BuckleSystem _buckle = default!;
+    private SharedBuckleSystem _buckle = default!;
 
     [ViewVariables(VVAccess.ReadWrite)] [DataField("isBuckled")] public bool IsBuckled = true;
 
     public override void Initialize(IEntitySystemManager sysManager)
     {
         base.Initialize(sysManager);
-        _buckle = sysManager.GetEntitySystem<BuckleSystem>();
+        _buckle = sysManager.GetEntitySystem<SharedBuckleSystem>();
     }
 
     public override bool IsMet(NPCBlackboard blackboard)
index 92bacf95b7f61ef33ee629782770422a142fe18a..f0448003fc5b4411100e75ab3dbeebe23340cf5a 100644 (file)
@@ -15,6 +15,7 @@ using Robust.Shared.Containers;
 using Robust.Shared.Map;
 using Content.Server.Mind.Components;
 using Content.Server.Traitor;
+using Content.Shared.Light.Component;
 
 namespace Content.Server.PDA
 {
index c08a92be1739d2c2248e8cafbf3386ab1a4704fe..621eafb3b64c4f0e70e006d70d4d0bb67042c378 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Actions;
-using Content.Server.Buckle.Systems;
 using Content.Server.Humanoid;
 using Content.Server.Inventory;
 using Content.Server.Mind.Commands;
@@ -7,6 +6,7 @@ using Content.Server.Mind.Components;
 using Content.Server.Polymorph.Components;
 using Content.Shared.Actions;
 using Content.Shared.Actions.ActionTypes;
+using Content.Shared.Buckle;
 using Content.Shared.Damage;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.IdentityManagement;
@@ -30,7 +30,7 @@ namespace Content.Server.Polymorph.Systems
         [Dependency] private readonly IPrototypeManager _proto = default!;
         [Dependency] private readonly ActionsSystem _actions = default!;
         [Dependency] private readonly AudioSystem _audio = default!;
-        [Dependency] private readonly BuckleSystem _buckle = default!;
+        [Dependency] private readonly SharedBuckleSystem _buckle = default!;
         [Dependency] private readonly ContainerSystem _container = default!;
         [Dependency] private readonly DamageableSystem _damageable = default!;
         [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
index d158b71616a4c69b4aa79e9a9613880c4d3ff9b6..1177fdf1d7fe2538bf21fa84351666f8c30da2eb 100644 (file)
@@ -1,12 +1,11 @@
 using Content.Server.Body.Systems;
-using Content.Server.Buckle.Systems;
+using Content.Shared.Buckle;
 using Content.Server.Popups;
 using Content.Server.Storage.Components;
 using Content.Server.Storage.EntitySystems;
 using Content.Shared.Body.Components;
 using Content.Shared.Body.Part;
 using Content.Shared.Buckle.Components;
-using Content.Shared.DoAfter;
 using Content.Shared.Examine;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Interaction;
@@ -37,7 +36,7 @@ namespace Content.Server.Toilet
             SubscribeLocalEvent<ToiletComponent, ComponentInit>(OnInit);
             SubscribeLocalEvent<ToiletComponent, MapInitEvent>(OnMapInit);
             SubscribeLocalEvent<ToiletComponent, InteractUsingEvent>(OnInteractUsing);
-            SubscribeLocalEvent<ToiletComponent, InteractHandEvent>(OnInteractHand, new []{typeof(BuckleSystem)});
+            SubscribeLocalEvent<ToiletComponent, InteractHandEvent>(OnInteractHand, new []{typeof(SharedBuckleSystem)});
             SubscribeLocalEvent<ToiletComponent, ExaminedEvent>(OnExamine);
             SubscribeLocalEvent<ToiletComponent, SuicideEvent>(OnSuicide);
             SubscribeLocalEvent<ToiletComponent, ToiletPryDoAfterEvent>(OnToiletPried);
diff --git a/Content.Server/Vehicle/VehicleSystem.Rider.cs b/Content.Server/Vehicle/VehicleSystem.Rider.cs
deleted file mode 100644 (file)
index eb835e3..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-using Content.Server.Standing;
-using Content.Shared.Hands;
-using Content.Shared.Mobs;
-using Content.Shared.Vehicle.Components;
-using Robust.Shared.GameStates;
-
-namespace Content.Server.Vehicle
-{
-    public sealed partial class VehicleSystem
-    {
-        private void InitializeRider()
-        {
-            SubscribeLocalEvent<RiderComponent, ComponentGetState>(OnRiderGetState);
-            SubscribeLocalEvent<RiderComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
-            SubscribeLocalEvent<RiderComponent, FellDownEvent>(OnFallDown);
-            SubscribeLocalEvent<RiderComponent, MobStateChangedEvent>(OnMobStateChanged);
-        }
-
-        private void OnRiderGetState(EntityUid uid, RiderComponent component, ref ComponentGetState args)
-        {
-            args.State = new RiderComponentState()
-            {
-                Entity = component.Vehicle,
-            };
-        }
-
-        /// <summary>
-        /// Kick the rider off the vehicle if they press q / drop the virtual item
-        /// </summary>
-        private void OnVirtualItemDeleted(EntityUid uid, RiderComponent component, VirtualItemDeletedEvent args)
-        {
-            if (args.BlockingEntity == component.Vehicle)
-            {
-                UnbuckleFromVehicle(uid);
-            }
-        }
-
-        /// <summary>
-        /// Kick the rider off the vehicle if they get stunned
-        /// </summary>
-        private void OnFallDown(EntityUid uid, RiderComponent rider, FellDownEvent args)
-        {
-           UnbuckleFromVehicle(uid);
-        }
-
-        /// <summary>
-        /// Kick the rider off the vehicle if they go into crit or die.
-        /// </summary>
-        private void OnMobStateChanged(EntityUid uid, RiderComponent rider, MobStateChangedEvent args)
-        {
-            if (args.NewMobState is MobState.Critical or MobState.Dead)
-            {
-                UnbuckleFromVehicle(uid);
-            }
-        }
-
-        public void UnbuckleFromVehicle(EntityUid uid)
-        {
-            _buckle.TryUnbuckle(uid, uid, true);
-        }
-    }
-}
index 737c7ff3593ed49ffc0712b8c06424f85d7160f7..f0eea36ffba4506ed8b36c126c3797f5e3b19466 100644 (file)
@@ -1,125 +1,7 @@
-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.Movement.Systems;
 using Content.Shared.Vehicle;
-using Content.Shared.Vehicle.Components;
-using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
-using Robust.Shared.Physics.Systems;
-using Robust.Shared.Player;
 
-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!;
-        [Dependency] private readonly SharedMoverController _mover = default!;
-
-        public override void Initialize()
-        {
-            base.Initialize();
-
-            InitializeRider();
-
-            SubscribeLocalEvent<VehicleComponent, HonkActionEvent>(OnHonk);
-            SubscribeLocalEvent<VehicleComponent, BuckleChangeEvent>(OnBuckleChange);
-        }
-
-        /// <summary>
-        /// This fires when the rider presses the honk action
-        /// </summary>
-        private void OnHonk(EntityUid uid, VehicleComponent vehicle, HonkActionEvent args)
-        {
-            if (args.Handled || vehicle.HornSound == null)
-                return;
-
-            // TODO: Need audio refactor maybe, just some way to null it when the stream is over.
-            // For now better to just not loop to keep the code much cleaner.
-            vehicle.HonkPlayingStream?.Stop();
-            vehicle.HonkPlayingStream = SoundSystem.Play(vehicle.HornSound.GetSound(), Filter.Pvs(uid), uid, vehicle.HornSound.Params);
-            args.Handled = true;
-        }
-
-        /// <summary>
-        /// This just controls whether the wheels are turning.
-        /// </summary>
-        public override void Update(float frameTime)
-        {
-            foreach (var (vehicle, mover) in EntityQuery<VehicleComponent, InputMoverComponent>())
-            {
-                if (_mover.GetVelocityInput(mover).Sprinting == Vector2.Zero)
-                {
-                    UpdateAutoAnimate(vehicle.Owner, false);
-                    continue;
-                }
-                UpdateAutoAnimate(vehicle.Owner, true);
-            }
-        }
-
-        /// <summary>
-        /// Give the user the rider component if they're buckling to the vehicle,
-        /// otherwise remove it.
-        /// </summary>
-        private void OnBuckleChange(EntityUid uid, VehicleComponent component, BuckleChangeEvent args)
-        {
-            // Add Rider
-            if (args.Buckling)
-            {
-                // Add a virtual item to rider's hand, unbuckle if we can't.
-                if (!_virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.BuckledEntity))
-                {
-                    UnbuckleFromVehicle(args.BuckledEntity);
-                    return;
-                }
-
-                // Set up the rider and vehicle with each other
-                EnsureComp<InputMoverComponent>(uid);
-                var rider = EnsureComp<RiderComponent>(args.BuckledEntity);
-                component.Rider = args.BuckledEntity;
-
-                var relay = EnsureComp<RelayInputMoverComponent>(args.BuckledEntity);
-                _mover.SetRelay(args.BuckledEntity, uid, relay);
-                rider.Vehicle = uid;
+namespace Content.Server.Vehicle;
 
-                // Update appearance stuff, add actions
-                UpdateBuckleOffset(Transform(uid), component);
-                if (TryComp<InputMoverComponent>(uid, out var mover))
-                    UpdateDrawDepth(uid, GetDrawDepth(Transform(uid), component, mover.RelativeRotation.Degrees));
-
-                if (TryComp<ActionsComponent>(args.BuckledEntity, out var actions) && TryComp<UnpoweredFlashlightComponent>(uid, out var flashlight))
-                {
-                    _actionsSystem.AddAction(args.BuckledEntity, flashlight.ToggleAction, uid, actions);
-                }
-
-                if (component.HornSound != null)
-                {
-                    _actionsSystem.AddAction(args.BuckledEntity, component.HornAction, uid, actions);
-                }
-
-                _joints.ClearJoints(args.BuckledEntity);
-
-                return;
-            }
-
-            // Remove rider
-
-            // Clean up actions and virtual items
-            _actionsSystem.RemoveProvidedActions(args.BuckledEntity, uid);
-            _virtualItemSystem.DeleteInHandsMatching(args.BuckledEntity, uid);
-
-            // Entity is no longer riding
-            RemComp<RiderComponent>(args.BuckledEntity);
-            RemComp<RelayInputMoverComponent>(args.BuckledEntity);
-
-            // Reset component
-            component.Rider = null;
-        }
-    }
+public sealed class VehicleSystem : SharedVehicleSystem
+{
 }
index 3d7269ce96747eaeabe91e738af796293bb5ea43..1e4cc3c19628373d1c8e78982d15a124194a5943 100644 (file)
@@ -5,76 +5,99 @@ using Robust.Shared.Serialization;
 namespace Content.Shared.Buckle.Components;
 
 [RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedBuckleSystem))]
 public sealed class BuckleComponent : Component
 {
     /// <summary>
-    ///     The range from which this entity can buckle to a <see cref="StrapComponent"/>.
+    /// The range from which this entity can buckle to a <see cref="StrapComponent"/>.
+    /// Separated from normal interaction range to fix the "someone buckled to a strap
+    /// across a table two tiles away" problem.
     /// </summary>
     [DataField("range")]
+    [ViewVariables(VVAccess.ReadWrite)]
     public float Range = SharedInteractionSystem.InteractionRange / 1.4f;
 
     /// <summary>
-    ///     True if the entity is buckled, false otherwise.
+    /// True if the entity is buckled, false otherwise.
     /// </summary>
-    public bool Buckled { get; set; }
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool Buckled;
 
-    public EntityUid? LastEntityBuckledTo { get; set; }
+    [ViewVariables]
+    public EntityUid? LastEntityBuckledTo;
 
-    public bool DontCollide { get; set; }
+    /// <summary>
+    /// Whether or not collisions should be possible with the entity we are strapped to
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite)]
+    [DataField("dontCollide")]
+    public bool DontCollide;
+
+    /// <summary>
+    /// Whether or not we should be allowed to pull the entity we are strapped to
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite)]
+    [DataField("pullStrap")]
+    public bool PullStrap;
 
     /// <summary>
-    ///     The amount of time that must pass for this entity to
-    ///     be able to unbuckle after recently buckling.
+    /// The amount of time that must pass for this entity to
+    /// be able to unbuckle after recently buckling.
     /// </summary>
     [DataField("delay")]
+    [ViewVariables(VVAccess.ReadWrite)]
     public TimeSpan UnbuckleDelay = TimeSpan.FromSeconds(0.25f);
 
     /// <summary>
-    ///     The time that this entity buckled at.
+    /// The time that this entity buckled at.
     /// </summary>
-    [ViewVariables] public TimeSpan BuckleTime;
+    [ViewVariables]
+    public TimeSpan BuckleTime;
 
     /// <summary>
-    ///     The strap that this component is buckled to.
+    /// The strap that this component is buckled to.
     /// </summary>
     [ViewVariables]
-    public StrapComponent? BuckledTo { get; set; }
+    public EntityUid? BuckledTo;
 
     /// <summary>
-    ///     The amount of space that this entity occupies in a
-    ///     <see cref="StrapComponent"/>.
+    /// The amount of space that this entity occupies in a
+    /// <see cref="StrapComponent"/>.
     /// </summary>
     [DataField("size")]
+    [ViewVariables(VVAccess.ReadWrite)]
     public int Size = 100;
 
     /// <summary>
     /// Used for client rendering
     /// </summary>
-    public int? OriginalDrawDepth { get; set; }
+    [ViewVariables]
+    public int? OriginalDrawDepth;
 }
 
 [Serializable, NetSerializable]
 public sealed class BuckleComponentState : ComponentState
 {
-    public BuckleComponentState(bool buckled, EntityUid? lastEntityBuckledTo, bool dontCollide)
+    public BuckleComponentState(bool buckled, EntityUid? buckledTo, EntityUid? lastEntityBuckledTo,
+        bool dontCollide)
     {
         Buckled = buckled;
+        BuckledTo = buckledTo;
         LastEntityBuckledTo = lastEntityBuckledTo;
         DontCollide = dontCollide;
     }
 
-    public bool Buckled { get; }
-    public EntityUid? LastEntityBuckledTo { get; }
-    public bool DontCollide { get; }
+    public readonly bool Buckled;
+    public readonly EntityUid? BuckledTo;
+    public  readonly EntityUid? LastEntityBuckledTo;
+    public readonly bool DontCollide;
 }
 
-public sealed class BuckleChangeEvent : EntityEventArgs
-{
-    public EntityUid Strap;
+[ByRefEvent]
+public readonly record struct BuckleAttemptEvent(EntityUid StrapEntity, EntityUid BuckledEntity, bool Buckling, bool Cancelled = false);
 
-    public EntityUid BuckledEntity;
-    public bool Buckling;
-}
+[ByRefEvent]
+public readonly record struct BuckleChangeEvent(EntityUid StrapEntity, EntityUid BuckledEntity, bool Buckling);
 
 [Serializable, NetSerializable]
 public enum BuckleVisuals
index 6d070a10af34753b4385a5bf6e875de055208f3f..5d19cb5883004e7efb7817bce1d2e7891dfea277 100644 (file)
@@ -1,52 +1,55 @@
 using Content.Shared.Alert;
+using Content.Shared.Vehicle;
+using Content.Shared.Whitelist;
 using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.Buckle.Components;
 
-public enum StrapPosition
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedBuckleSystem), typeof(SharedVehicleSystem))]
+public sealed class StrapComponent : Component
 {
     /// <summary>
-    /// (Default) Makes no change to the buckled mob
+    /// The entities that are currently buckled
     /// </summary>
-    None = 0,
+    [ViewVariables]
+    public readonly HashSet<EntityUid> BuckledEntities = new();
 
     /// <summary>
-    /// Makes the mob stand up
+    /// Entities that this strap accepts and can buckle
+    /// If null it accepts any entity
     /// </summary>
-    Stand,
+    [DataField("allowedEntities")]
+    [ViewVariables]
+    public EntityWhitelist? AllowedEntities;
 
-    /// <summary>
-    /// Makes the mob lie down
-    /// </summary>
-    Down
-}
-
-[RegisterComponent, NetworkedComponent]
-public sealed class StrapComponent : Component
-{
     /// <summary>
     /// The change in position to the strapped mob
     /// </summary>
     [DataField("position")]
-    public StrapPosition Position { get; set; } = StrapPosition.None;
-
-    /// <summary>
-    /// The entity that is currently buckled here
-    /// </summary>
-    public readonly HashSet<EntityUid> BuckledEntities = new();
+    [ViewVariables(VVAccess.ReadWrite)]
+    public StrapPosition Position = StrapPosition.None;
 
     /// <summary>
     /// The distance above which a buckled entity will be automatically unbuckled.
     /// Don't change it unless you really have to
     /// </summary>
+    /// <remarks>
+    /// Dont set this below 0.2 because that causes audio issues with <see cref="SharedBuckleSystem.OnBuckleMove"/>
+    /// My guess after testing is that the client sets BuckledTo to the strap in *some* ticks for some reason
+    /// whereas the server doesnt, thus the client tries to unbuckle like 15 times because it passes the strap null check
+    /// This is why this needs to be above 0.1 to make the InRange check fail in both client and server.
+    /// </remarks>
     [DataField("maxBuckleDistance", required: false)]
-    public float MaxBuckleDistance = 0.1f;
+    [ViewVariables(VVAccess.ReadWrite)]
+    public float MaxBuckleDistance = 0.2f;
 
     /// <summary>
     /// Gets and clamps the buckle offset to MaxBuckleDistance
     /// </summary>
+    [ViewVariables]
     public Vector2 BuckleOffset => Vector2.Clamp(
         BuckleOffsetUnclamped,
         Vector2.One * -MaxBuckleDistance,
@@ -57,77 +60,102 @@ public sealed class StrapComponent : Component
     /// If this offset it too big, it will be clamped to <see cref="MaxBuckleDistance"/>
     /// </summary>
     [DataField("buckleOffset", required: false)]
-    [Access(Other = AccessPermissions.ReadWrite)]
+    [ViewVariables(VVAccess.ReadWrite)]
     public Vector2 BuckleOffsetUnclamped = Vector2.Zero;
 
-
     /// <summary>
     /// The angle in degrees to rotate the player by when they get strapped
     /// </summary>
     [DataField("rotation")]
-    public int Rotation { get; set; }
+    [ViewVariables(VVAccess.ReadWrite)]
+    public int Rotation;
 
     /// <summary>
     /// The size of the strap which is compared against when buckling entities
     /// </summary>
     [DataField("size")]
-    public int Size { get; set; } = 100;
+    [ViewVariables(VVAccess.ReadWrite)]
+    public int Size = 100;
 
     /// <summary>
     /// If disabled, nothing can be buckled on this object, and it will unbuckle anything that's already buckled
     /// </summary>
-    public bool Enabled { get; set; } = true;
+    [ViewVariables]
+    public bool Enabled = true;
 
     /// <summary>
     /// You can specify the offset the entity will have after unbuckling.
     /// </summary>
     [DataField("unbuckleOffset", required: false)]
+    [ViewVariables(VVAccess.ReadWrite)]
     public Vector2 UnbuckleOffset = Vector2.Zero;
+
     /// <summary>
     /// The sound to be played when a mob is buckled
     /// </summary>
     [DataField("buckleSound")]
-    public SoundSpecifier BuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/buckle.ogg");
+    [ViewVariables(VVAccess.ReadWrite)]
+    public SoundSpecifier BuckleSound  = new SoundPathSpecifier("/Audio/Effects/buckle.ogg");
 
     /// <summary>
     /// The sound to be played when a mob is unbuckled
     /// </summary>
     [DataField("unbuckleSound")]
-    public SoundSpecifier UnbuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg");
+    [ViewVariables(VVAccess.ReadWrite)]
+    public SoundSpecifier UnbuckleSound = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg");
 
     /// <summary>
     /// ID of the alert to show when buckled
     /// </summary>
     [DataField("buckledAlertType")]
-    public AlertType BuckledAlertType { get; } = AlertType.Buckled;
+    [ViewVariables(VVAccess.ReadWrite)]
+    public AlertType BuckledAlertType = AlertType.Buckled;
 
     /// <summary>
     /// The sum of the sizes of all the buckled entities in this strap
     /// </summary>
-    public int OccupiedSize { get; set; }
+    [ViewVariables]
+    public int OccupiedSize;
 }
 
 [Serializable, NetSerializable]
 public sealed class StrapComponentState : ComponentState
 {
-    /// <summary>
-    /// The change in position that this strap makes to the strapped mob
-    /// </summary>
-    public StrapPosition Position;
-
-    public float MaxBuckleDistance;
-    public Vector2 BuckleOffsetClamped;
-    public HashSet<EntityUid> BuckledEntities;
-
-    public StrapComponentState(StrapPosition position, Vector2 offset, HashSet<EntityUid> buckled, float maxBuckleDistance)
+    public readonly StrapPosition Position;
+    public readonly float MaxBuckleDistance;
+    public readonly Vector2 BuckleOffsetClamped;
+    public readonly HashSet<EntityUid> BuckledEntities;
+    public readonly int OccupiedSize;
+
+    public StrapComponentState(StrapPosition position, Vector2 offset, HashSet<EntityUid> buckled,
+        float maxBuckleDistance, int occupiedSize)
     {
         Position = position;
         BuckleOffsetClamped = offset;
         BuckledEntities = buckled;
         MaxBuckleDistance = maxBuckleDistance;
+        OccupiedSize = occupiedSize;
     }
 }
 
+public enum StrapPosition
+{
+    /// <summary>
+    /// (Default) Makes no change to the buckled mob
+    /// </summary>
+    None = 0,
+
+    /// <summary>
+    /// Makes the mob stand up
+    /// </summary>
+    Stand,
+
+    /// <summary>
+    /// Makes the mob lie down
+    /// </summary>
+    Down
+}
+
 [Serializable, NetSerializable]
 public enum StrapVisuals : byte
 {
index 972db5b7c97b3e95ec0e3e2c73ec26aea9a12fb7..1716e03818e51a6047cffaac99e3da38cc09281e 100644 (file)
@@ -1,11 +1,25 @@
-using Content.Shared.Buckle.Components;
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Alert;
+using Content.Shared.Bed.Sleep;
+using Content.Shared.Buckle.Components;
+using Content.Shared.Database;
+using Content.Shared.Hands.Components;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
+using Content.Shared.Mobs.Components;
 using Content.Shared.Movement.Events;
+using Content.Shared.Popups;
+using Content.Shared.Pulling.Components;
 using Content.Shared.Standing;
+using Content.Shared.Storage.Components;
+using Content.Shared.Stunnable;
 using Content.Shared.Throwing;
 using Content.Shared.Vehicle.Components;
-using Robust.Shared.Map;
+using Content.Shared.Verbs;
+using Robust.Shared.GameStates;
 using Robust.Shared.Physics.Events;
+using Robust.Shared.Utility;
 
 namespace Content.Shared.Buckle;
 
@@ -13,48 +27,125 @@ public abstract partial class SharedBuckleSystem
 {
     private void InitializeBuckle()
     {
-        SubscribeLocalEvent<BuckleComponent, PreventCollideEvent>(PreventCollision);
-        SubscribeLocalEvent<BuckleComponent, DownAttemptEvent>(HandleDown);
-        SubscribeLocalEvent<BuckleComponent, StandAttemptEvent>(HandleStand);
-        SubscribeLocalEvent<BuckleComponent, ThrowPushbackAttemptEvent>(HandleThrowPushback);
-        SubscribeLocalEvent<BuckleComponent, UpdateCanMoveEvent>(HandleMove);
+        SubscribeLocalEvent<BuckleComponent, ComponentStartup>(OnBuckleComponentStartup);
+        SubscribeLocalEvent<BuckleComponent, ComponentShutdown>(OnBuckleComponentShutdown);
+        SubscribeLocalEvent<BuckleComponent, ComponentGetState>(OnBuckleComponentGetState);
+        SubscribeLocalEvent<BuckleComponent, MoveEvent>(OnBuckleMove);
+        SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(OnBuckleInteractHand);
+        SubscribeLocalEvent<BuckleComponent, GetVerbsEvent<InteractionVerb>>(AddUnbuckleVerb);
+        SubscribeLocalEvent<BuckleComponent, InsertIntoEntityStorageAttemptEvent>(OnBuckleInsertIntoEntityStorageAttempt);
+
+        SubscribeLocalEvent<BuckleComponent, PreventCollideEvent>(OnBucklePreventCollide);
+        SubscribeLocalEvent<BuckleComponent, DownAttemptEvent>(OnBuckleDownAttempt);
+        SubscribeLocalEvent<BuckleComponent, StandAttemptEvent>(OnBuckleStandAttempt);
+        SubscribeLocalEvent<BuckleComponent, ThrowPushbackAttemptEvent>(OnBuckleThrowPushbackAttempt);
+        SubscribeLocalEvent<BuckleComponent, UpdateCanMoveEvent>(OnBuckleUpdateCanMove);
         SubscribeLocalEvent<BuckleComponent, ChangeDirectionAttemptEvent>(OnBuckleChangeDirectionAttempt);
     }
 
-    private void PreventCollision(EntityUid uid, BuckleComponent component, ref PreventCollideEvent args)
+    private void OnBuckleComponentStartup(EntityUid uid, BuckleComponent component, ComponentStartup args)
+    {
+        UpdateBuckleStatus(uid, component);
+    }
+
+    private void OnBuckleComponentShutdown(EntityUid uid, BuckleComponent component, ComponentShutdown args)
+    {
+        TryUnbuckle(uid, uid, true, component);
+
+        component.BuckleTime = default;
+    }
+
+    private void OnBuckleComponentGetState(EntityUid uid, BuckleComponent component, ref ComponentGetState args)
+    {
+        args.State = new BuckleComponentState(component.Buckled, component.BuckledTo, component.LastEntityBuckledTo, component.DontCollide);
+    }
+
+    private void OnBuckleMove(EntityUid uid, BuckleComponent component, ref MoveEvent ev)
+    {
+        if (component.BuckledTo is not {} strapUid)
+            return;
+
+        if (!TryComp<StrapComponent>(strapUid, out var strapComp))
+            return;
+
+        var strapPosition = Transform(strapUid).Coordinates;
+        if (ev.NewPosition.InRange(EntityManager, _transformSystem, strapPosition, strapComp.MaxBuckleDistance))
+            return;
+
+        TryUnbuckle(uid, uid, true, component);
+    }
+
+    private void OnBuckleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args)
+    {
+        if (!component.Buckled)
+            return;
+
+        if (TryUnbuckle(uid, args.User, buckleComp: component))
+            args.Handled = true;
+    }
+
+    private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetVerbsEvent<InteractionVerb> args)
+    {
+        if (!args.CanAccess || !args.CanInteract || !component.Buckled)
+            return;
+
+        InteractionVerb verb = new()
+        {
+            Act = () => TryUnbuckle(uid, args.User, buckleComp: component),
+            Text = Loc.GetString("verb-categories-unbuckle"),
+            Icon = new SpriteSpecifier.Texture(new ("/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);
+    }
+
+    private void OnBuckleInsertIntoEntityStorageAttempt(EntityUid uid, BuckleComponent component, ref InsertIntoEntityStorageAttemptEvent args)
+    {
+        if (component.Buckled)
+            args.Cancelled = true;
+    }
+
+    private void OnBucklePreventCollide(EntityUid uid, BuckleComponent component, ref PreventCollideEvent args)
     {
-        if (args.BodyB.Owner != component.LastEntityBuckledTo)
+        if (args.BodyB.Owner != component.BuckledTo)
             return;
 
         if (component.Buckled || component.DontCollide)
             args.Cancelled = true;
     }
 
-    private void HandleDown(EntityUid uid, BuckleComponent component, DownAttemptEvent args)
+    private void OnBuckleDownAttempt(EntityUid uid, BuckleComponent component, DownAttemptEvent args)
     {
         if (component.Buckled)
             args.Cancel();
     }
 
-    private void HandleStand(EntityUid uid, BuckleComponent component, StandAttemptEvent args)
+    private void OnBuckleStandAttempt(EntityUid uid, BuckleComponent component, StandAttemptEvent args)
     {
         if (component.Buckled)
             args.Cancel();
     }
 
-    private void HandleThrowPushback(EntityUid uid, BuckleComponent component, ThrowPushbackAttemptEvent args)
+    private void OnBuckleThrowPushbackAttempt(EntityUid uid, BuckleComponent component, ThrowPushbackAttemptEvent args)
     {
         if (component.Buckled)
             args.Cancel();
     }
 
-    private void HandleMove(EntityUid uid, BuckleComponent component, UpdateCanMoveEvent args)
+    private void OnBuckleUpdateCanMove(EntityUid uid, BuckleComponent component, UpdateCanMoveEvent args)
     {
         if (component.LifeStage > ComponentLifeStage.Running)
             return;
 
         if (component.Buckled &&
-            !HasComp<VehicleComponent>(Transform(uid).ParentUid)) // buckle+vehicle shitcode
+            !HasComp<VehicleComponent>(component.BuckledTo)) // buckle+vehicle shitcode
             args.Cancel();
     }
 
@@ -70,38 +161,369 @@ public abstract partial class SharedBuckleSystem
     }
 
     /// <summary>
-    ///     Reattaches this entity to the strap, modifying its position and rotation.
+    /// Shows or hides the buckled status effect depending on if the
+    /// entity is buckled or not.
     /// </summary>
-    /// <param name="buckleId">The entity to reattach.</param>
-    /// <param name="strap">The strap to reattach to.</param>
-    /// <param name="buckle">The buckle component of the entity to reattach.</param>
-    public void ReAttach(EntityUid buckleId, StrapComponent strap, BuckleComponent? buckle = null)
+    /// <param name="uid"> Entity that we want to show the alert </param>
+    /// <param name="buckleComp"> buckle component of the entity </param>
+    /// <param name="strapComp"> strap component of the thing we are strapping to </param>
+    private void UpdateBuckleStatus(EntityUid uid, BuckleComponent buckleComp, StrapComponent? strapComp = null)
     {
-        if (!Resolve(buckleId, ref buckle, false))
-            return;
+        if (buckleComp.BuckledTo != null)
+        {
+            if (!Resolve(buckleComp.BuckledTo.Value, ref strapComp))
+                return;
 
-        var ownTransform = Transform(buckleId);
-        var strapTransform = Transform(strap.Owner);
+            var alertType = strapComp.BuckledAlertType;
+            _alertsSystem.ShowAlert(uid, alertType);
+        }
+        else
+        {
+            _alertsSystem.ClearAlertCategory(uid, AlertCategory.Buckled);
+        }
+    }
 
-        ownTransform.Coordinates = new EntityCoordinates(strapTransform.Owner, strap.BuckleOffset);
+    /// <summary>
+    /// Sets the <see cref="BuckleComponent.BuckledTo"/> field in the component to a value
+    /// </summary>
+    /// <param name="strapUid"> Value tat with be assigned to the field </param>
+    private void SetBuckledTo(EntityUid buckleUid, EntityUid? strapUid, StrapComponent? strapComp, BuckleComponent buckleComp)
+    {
+        buckleComp.BuckledTo = strapUid;
 
-        // Buckle subscribes to move for <reasons> so this might fail.
-        // TODO: Make buckle not do that.
-        if (ownTransform.ParentUid != strapTransform.Owner)
-            return;
+        if (strapUid == null)
+        {
+            buckleComp.Buckled = false;
+        }
+        else
+        {
+            buckleComp.LastEntityBuckledTo = strapUid;
+            buckleComp.DontCollide = true;
+            buckleComp.Buckled = true;
+            buckleComp.BuckleTime = _gameTiming.CurTime;
+        }
 
-        ownTransform.LocalRotation = Angle.Zero;
+        ActionBlockerSystem.UpdateCanMove(buckleUid);
+        UpdateBuckleStatus(buckleUid, buckleComp, strapComp);
+        Dirty(buckleComp);
+    }
 
-        switch (strap.Position)
+    /// <summary>
+    /// Checks whether or not buckling is possible
+    /// </summary>
+    /// <param name="buckleUid"> Uid of the owner of BuckleComponent </param>
+    /// <param name="userUid">
+    /// Uid of a third party entity,
+    /// i.e, the uid of someone else you are dragging to a chair.
+    /// Can equal buckleUid sometimes
+    /// </param>
+    /// <param name="strapUid"> Uid of the owner of strap component </param>
+    private bool CanBuckle(
+        EntityUid buckleUid,
+        EntityUid userUid,
+        EntityUid strapUid,
+        [NotNullWhen(true)] out StrapComponent? strapComp,
+        BuckleComponent? buckleComp = null)
+    {
+        strapComp = null;
+
+        if (userUid == strapUid ||
+            !Resolve(buckleUid, ref buckleComp, false) ||
+            !Resolve(strapUid, ref strapComp, false))
+        {
+            return false;
+        }
+
+        // Does it pass the Whitelist
+        if (strapComp.AllowedEntities != null &&
+            !strapComp.AllowedEntities.IsValid(userUid, EntityManager))
+        {
+            if (_netManager.IsServer)
+                _popupSystem.PopupEntity(Loc.GetString("buckle-component-cannot-fit-message"), userUid, buckleUid, PopupType.Medium);
+            return false;
+        }
+
+        // Is it within range
+        bool Ignored(EntityUid entity) => entity == buckleUid || entity == userUid || entity == strapUid;
+
+        if (!_interactionSystem.InRangeUnobstructed(buckleUid, strapUid, buckleComp.Range, predicate: Ignored,
+                popup: true))
         {
-            case StrapPosition.None:
-                break;
-            case StrapPosition.Stand:
-                _standing.Stand(buckleId);
-                break;
-            case StrapPosition.Down:
-                _standing.Down(buckleId, false, false);
-                break;
+            return false;
         }
+
+        // If in a container
+        if (_containerSystem.TryGetContainingContainer(buckleUid, out var ownerContainer))
+        {
+            // And not in the same container as the strap
+            if (!_containerSystem.TryGetContainingContainer(strapUid, out var strapContainer) ||
+                ownerContainer != strapContainer)
+            {
+                return false;
+            }
+        }
+
+        if (!HasComp<HandsComponent>(userUid))
+        {
+            // PopupPredicted when
+            if (_netManager.IsServer)
+                _popupSystem.PopupEntity(Loc.GetString("buckle-component-no-hands-message"), userUid, userUid);
+            return false;
+        }
+
+        if (buckleComp.Buckled)
+        {
+            var message = Loc.GetString(buckleUid == userUid
+                    ? "buckle-component-already-buckled-message"
+                    : "buckle-component-other-already-buckled-message",
+                ("owner", Identity.Entity(buckleUid, EntityManager)));
+            if (_netManager.IsServer)
+                _popupSystem.PopupEntity(message, userUid, userUid);
+
+            return false;
+        }
+
+        var parent = Transform(strapUid).ParentUid;
+        while (parent.IsValid())
+        {
+            if (parent == userUid)
+            {
+                var message = Loc.GetString(buckleUid == userUid
+                    ? "buckle-component-cannot-buckle-message"
+                    : "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
+                if (_netManager.IsServer)
+                    _popupSystem.PopupEntity(message, userUid, userUid);
+
+                return false;
+            }
+
+            parent = Transform(parent).ParentUid;
+        }
+
+        if (!StrapHasSpace(strapUid, buckleComp, strapComp))
+        {
+            var message = Loc.GetString(buckleUid == userUid
+                ? "buckle-component-cannot-fit-message"
+                : "buckle-component-other-cannot-fit-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
+            if (_netManager.IsServer)
+                _popupSystem.PopupEntity(message, userUid, userUid);
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Attempts to buckle an entity to a strap
+    /// </summary>
+    /// <param name="buckleUid"> Uid of the owner of BuckleComponent </param>
+    /// <param name="userUid">
+    /// Uid of a third party entity,
+    /// i.e, the uid of someone else you are dragging to a chair.
+    /// Can equal buckleUid sometimes
+    /// </param>
+    /// <param name="strapUid"> Uid of the owner of strap component </param>
+    public bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUid, BuckleComponent? buckleComp = null)
+    {
+       if (!Resolve(buckleUid, ref buckleComp, false))
+            return false;
+
+       if (!CanBuckle(buckleUid, userUid, strapUid, out var strapComp, buckleComp))
+           return false;
+
+       var attemptEvent = new BuckleAttemptEvent(strapUid, buckleUid, true);
+       RaiseLocalEvent(attemptEvent.BuckledEntity, ref attemptEvent);
+       RaiseLocalEvent(attemptEvent.StrapEntity, ref attemptEvent);
+       if (attemptEvent.Cancelled)
+           return false;
+
+       if (!StrapTryAdd(strapUid, buckleUid, buckleComp, false, strapComp))
+       {
+           var message = Loc.GetString(buckleUid == userUid
+               ? "buckle-component-cannot-buckle-message"
+               : "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
+            if (_netManager.IsServer)
+               _popupSystem.PopupEntity(message, userUid, userUid);
+           return false;
+       }
+
+       if (TryComp<AppearanceComponent>(buckleUid, out var appearance))
+           AppearanceSystem.SetData(buckleUid, BuckleVisuals.Buckled, true, appearance);
+
+       ReAttach(buckleUid, strapUid, buckleComp, strapComp);
+       SetBuckledTo(buckleUid,strapUid, strapComp, buckleComp);
+       _audioSystem.PlayPredicted(strapComp.BuckleSound, strapUid, buckleUid);
+
+       var ev = new BuckleChangeEvent(strapUid, buckleUid, true);
+       RaiseLocalEvent(ev.BuckledEntity, ref ev);
+       RaiseLocalEvent(ev.StrapEntity, ref ev);
+
+       if (TryComp<SharedPullableComponent>(buckleUid, out var ownerPullable))
+       {
+           if (ownerPullable.Puller != null)
+           {
+               _pullingSystem.TryStopPull(ownerPullable);
+           }
+       }
+
+       if (!buckleComp.PullStrap && TryComp<SharedPullableComponent>(strapUid, out var toPullable))
+       {
+           if (toPullable.Puller == buckleUid)
+           {
+               // can't pull it and buckle to it at the same time
+               _pullingSystem.TryStopPull(toPullable);
+           }
+       }
+
+       // Logging
+       if (userUid != buckleUid)
+           _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} buckled {ToPrettyString(buckleUid)} to {ToPrettyString(strapUid)}");
+       else
+           _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} buckled themselves to {ToPrettyString(strapUid)}");
+
+       return true;
+    }
+
+    /// <summary>
+    /// Tries to unbuckle the Owner of this component from its current strap.
+    /// </summary>
+    /// <param name="buckleUid">The entity to unbuckle.</param>
+    /// <param name="userUid">The entity doing the unbuckling.</param>
+    /// <param name="force">
+    /// Whether to force the unbuckling or not. Does not guarantee true to
+    /// be returned, but guarantees the owner to be unbuckled afterwards.
+    /// </param>
+    /// <param name="buckleComp">The buckle component of the entity to unbuckle.</param>
+    /// <returns>
+    ///     true if the owner was unbuckled, otherwise false even if the owner
+    ///     was previously already unbuckled.
+    /// </returns>
+    public bool TryUnbuckle(EntityUid buckleUid, EntityUid userUid, bool force = false, BuckleComponent? buckleComp = null)
+    {
+        if (!Resolve(buckleUid, ref buckleComp, false) ||
+            buckleComp.BuckledTo is not { } strapUid)
+            return false;
+
+        var attemptEvent = new BuckleAttemptEvent(strapUid, buckleUid, false);
+        RaiseLocalEvent(attemptEvent.BuckledEntity, ref attemptEvent);
+        RaiseLocalEvent(attemptEvent.StrapEntity, ref attemptEvent);
+        if (attemptEvent.Cancelled)
+            return false;
+
+        Logger.Debug($"{force}");
+        if (!force)
+        {
+            if (_gameTiming.CurTime < buckleComp.BuckleTime + buckleComp.UnbuckleDelay)
+                return false;
+
+            if (!_interactionSystem.InRangeUnobstructed(userUid, strapUid, buckleComp.Range, popup: true))
+                return false;
+
+            if (HasComp<SleepingComponent>(buckleUid) && buckleUid == userUid)
+                return false;
+
+            // If the strap is a vehicle and the rider is not the person unbuckling, return.
+            if (TryComp<VehicleComponent>(strapUid, out var vehicle) &&
+                vehicle.Rider != userUid)
+                return false;
+        }
+
+        // Logging
+        if (userUid != buckleUid)
+            _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} unbuckled {ToPrettyString(buckleUid)} from {ToPrettyString(strapUid)}");
+        else
+            _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} unbuckled themselves from {ToPrettyString(strapUid)}");
+
+        SetBuckledTo(buckleUid, null, null, buckleComp);
+
+        if (!TryComp<StrapComponent>(strapUid, out var strapComp))
+            return false;
+
+        var buckleXform = Transform(buckleUid);
+        var oldBuckledXform = Transform(strapUid);
+
+        if (buckleXform.ParentUid == strapUid && !Terminating(buckleXform.ParentUid))
+        {
+            _containerSystem.AttachParentToContainerOrGrid(buckleXform);
+
+            var oldBuckledToWorldRot = _transformSystem.GetWorldRotation(strapUid);
+            _transformSystem.SetWorldRotation(buckleXform, oldBuckledToWorldRot);
+
+            if (strapComp.UnbuckleOffset != Vector2.Zero)
+                buckleXform.Coordinates = oldBuckledXform.Coordinates.Offset(strapComp.UnbuckleOffset);
+        }
+
+        if (TryComp(buckleUid, out AppearanceComponent? appearance))
+            AppearanceSystem.SetData(buckleUid, BuckleVisuals.Buckled, false, appearance);
+
+        if (TryComp<MobStateComponent>(buckleUid, out var mobState)
+            && _mobStateSystem.IsIncapacitated(buckleUid, mobState)
+            || HasComp<KnockedDownComponent>(buckleUid))
+        {
+            _standingSystem.Down(buckleUid);
+        }
+        else
+        {
+            _standingSystem.Stand(buckleUid);
+        }
+
+        if (_mobStateSystem.IsIncapacitated(buckleUid, mobState))
+        {
+            _standingSystem.Down(buckleUid);
+        }
+        // Sync StrapComponent data
+        AppearanceSystem.SetData(strapUid, StrapVisuals.State, false);
+        if (strapComp.BuckledEntities.Remove(buckleUid))
+        {
+            strapComp.OccupiedSize -= buckleComp.Size;
+            //Dirty(strapUid);
+            Dirty(strapComp);
+        }
+
+        _audioSystem.PlayPredicted(strapComp.UnbuckleSound, strapUid, buckleUid);
+
+        var ev = new BuckleChangeEvent(strapUid, buckleUid, false);
+        RaiseLocalEvent(buckleUid, ref ev);
+        RaiseLocalEvent(strapUid, ref ev);
+
+        return true;
+    }
+
+    /// <summary>
+    /// Makes an entity toggle the buckling status of the owner to a
+    /// specific entity.
+    /// </summary>
+    /// <param name="buckleUid">The entity to buckle/unbuckle from <see cref="to"/>.</param>
+    /// <param name="userUid">The entity doing the buckling/unbuckling.</param>
+    /// <param name="strapUid">
+    /// The entity to toggle the buckle status of the owner to.
+    /// </param>
+    /// <param name="force">
+    /// 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.
+    /// </param>
+    /// <param name="buckle">The buckle component of the entity to buckle/unbuckle from <see cref="to"/>.</param>
+    /// <returns>true if the buckling status was changed, false otherwise.</returns>
+    public bool ToggleBuckle(
+        EntityUid buckleUid,
+        EntityUid userUid,
+        EntityUid strapUid,
+        bool force = false,
+        BuckleComponent? buckle = null)
+    {
+        if (!Resolve(buckleUid, ref buckle, false))
+            return false;
+
+        if (!buckle.Buckled)
+        {
+            return TryBuckle(buckleUid, userUid, strapUid, buckle);
+        }
+        else
+        {
+            return TryUnbuckle(buckleUid, userUid, force, buckle);
+        }
+
     }
 }
index 9e9ffa15838a7649a5bf4ab1a6c6b63aef894949..a87f9fea149f6760b8d14f7e22c268cbecf4e592 100644 (file)
@@ -1,19 +1,50 @@
-using Content.Shared.Buckle.Components;
+using System.Linq;
+using Content.Shared.Buckle.Components;
+using Content.Shared.Destructible;
 using Content.Shared.DragDrop;
 using Content.Shared.Interaction;
+using Content.Shared.Storage;
+using Content.Shared.Verbs;
+using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
 
 namespace Content.Shared.Buckle;
 
 public abstract partial class SharedBuckleSystem
 {
-    [Dependency] private readonly SharedInteractionSystem _interactions = default!;
-
     private void InitializeStrap()
     {
-        SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapRotate);
+        SubscribeLocalEvent<StrapComponent, ComponentShutdown>(OnStrapShutdown);
+        SubscribeLocalEvent<StrapComponent, ComponentRemove>((_, c, _) => StrapRemoveAll(c));
+
+        SubscribeLocalEvent<StrapComponent, ComponentGetState>(OnStrapGetState);
         SubscribeLocalEvent<StrapComponent, ComponentHandleState>(OnStrapHandleState);
-        SubscribeLocalEvent<StrapComponent, CanDropTargetEvent>(OnStrapCanDropOn);
+
+        SubscribeLocalEvent<StrapComponent, EntInsertedIntoContainerMessage>(OnStrapEntModifiedFromContainer);
+        SubscribeLocalEvent<StrapComponent, EntRemovedFromContainerMessage>(OnStrapEntModifiedFromContainer);
+        SubscribeLocalEvent<StrapComponent, GetVerbsEvent<InteractionVerb>>(AddStrapVerbs);
+        SubscribeLocalEvent<StrapComponent, ContainerGettingInsertedAttemptEvent>(OnStrapContainerGettingInsertedAttempt);
+        SubscribeLocalEvent<StrapComponent, InteractHandEvent>(OnStrapInteractHand);
+        SubscribeLocalEvent<StrapComponent, DestructionEventArgs>((_,c,_) => StrapRemoveAll(c));
+        SubscribeLocalEvent<StrapComponent, BreakageEventArgs>((_, c, _) => StrapRemoveAll(c));
+
+        SubscribeLocalEvent<StrapComponent, DragDropTargetEvent>(OnStrapDragDropTarget);
+        SubscribeLocalEvent<StrapComponent, CanDropTargetEvent>(OnCanDropTarget);
+
+        SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
+    }
+
+    private void OnStrapShutdown(EntityUid uid, StrapComponent component, ComponentShutdown args)
+    {
+        if (LifeStage(uid) > EntityLifeStage.MapInitialized)
+            return;
+
+        StrapRemoveAll(component);
+    }
+
+    private void OnStrapGetState(EntityUid uid, StrapComponent component, ref ComponentGetState args)
+    {
+        args.State = new StrapComponentState(component.Position, component.BuckleOffset, component.BuckledEntities, component.MaxBuckleDistance, component.OccupiedSize);
     }
 
     private void OnStrapHandleState(EntityUid uid, StrapComponent component, ref ComponentHandleState args)
@@ -26,9 +57,151 @@ public abstract partial class SharedBuckleSystem
         component.BuckledEntities.Clear();
         component.BuckledEntities.UnionWith(state.BuckledEntities);
         component.MaxBuckleDistance = state.MaxBuckleDistance;
+        component.OccupiedSize = state.OccupiedSize;
+    }
+
+    private void OnStrapEntModifiedFromContainer(EntityUid uid, StrapComponent component, ContainerModifiedMessage message)
+    {
+        if (_gameTiming.ApplyingState)
+            return;
+
+        foreach (var buckledEntity in component.BuckledEntities)
+        {
+            if (!TryComp<BuckleComponent>(buckledEntity, out var buckleComp))
+            {
+                continue;
+            }
+
+            ContainerModifiedReAttach(buckledEntity, uid, buckleComp, component);
+        }
+    }
+
+    private void ContainerModifiedReAttach(EntityUid buckleUid, EntityUid strapUid, BuckleComponent? buckleComp = null, StrapComponent? strapComp = null)
+    {
+        if (!Resolve(buckleUid, ref buckleComp, false) ||
+            !Resolve(strapUid, ref strapComp, false))
+            return;
+
+        var contained = _containerSystem.TryGetContainingContainer(buckleUid, out var ownContainer);
+        var strapContained = _containerSystem.TryGetContainingContainer(strapUid, out var strapContainer);
+
+        if (contained != strapContained || ownContainer != strapContainer)
+        {
+            TryUnbuckle(buckleUid, buckleUid, true, buckleComp);
+            return;
+        }
+
+        if (!contained)
+        {
+            ReAttach(buckleUid, strapUid, buckleComp, strapComp);
+        }
     }
 
-    private void OnStrapRotate(EntityUid uid, StrapComponent component, ref MoveEvent args)
+    private void OnStrapContainerGettingInsertedAttempt(EntityUid uid, StrapComponent component, ContainerGettingInsertedAttemptEvent args)
+    {
+        // If someone is attempting to put this item inside of a backpack, ensure that it has no entities strapped to it.
+        if (HasComp<SharedStorageComponent>(args.Container.Owner) && component.BuckledEntities.Count != 0)
+            args.Cancel();
+    }
+
+    private void OnStrapInteractHand(EntityUid uid, StrapComponent component, InteractHandEvent args)
+    {
+        if (args.Handled)
+            return;
+
+        ToggleBuckle(args.User, args.User, uid);
+    }
+
+    private void AddStrapVerbs(EntityUid uid, StrapComponent component, GetVerbsEvent<InteractionVerb> args)
+    {
+        if (args.Hands == null || !args.CanAccess || !args.CanInteract || !component.Enabled)
+            return;
+
+        // Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this
+        // range can be set per-component, so we have to check a modified InRangeUnobstructed for every verb.
+
+        // Add unstrap verbs for every strapped entity.
+        foreach (var entity in component.BuckledEntities)
+        {
+            var buckledComp = Comp<BuckleComponent>(entity);
+
+            if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range))
+                continue;
+
+            var verb = new InteractionVerb()
+            {
+                Act = () => TryUnbuckle(entity, args.User, buckleComp: buckledComp),
+                Category = VerbCategory.Unbuckle,
+                Text = entity == args.User
+                    ? Loc.GetString("verb-self-target-pronoun")
+                    : Comp<MetaDataComponent>(entity).EntityName
+            };
+
+            // In the event that you have more than once entity with the same name strapped to the same object,
+            // these two verbs will be identical according to Verb.CompareTo, and only one with actually be added to
+            // the verb list. However this should rarely ever be a problem. If it ever is, it could be fixed by
+            // appending an integer to verb.Text to distinguish the verbs.
+
+            args.Verbs.Add(verb);
+        }
+
+        // Add a verb to buckle the user.
+        if (TryComp<BuckleComponent>(args.User, out var buckle) &&
+            buckle.BuckledTo != uid &&
+            args.User != uid &&
+            StrapHasSpace(uid, buckle, component) &&
+            _interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckle.Range))
+        {
+            InteractionVerb verb = new()
+            {
+                Act = () => TryBuckle(args.User, args.User, args.Target, buckle),
+                Category = VerbCategory.Buckle,
+                Text = Loc.GetString("verb-self-target-pronoun")
+            };
+            args.Verbs.Add(verb);
+        }
+
+        // If the user is currently holding/pulling an entity that can be buckled, add a verb for that.
+        if (args.Using is {Valid: true} @using &&
+            TryComp<BuckleComponent>(@using, out var usingBuckle) &&
+            StrapHasSpace(uid, usingBuckle, component) &&
+            _interactionSystem.InRangeUnobstructed(@using, args.Target, range: usingBuckle.Range))
+        {
+            // Check that the entity is unobstructed from the target (ignoring the user).
+            bool Ignored(EntityUid entity) => entity == args.User || entity == args.Target || entity == @using;
+            if (!_interactionSystem.InRangeUnobstructed(@using, args.Target, usingBuckle.Range, predicate: Ignored))
+                return;
+
+            var isPlayer = _playerManager.TryGetSessionByEntity(@using, out var _);
+            InteractionVerb verb = new()
+            {
+                Act = () => TryBuckle(@using, args.User, args.Target, usingBuckle),
+                Category = VerbCategory.Buckle,
+                Text = Comp<MetaDataComponent>(@using).EntityName,
+                // just a held object, the user is probably just trying to sit down.
+                // If the used entity is a person being pulled, prioritize this verb. Conversely, if it is
+                Priority = isPlayer ? 1 : -1
+            };
+
+            args.Verbs.Add(verb);
+        }
+    }
+
+    private void OnCanDropTarget(EntityUid uid, StrapComponent component, ref CanDropTargetEvent args)
+    {
+        args.CanDrop = StrapCanDragDropOn(uid, args.User, uid, args.Dragged, component);
+        args.Handled = true;
+    }
+
+    private void OnStrapDragDropTarget(EntityUid uid, StrapComponent component, ref DragDropTargetEvent args)
+    {
+        if (!StrapCanDragDropOn(uid, args.User, uid, args.Dragged, component))
+            return;
+
+        args.Handled = TryBuckle(args.Dragged, args.User, uid);
+    }
+
+    private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)
     {
         // TODO: This looks dirty af.
         // On rotation of a strap, reattach all buckled entities.
@@ -45,15 +218,13 @@ public abstract partial class SharedBuckleSystem
         // 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)
+        if (_gameTiming.ApplyingState || args.NewRotation == args.OldRotation)
             return;
 
         foreach (var buckledEntity in component.BuckledEntities)
         {
-            if (!EntityManager.TryGetComponent(buckledEntity, out BuckleComponent? buckled))
-            {
+            if (!TryComp<BuckleComponent>(buckledEntity, out var buckled))
                 continue;
-            }
 
             if (!buckled.Buckled || buckled.LastEntityBuckledTo != uid)
             {
@@ -61,33 +232,90 @@ public abstract partial class SharedBuckleSystem
                 continue;
             }
 
-            ReAttach(buckledEntity, component, buckle: buckled);
+            ReAttach(buckledEntity, uid, buckled, component);
             Dirty(buckled);
         }
     }
 
-    protected bool StrapCanDragDropOn(
-        EntityUid strapId,
-        EntityUid user,
-        EntityUid target,
-        EntityUid buckleId,
-        StrapComponent? strap = null,
-        BuckleComponent? buckle = null)
+    private bool StrapCanDragDropOn(
+        EntityUid strapUid,
+        EntityUid userUid,
+        EntityUid targetUid,
+        EntityUid buckleUid,
+        StrapComponent? strapComp = null,
+        BuckleComponent? buckleComp = null)
     {
-        if (!Resolve(strapId, ref strap, false) ||
-            !Resolve(buckleId, ref buckle, false))
+        if (!Resolve(strapUid, ref strapComp, false) ||
+            !Resolve(buckleUid, ref buckleComp, false))
         {
             return false;
         }
 
-        bool Ignored(EntityUid entity) => entity == user || entity == buckleId || entity == target;
+        bool Ignored(EntityUid entity) => entity == userUid || entity == buckleUid || entity == targetUid;
 
-        return _interactions.InRangeUnobstructed(target, buckleId, buckle.Range, predicate: Ignored);
+        return _interactionSystem.InRangeUnobstructed(targetUid, buckleUid, buckleComp.Range, predicate: Ignored);
     }
 
-    private void OnStrapCanDropOn(EntityUid uid, StrapComponent strap, ref CanDropTargetEvent args)
+    /// <summary>
+    /// Remove everything attached to the strap
+    /// </summary>
+    private void StrapRemoveAll(StrapComponent strapComp)
     {
-        args.CanDrop = StrapCanDragDropOn(uid, args.User, uid, args.Dragged, strap);
-        args.Handled = true;
+        foreach (var entity in strapComp.BuckledEntities.ToArray())
+        {
+            TryUnbuckle(entity, entity, true);
+        }
+
+        strapComp.BuckledEntities.Clear();
+        strapComp.OccupiedSize = 0;
+        Dirty(strapComp);
+    }
+
+    private bool StrapHasSpace(EntityUid strapUid, BuckleComponent buckleComp, StrapComponent? strapComp = null)
+    {
+        if (!Resolve(strapUid, ref strapComp, false))
+            return false;
+
+        return strapComp.OccupiedSize + buckleComp.Size <= strapComp.Size;
+    }
+
+    /// <summary>
+    /// Try to add an entity to the strap
+    /// </summary>
+    private bool StrapTryAdd(EntityUid strapUid, EntityUid buckleUid, BuckleComponent buckleComp, bool force = false, StrapComponent? strapComp = null)
+    {
+        if (!Resolve(strapUid, ref strapComp, false) ||
+            !strapComp.Enabled)
+            return false;
+
+        if (!force && !StrapHasSpace(strapUid, buckleComp, strapComp))
+            return false;
+
+        if (!strapComp.BuckledEntities.Add(buckleUid))
+            return false;
+
+        strapComp.OccupiedSize += buckleComp.Size;
+
+        AppearanceSystem.SetData(buckleUid, StrapVisuals.RotationAngle, strapComp.Rotation);
+
+        AppearanceSystem.SetData(strapUid, StrapVisuals.State, true);
+
+        Dirty(strapComp);
+        return true;
+    }
+
+    /// <summary>
+    /// Sets the enabled field in the strap component to a value
+    /// </summary>
+    public void StrapSetEnabled(EntityUid strapUid, bool enabled, StrapComponent? strapComp = null)
+    {
+        if (!Resolve(strapUid, ref strapComp, false) ||
+            strapComp.Enabled == enabled)
+            return;
+
+        strapComp.Enabled = enabled;
+
+        if (!enabled)
+            StrapRemoveAll(strapComp);
     }
 }
index 9ad83acbfa77bc27a29d8002c33e9f4a8460e498..49b86cb9688c2082badef419555a56fd1edeb078 100644 (file)
@@ -1,19 +1,87 @@
+using Content.Shared.ActionBlocker;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Alert;
+using Content.Shared.Buckle.Components;
+using Content.Shared.Interaction;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Popups;
+using Content.Shared.Pulling;
 using Content.Shared.Standing;
+using Robust.Shared.Containers;
+using Robust.Shared.Map;
+using Robust.Shared.Network;
+using Robust.Shared.Players;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Buckle;
 
 public abstract partial class SharedBuckleSystem : EntitySystem
 {
-    [Dependency] protected readonly IGameTiming GameTiming = default!;
+    [Dependency] private readonly INetManager _netManager = default!;
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] private readonly ISharedPlayerManager _playerManager = default!;
 
-    [Dependency] private readonly StandingStateSystem _standing = default!;
+    [Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
+    [Dependency] protected readonly ActionBlockerSystem ActionBlockerSystem = default!;
+    [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
+    [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
+    [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
+    [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly SharedPullingSystem _pullingSystem = default!;
+    [Dependency] private readonly StandingStateSystem _standingSystem = default!;
+    [Dependency] private readonly AlertsSystem _alertsSystem = default!;
+    [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
 
+    /// <inheritdoc/>
     public override void Initialize()
     {
         base.Initialize();
 
+        UpdatesAfter.Add(typeof(SharedInteractionSystem));
+        UpdatesAfter.Add(typeof(SharedInputSystem));
+
         InitializeBuckle();
         InitializeStrap();
     }
+
+    /// <summary>
+    /// Reattaches this entity to the strap, modifying its position and rotation.
+    /// </summary>
+    /// <param name="buckleUid">The entity to reattach.</param>
+    /// <param name="strapUid">The entity to reattach the buckleUid entity to.</param>
+    private void ReAttach(
+        EntityUid buckleUid,
+        EntityUid strapUid,
+        BuckleComponent? buckleComp = null,
+        StrapComponent? strapComp = null)
+    {
+        if (!Resolve(strapUid, ref strapComp, false)
+            || !Resolve(buckleUid, ref buckleComp, false))
+            return;
+
+        var buckleTransform = Transform(buckleUid);
+
+        buckleTransform.Coordinates = new EntityCoordinates(strapUid, strapComp.BuckleOffset);
+
+        // Buckle subscribes to move for <reasons> so this might fail.
+        // TODO: Make buckle not do that.
+        if (buckleTransform.ParentUid != strapUid)
+            return;
+
+        _transformSystem.SetLocalRotation(buckleUid, Angle.Zero, buckleTransform);
+
+        switch (strapComp.Position)
+        {
+            case StrapPosition.None:
+                break;
+            case StrapPosition.Stand:
+                _standingSystem.Stand(buckleUid);
+                break;
+            case StrapPosition.Down:
+                _standingSystem.Down(buckleUid, false, false);
+                break;
+        }
+    }
 }
diff --git a/Content.Shared/Light/Component/UnpoweredFlashlightComponent.cs b/Content.Shared/Light/Component/UnpoweredFlashlightComponent.cs
new file mode 100644 (file)
index 0000000..f9d572e
--- /dev/null
@@ -0,0 +1,20 @@
+using Content.Shared.Actions.ActionTypes;
+using Robust.Shared.Audio;
+
+namespace Content.Shared.Light.Component;
+
+/// <summary>
+/// This is simplified version of <see cref="HandheldLightComponent"/>.
+/// It doesn't consume any power and can be toggle only by verb.
+/// </summary>
+[RegisterComponent]
+public sealed class UnpoweredFlashlightComponent : Robust.Shared.GameObjects.Component
+{
+    [DataField("toggleFlashlightSound")]
+    public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
+
+    [ViewVariables] public bool LightOn = false;
+
+    [DataField("toggleAction", required: true)]
+    public InstantAction ToggleAction = new();
+}
index 9fbd04382b1e50ed74598054ff49002c78221c26..c0893a40797b515bd56268619dbdc714aa412b82 100644 (file)
@@ -64,7 +64,7 @@ namespace Content.Shared.Pulling
             if (EntityManager.TryGetComponent<BuckleComponent?>(puller, out var buckle))
             {
                 // Prevent people pulling the chair they're on, etc.
-                if (buckle.Buckled && (buckle.LastEntityBuckledTo == pulled))
+                if (buckle is { PullStrap: false, Buckled: true } && (buckle.LastEntityBuckledTo == pulled))
                 {
                     return false;
                 }
index 3fd559e2d6cb1c946e556a8c5a3fa13d3ef2b597..76bbabe325b7c30dd145427b67f21d0a4c6f14f0 100644 (file)
@@ -1,19 +1,25 @@
 using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
 
-namespace Content.Shared.Vehicle.Components
+namespace Content.Shared.Vehicle.Components;
+
+/// <summary>
+/// Added to people when they are riding in a vehicle
+/// used mostly to keep track of them for entityquery.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed class RiderComponent : Component
 {
     /// <summary>
-    /// Added to people when they are riding in a vehicle
-    /// used mostly to keep track of them for entityquery.
+    /// The vehicle this rider is currently riding.
     /// </summary>
-    [RegisterComponent, NetworkedComponent]
-    public sealed class RiderComponent : Component
-    {
-        /// <summary>
-        /// The vehicle this rider is currently riding.
-        /// </summary>
-        [ViewVariables] public EntityUid? Vehicle;
+    [ViewVariables] public EntityUid? Vehicle;
+
+    public override bool SendOnlyToOwner => true;
+}
 
-        public override bool SendOnlyToOwner => true;
-    }
+[Serializable, NetSerializable]
+public sealed class RiderComponentState : ComponentState
+{
+    public EntityUid? Entity;
 }
index 30dfabc4bff92ceacfd5a224d39a552657d11b7c..01053f6c6532941b9b0d6360119db00fa233be41 100644 (file)
 using Content.Shared.Actions.ActionTypes;
 using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
 using Robust.Shared.Utility;
 
-namespace Content.Shared.Vehicle.Components
+namespace Content.Shared.Vehicle.Components;
+
+/// <summary>
+/// This is particularly for vehicles that use
+/// buckle. Stuff like clown cars may need a different
+/// component at some point.
+/// All vehicles should have Physics, Strap, and SharedPlayerInputMover components.
+/// </summary>
+[AutoGenerateComponentState]
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedVehicleSystem))]
+public sealed partial class VehicleComponent : Component
 {
     /// <summary>
-    /// This is particularly for vehicles that use
-    /// buckle. Stuff like clown cars may need a different
-    /// component at some point.
-    /// All vehicles should have Physics, Strap, and SharedPlayerInputMover components.
+    /// The entity currently riding the vehicle.
+    /// </summary>
+    [ViewVariables]
+    [AutoNetworkedField]
+    public EntityUid? Rider;
+
+    [ViewVariables]
+    [AutoNetworkedField]
+    public EntityUid? LastRider;
+
+    /// <summary>
+    /// The base offset for the vehicle (when facing east)
+    /// </summary>
+    [ViewVariables]
+    public Vector2 BaseBuckleOffset = Vector2.Zero;
+
+    /// <summary>
+    /// The sound that the horn makes
     /// </summary>
-    [RegisterComponent]
-    public sealed class VehicleComponent : Component
+    [DataField("hornSound")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public SoundSpecifier? HornSound = new SoundPathSpecifier("/Audio/Effects/Vehicle/carhorn.ogg")
     {
-        /// <summary>
-        /// Whether someone is currently riding the vehicle
-        /// </summary>
-        public bool HasRider => Rider != null;
-
-        /// <summary>
-        /// The entity currently riding the vehicle.
-        /// </summary>
-        [ViewVariables]
-        public EntityUid? Rider;
-
-        /// <summary>
-        /// The base offset for the vehicle (when facing east)
-        /// </summary>
-        public Vector2 BaseBuckleOffset = Vector2.Zero;
-
-        /// <summary>
-        /// The sound that the horn makes
-        /// </summary>
-        [DataField("hornSound")] public SoundSpecifier? HornSound =
-        new SoundPathSpecifier("/Audio/Effects/Vehicle/carhorn.ogg")
-        {
-            Params = AudioParams.Default.WithVolume(-3f)
-        };
-
-        public IPlayingAudioStream? HonkPlayingStream;
-
-        /// Use ambient sound component for the idle sound.
-
-        /// <summary>
-        /// The action for the horn (if any)
-        /// </summary>
-        [DataField("hornAction")]
-        public InstantAction HornAction = new()
-        {
-            UseDelay = TimeSpan.FromSeconds(3.4),
-            Icon = new SpriteSpecifier.Texture(new("Objects/Fun/bikehorn.rsi/icon.png")),
-            DisplayName = "action-name-honk",
-            Description = "action-desc-honk",
-            Event = new HonkActionEvent(),
-        };
-
-        /// <summary>
-        /// Whether the vehicle has a key currently inside it or not.
-        /// </summary>
-        [DataField("hasKey")]
-        public bool HasKey = false;
-
-        /// <summary>
-        /// Determines from which side the vehicle will be displayed on top of the player.
-        /// </summary>
-
-        [DataField("southOver")]
-        public bool SouthOver = false;
-
-        [DataField("northOver")]
-        public bool NorthOver = false;
-
-        [DataField("westOver")]
-        public bool WestOver = false;
-
-        [DataField("eastOver")]
-        public bool EastOver = false;
-
-        /// <summary>
-        /// What the y buckle offset should be in north / south
-        /// </summary>
-        [DataField("northOverride")]
-        public float NorthOverride = 0f;
-
-        /// <summary>
-        /// What the y buckle offset should be in north / south
-        /// </summary>
-        [DataField("southOverride")]
-        public float SouthOverride = 0f;
-    }
+        Params = AudioParams.Default.WithVolume(-3f)
+    };
+
+    [ViewVariables]
+    public IPlayingAudioStream? HonkPlayingStream;
+
+    /// Use ambient sound component for the idle sound.
+
+    /// <summary>
+    /// The action for the horn (if any)
+    /// </summary>
+    [DataField("hornAction")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public InstantAction HornAction = new()
+    {
+        UseDelay = TimeSpan.FromSeconds(3.4),
+        Icon = new SpriteSpecifier.Texture(new("Objects/Fun/bikehorn.rsi/icon.png")),
+        DisplayName = "action-name-honk",
+        Description = "action-desc-honk",
+        Event = new HonkActionEvent(),
+    };
+
+    /// <summary>
+    /// Whether the vehicle has a key currently inside it or not.
+    /// </summary>
+    [DataField("hasKey")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool HasKey;
+
+    /// <summary>
+    /// Determines from which side the vehicle will be displayed on top of the player.
+    /// </summary>
+
+    [DataField("southOver")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool SouthOver;
+
+    [DataField("northOver")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool NorthOver;
+
+    [DataField("westOver")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool WestOver;
+
+    [DataField("eastOver")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool EastOver;
+
+    /// <summary>
+    /// What the y buckle offset should be in north / south
+    /// </summary>
+    [DataField("northOverride")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public float NorthOverride;
+
+    /// <summary>
+    /// What the y buckle offset should be in north / south
+    /// </summary>
+    [DataField("southOverride")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public float SouthOverride;
+
+    [DataField("autoAnimate")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool AutoAnimate = true;
+
+    [DataField("hideRider")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool HideRider;
 }
index b47be83f032b18def8bed9bd39fd2d35c98182a6..b4b566390c881b27adf7165da7caf6c6342bc043 100644 (file)
@@ -1,18 +1,39 @@
+using Content.Shared.Hands;
 using Content.Shared.Physics.Pull;
 using Content.Shared.Vehicle.Components;
-using Robust.Shared.Serialization;
+using Robust.Shared.GameStates;
 
 namespace Content.Shared.Vehicle;
 
 public abstract partial class SharedVehicleSystem
 {
-    [Serializable, NetSerializable]
-    protected sealed class RiderComponentState : ComponentState
+    private void InitializeRider()
     {
-        public EntityUid? Entity;
+        SubscribeLocalEvent<RiderComponent, ComponentGetState>(OnRiderGetState);
+        SubscribeLocalEvent<RiderComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
+        SubscribeLocalEvent<RiderComponent, PullAttemptEvent>(OnPullAttempt);
     }
 
-    private void OnRiderPull(EntityUid uid, RiderComponent component, PullAttemptEvent args)
+    private void OnRiderGetState(EntityUid uid, RiderComponent component, ref ComponentGetState args)
+    {
+        args.State = new RiderComponentState()
+        {
+            Entity = component.Vehicle,
+        };
+    }
+
+    /// <summary>
+    /// Kick the rider off the vehicle if they press q / drop the virtual item
+    /// </summary>
+    private void OnVirtualItemDeleted(EntityUid uid, RiderComponent component, VirtualItemDeletedEvent args)
+    {
+        if (args.BlockingEntity == component.Vehicle)
+        {
+            _buckle.TryUnbuckle(uid, uid, true);
+        }
+    }
+
+    private void OnPullAttempt(EntityUid uid, RiderComponent component, PullAttemptEvent args)
     {
         if (component.Vehicle != null)
             args.Cancelled = true;
index 622b1bfdfb8e2126e9fefe8d9f39eabeb9528c29..d299945a2110fa2208c7e095f68a2a552274e398 100644 (file)
@@ -6,12 +6,16 @@ using Content.Shared.Buckle.Components;
 using Content.Shared.Item;
 using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Systems;
-using Content.Shared.Physics.Pull;
 using Robust.Shared.Serialization;
 using Robust.Shared.Containers;
 using Content.Shared.Tag;
 using Content.Shared.Audio;
-using Serilog;
+using Content.Shared.Buckle;
+using Content.Shared.Hands;
+using Content.Shared.Light.Component;
+using Content.Shared.Popups;
+using Robust.Shared.Network;
+using Robust.Shared.Physics.Systems;
 
 namespace Content.Shared.Vehicle;
 
@@ -22,26 +26,155 @@ namespace Content.Shared.Vehicle;
 /// </summary>
 public abstract partial class SharedVehicleSystem : EntitySystem
 {
+    [Dependency] private readonly INetManager _netManager = default!;
+
     [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
+    [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
     [Dependency] private readonly MovementSpeedModifierSystem _modifier = default!;
     [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
     [Dependency] private readonly SharedTransformSystem _transform = default!;
     [Dependency] private readonly TagSystem _tagSystem = default!;
     [Dependency] private readonly AccessReaderSystem _access = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly SharedHandVirtualItemSystem _virtualItemSystem = default!;
+    [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+    [Dependency] private readonly SharedJointSystem _joints = default!;
+    [Dependency] private readonly SharedBuckleSystem _buckle = default!;
+    [Dependency] private readonly SharedMoverController _mover = default!;
 
     private const string KeySlot = "key_slot";
 
+    /// <inheritdoc/>
     public override void Initialize()
     {
         base.Initialize();
-        SubscribeLocalEvent<InVehicleComponent, GettingPickedUpAttemptEvent>(OnPickupAttempt);
-        SubscribeLocalEvent<RiderComponent, PullAttemptEvent>(OnRiderPull);
-        SubscribeLocalEvent<VehicleComponent, RefreshMovementSpeedModifiersEvent>(OnVehicleModifier);
+        InitializeRider();
+
         SubscribeLocalEvent<VehicleComponent, ComponentStartup>(OnVehicleStartup);
-        SubscribeLocalEvent<VehicleComponent, MoveEvent>(OnVehicleRotate);
+        SubscribeLocalEvent<VehicleComponent, BuckleChangeEvent>(OnBuckleChange);
+        SubscribeLocalEvent<VehicleComponent, HonkActionEvent>(OnHonkAction);
         SubscribeLocalEvent<VehicleComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
         SubscribeLocalEvent<VehicleComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
+        SubscribeLocalEvent<VehicleComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
+        SubscribeLocalEvent<VehicleComponent, MoveEvent>(OnMoveEvent);
         SubscribeLocalEvent<VehicleComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
+
+        SubscribeLocalEvent<InVehicleComponent, GettingPickedUpAttemptEvent>(OnGettingPickedUpAttempt);
+    }
+
+    /// <summary>
+    /// This just controls whether the wheels are turning.
+    /// </summary>
+    public override void Update(float frameTime)
+    {
+        var vehicleQuery = EntityQueryEnumerator<VehicleComponent, InputMoverComponent>();
+        while (vehicleQuery.MoveNext(out var uid, out var vehicle, out var mover))
+        {
+            if (!vehicle.AutoAnimate)
+                continue;
+
+            if (_mover.GetVelocityInput(mover).Sprinting == Vector2.Zero)
+            {
+                UpdateAutoAnimate(uid, false);
+                continue;
+            }
+
+            UpdateAutoAnimate(uid, true);
+        }
+    }
+
+    private void OnVehicleStartup(EntityUid uid, VehicleComponent component, ComponentStartup args)
+    {
+        UpdateDrawDepth(uid, 2);
+
+        // This code should be purged anyway but with that being said this doesn't handle components being changed.
+        if (TryComp<StrapComponent>(uid, out var strap))
+        {
+            component.BaseBuckleOffset = strap.BuckleOffset;
+            strap.BuckleOffsetUnclamped = Vector2.Zero;
+        }
+
+        _modifier.RefreshMovementSpeedModifiers(uid);
+    }
+
+    /// <summary>
+    /// Give the user the rider component if they're buckling to the vehicle,
+    /// otherwise remove it.
+    /// </summary>
+    private void OnBuckleChange(EntityUid uid, VehicleComponent component, ref BuckleChangeEvent args)
+    {
+        // Add Rider
+        if (args.Buckling)
+        {
+            // Add a virtual item to rider's hand, unbuckle if we can't.
+            if (!_virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.BuckledEntity))
+            {
+                _buckle.TryUnbuckle(uid, uid, true);
+                return;
+            }
+
+            // Set up the rider and vehicle with each other
+            EnsureComp<InputMoverComponent>(uid);
+            var rider = EnsureComp<RiderComponent>(args.BuckledEntity);
+            component.Rider = args.BuckledEntity;
+            component.LastRider = component.Rider;
+            Dirty(component);
+            Appearance.SetData(uid, VehicleVisuals.HideRider, true);
+
+            var relay = EnsureComp<RelayInputMoverComponent>(args.BuckledEntity);
+            _mover.SetRelay(args.BuckledEntity, uid, relay);
+            rider.Vehicle = uid;
+
+            // Update appearance stuff, add actions
+            UpdateBuckleOffset(uid, Transform(uid), component);
+            if (TryComp<InputMoverComponent>(uid, out var mover))
+                UpdateDrawDepth(uid, GetDrawDepth(Transform(uid), component, mover.RelativeRotation.Degrees));
+
+            if (TryComp<ActionsComponent>(args.BuckledEntity, out var actions) && TryComp<UnpoweredFlashlightComponent>(uid, out var flashlight))
+            {
+                _actionsSystem.AddAction(args.BuckledEntity, flashlight.ToggleAction, uid, actions);
+            }
+
+            if (component.HornSound != null)
+            {
+                _actionsSystem.AddAction(args.BuckledEntity, component.HornAction, uid, actions);
+            }
+
+            _joints.ClearJoints(args.BuckledEntity);
+
+            return;
+        }
+
+        // Remove rider
+
+        // Clean up actions and virtual items
+        _actionsSystem.RemoveProvidedActions(args.BuckledEntity, uid);
+        _virtualItemSystem.DeleteInHandsMatching(args.BuckledEntity, uid);
+
+
+        // Entity is no longer riding
+        RemComp<RiderComponent>(args.BuckledEntity);
+        RemComp<RelayInputMoverComponent>(args.BuckledEntity);
+
+        Appearance.SetData(uid, VehicleVisuals.HideRider, false);
+        // Reset component
+        component.Rider = null;
+        Dirty(component);
+    }
+
+    /// <summary>
+    /// This fires when the rider presses the honk action
+    /// </summary>
+    private void OnHonkAction(EntityUid uid, VehicleComponent vehicle, HonkActionEvent args)
+    {
+        if (args.Handled || vehicle.HornSound == null)
+            return;
+
+        // TODO: Need audio refactor maybe, just some way to null it when the stream is over.
+        // For now better to just not loop to keep the code much cleaner.
+        vehicle.HonkPlayingStream?.Stop();
+        vehicle.HonkPlayingStream = _audioSystem.PlayPredicted(vehicle.HornSound, uid, uid);
+        args.Handled = true;
     }
 
     /// <summary>
@@ -60,6 +193,11 @@ public abstract partial class SharedVehicleSystem : EntitySystem
 
         component.HasKey = true;
 
+        var msg = Loc.GetString("vehicle-use-key",
+            ("keys", args.Entity), ("vehicle", uid));
+        if (_netManager.IsServer)
+            _popupSystem.PopupEntity(msg, uid, args.OldParent, PopupType.Medium);
+
         // Audiovisual feedback
         _ambientSound.SetAmbience(uid, true);
         _tagSystem.AddTag(uid, "DoorBumpOpener");
@@ -81,7 +219,7 @@ public abstract partial class SharedVehicleSystem : EntitySystem
         _modifier.RefreshMovementSpeedModifiers(uid);
     }
 
-    private void OnVehicleModifier(EntityUid uid, VehicleComponent component, RefreshMovementSpeedModifiersEvent args)
+    private void OnRefreshMovementSpeedModifiers(EntityUid uid, VehicleComponent component, RefreshMovementSpeedModifiersEvent args)
     {
         if (!component.HasKey)
         {
@@ -89,42 +227,28 @@ public abstract partial class SharedVehicleSystem : EntitySystem
         }
     }
 
-    private void OnPickupAttempt(EntityUid uid, InVehicleComponent component, GettingPickedUpAttemptEvent args)
-    {
-        if (component.Vehicle == null || component.Vehicle.Rider != null && component.Vehicle.Rider != args.User)
-            args.Cancel();
-    }
-
     // TODO: Shitcode, needs to use sprites instead of actual offsets.
-    private void OnVehicleRotate(EntityUid uid, VehicleComponent component, ref MoveEvent args)
+    private void OnMoveEvent(EntityUid uid, VehicleComponent component, ref MoveEvent args)
     {
         if (args.NewRotation == args.OldRotation)
             return;
 
         // This first check is just for safety
-        if (!HasComp<InputMoverComponent>(uid))
+        if (component.AutoAnimate && !HasComp<InputMoverComponent>(uid))
         {
             UpdateAutoAnimate(uid, false);
             return;
         }
 
-        UpdateBuckleOffset(args.Component, component);
+        UpdateBuckleOffset(uid, args.Component, component);
         if (TryComp<InputMoverComponent>(uid, out var mover))
             UpdateDrawDepth(uid, GetDrawDepth(args.Component, component, mover.RelativeRotation));
     }
 
-    private void OnVehicleStartup(EntityUid uid, VehicleComponent component, ComponentStartup args)
+    private void OnGettingPickedUpAttempt(EntityUid uid, InVehicleComponent component, GettingPickedUpAttemptEvent args)
     {
-        UpdateDrawDepth(uid, 2);
-
-        // This code should be purged anyway but with that being said this doesn't handle components being changed.
-        if (TryComp<StrapComponent>(uid, out var strap))
-        {
-            component.BaseBuckleOffset = strap.BuckleOffset;
-            strap.BuckleOffsetUnclamped = Vector2.Zero;
-        }
-
-        _modifier.RefreshMovementSpeedModifiers(uid);
+        if (component.Vehicle == null || component.Vehicle.Rider != null && component.Vehicle.Rider != args.User)
+            args.Cancel();
     }
 
     /// <summary>
@@ -132,7 +256,7 @@ public abstract partial class SharedVehicleSystem : EntitySystem
     /// change its draw depth. Vehicles can choose between special drawdetph
     /// when facing north or south. East and west are easy.
     /// </summary>
-    protected int GetDrawDepth(TransformComponent xform, VehicleComponent component, Angle cameraAngle)
+    private int GetDrawDepth(TransformComponent xform, VehicleComponent component, Angle cameraAngle)
     {
         var itemDirection = cameraAngle.GetDir() switch
         {
@@ -167,9 +291,9 @@ public abstract partial class SharedVehicleSystem : EntitySystem
     /// teleport any buckled entities to it. This is the most crucial part of making
     /// buckled vehicles work.
     /// </summary>
-    protected void UpdateBuckleOffset(TransformComponent xform, VehicleComponent component)
+    private void UpdateBuckleOffset(EntityUid uid, TransformComponent xform, VehicleComponent component)
     {
-        if (!TryComp<StrapComponent>(component.Owner, out var strap))
+        if (!TryComp<StrapComponent>(uid, out var strap))
             return;
 
         // TODO: Strap should handle this but buckle E/C moment.
@@ -208,7 +332,7 @@ public abstract partial class SharedVehicleSystem : EntitySystem
     /// <summary>
     /// Set the draw depth for the sprite.
     /// </summary>
-    protected void UpdateDrawDepth(EntityUid uid, int drawDepth)
+    private void UpdateDrawDepth(EntityUid uid, int drawDepth)
     {
         Appearance.SetData(uid, VehicleVisuals.DrawDepth, drawDepth);
     }
@@ -216,7 +340,7 @@ public abstract partial class SharedVehicleSystem : EntitySystem
     /// <summary>
     /// Set whether the vehicle's base layer is animating or not.
     /// </summary>
-    protected void UpdateAutoAnimate(EntityUid uid, bool autoAnimate)
+    private void UpdateAutoAnimate(EntityUid uid, bool autoAnimate)
     {
         Appearance.SetData(uid, VehicleVisuals.AutoAnimate, autoAnimate);
     }
@@ -235,10 +359,13 @@ public enum VehicleVisuals : byte
     /// <summary>
     /// Whether the wheels should be turning
     /// </summary>
-    AutoAnimate
+    AutoAnimate,
+    HideRider
 }
+
 /// <summary>
 /// Raised when someone honks a vehicle horn
 /// </summary>
-public sealed class HonkActionEvent : InstantActionEvent { }
-
+public sealed class HonkActionEvent : InstantActionEvent
+{
+}
index 9e925c158c1108bf8d19be9b8478eeb88ad000e4..5cb739b1beabc0c58e69f769b63f85b0d3bf8085 100644 (file)
@@ -38,7 +38,6 @@
       - MobMask
       layer:
       - TableLayer
-  - type: VehicleVisuals
   - type: Appearance
   - type: Repairable
     fuelcost: 20