From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Mon, 1 May 2023 07:04:23 +0000 (-0400) Subject: Moves buckling and vehicles to shared, some cleanup (#15923) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=878c7c0b855c333aaa9ea3fa7df8d99426298e4c;p=space-station-14.git Moves buckling and vehicles to shared, some cleanup (#15923) --- diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index 9d2f72b7cf..70d25488ba 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -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(OnBuckleHandleState); - SubscribeLocalEvent(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(OnBuckleHandleState); + SubscribeLocalEvent(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(buckle.LastEntityBuckledTo)) - return; + if (!TryComp(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(buckle.LastEntityBuckledTo, out var buckledSprite)) - { - buckle.OriginalDrawDepth ??= ownerSprite.DrawDepth; - ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1; - return; - } + if (HasComp(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(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(uid, out var rotVisuals)) - return; + ownerSprite.DrawDepth = component.OriginalDrawDepth.Value; + component.OriginalDrawDepth = null; + } + } - if (!_appearanceSystem.TryGetData(uid, StrapVisuals.RotationAngle, out var angle, args.Component) || - !_appearanceSystem.TryGetData(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(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(uid, StrapVisuals.RotationAngle, out var angle, args.Component) || + !AppearanceSystem.TryGetData(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); } } diff --git a/Content.Client/Vehicle/VehicleSystem.cs b/Content.Client/Vehicle/VehicleSystem.cs index a27e4b7da1..007ce34ff0 100644 --- a/Content.Client/Vehicle/VehicleSystem.cs +++ b/Content.Client/Vehicle/VehicleSystem.cs @@ -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(OnRiderStartup); + SubscribeLocalEvent(OnRiderShutdown); + SubscribeLocalEvent(OnRiderHandleState); + SubscribeLocalEvent(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(OnRiderStartup); - SubscribeLocalEvent(OnRiderShutdown); - SubscribeLocalEvent(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(uid, VehicleVisuals.HideRider, out var hide, args.Component) + && TryComp(component.LastRider, out var riderSprite)) + riderSprite.Visible = !hide; + + // First check is for the sprite itself + if (Appearance.TryGetData(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(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 index 459cf3f6fd..0000000000 --- a/Content.Client/Vehicle/VehicleVisualsComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Client.Vehicle; - -/// -/// Controls visuals for vehicles -/// -[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 index 9fbbebc935..0000000000 --- a/Content.Client/Vehicle/VehicleVisualsSystem.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Robust.Client.GameObjects; -using Content.Shared.Vehicle; - -namespace Content.Client.Vehicle -{ - /// - /// Controls client-side visuals for - /// vehicles - /// - public sealed class VehicleVisualsSystem : VisualizerSystem - { - 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(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(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.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index 848be3a448..f2f278b4ae 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -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(); var actionBlocker = entityManager.EntitySysManager.GetEntitySystem(); - var buckleSystem = entityManager.EntitySysManager.GetEntitySystem(); + var buckleSystem = entityManager.EntitySysManager.GetEntitySystem(); var standingState = entityManager.EntitySysManager.GetEntitySystem(); 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(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(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(human).WorldPosition += (1, 0); @@ -225,7 +225,7 @@ namespace Content.IntegrationTests.Tests.Buckle var entityManager = server.ResolveDependency(); var handsSys = entityManager.EntitySysManager.GetEntitySystem(); - var buckleSystem = entityManager.EntitySysManager.GetEntitySystem(); + var buckleSystem = entityManager.EntitySysManager.GetEntitySystem(); 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(); - var buckleSystem = entityManager.System(); + var buckleSystem = entityManager.System(); EntityUid human = default; EntityUid chair = default; @@ -320,7 +320,7 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.True(entityManager.HasComponent(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(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); }); diff --git a/Content.Server/Alert/Click/Unbuckle.cs b/Content.Server/Alert/Click/Unbuckle.cs index 8707b14ed3..b0a4e19099 100644 --- a/Content.Server/Alert/Click/Unbuckle.cs +++ b/Content.Server/Alert/Click/Unbuckle.cs @@ -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().System().TryUnbuckle(player, player); + IoCManager.Resolve().System().TryUnbuckle(player, player); } } } diff --git a/Content.Server/Bed/BedSystem.cs b/Content.Server/Bed/BedSystem.cs index 01346fd19a..9791eb2621 100644 --- a/Content.Server/Bed/BedSystem.cs +++ b/Content.Server/Bed/BedSystem.cs @@ -40,7 +40,7 @@ namespace Content.Server.Bed SubscribeLocalEvent(OnUpgradeExamine); } - private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, BuckleChangeEvent args) + private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, ref BuckleChangeEvent args) { _prototypeManager.TryIndex("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 index eb62e902f0..0000000000 --- a/Content.Server/Buckle/Systems/BuckleSystem.Buckle.cs +++ /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(OnBuckleStartup); - SubscribeLocalEvent(OnBuckleShutdown); - SubscribeLocalEvent(OnBuckleGetState); - SubscribeLocalEvent(MoveEvent); - SubscribeLocalEvent(HandleInteractHand); - SubscribeLocalEvent>(AddUnbuckleVerb); - SubscribeLocalEvent(OnEntityStorageInsertAttempt); - SubscribeLocalEvent(OnBuckleCanDrop); - SubscribeLocalEvent(OnBuckleDragDrop); - } - - private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetVerbsEvent 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(args.Target); - } - - private void OnBuckleDragDrop(EntityUid uid, BuckleComponent component, ref DragDropDraggedEvent args) - { - args.Handled = TryBuckle(uid, args.User, args.Target, component); - } - - /// - /// Shows or hides the buckled status effect depending on if the - /// entity is buckled or not. - /// - private void UpdateBuckleStatus(EntityUid uid, BuckleComponent component) - { - if (component.Buckled) - { - var alertType = component.BuckledTo?.BuckledAlertType ?? AlertType.Buckled; - _alerts.ShowAlert(uid, alertType); - } - else - { - _alerts.ClearAlertCategory(uid, AlertCategory.Buckled); - } - } - - private void SetBuckledTo(BuckleComponent buckle, StrapComponent? strap) - { - buckle.BuckledTo = strap; - buckle.LastEntityBuckledTo = strap?.Owner; - - if (strap == null) - { - buckle.Buckled = false; - } - else - { - buckle.DontCollide = true; - buckle.Buckled = true; - buckle.BuckleTime = _gameTiming.CurTime; - } - - _actionBlocker.UpdateCanMove(buckle.Owner); - UpdateBuckleStatus(buckle.Owner, buckle); - Dirty(buckle); - } - - 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(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(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; - } - - /// - /// Tries to unbuckle the Owner of this component from its current strap. - /// - /// The entity to unbuckle. - /// The entity doing the unbuckling. - /// - /// Whether to force the unbuckling or not. Does not guarantee true to - /// be returned, but guarantees the owner to be unbuckled afterwards. - /// - /// The buckle component of the entity to unbuckle. - /// - /// true if the owner was unbuckled, otherwise false even if the owner - /// was previously already unbuckled. - /// - public bool TryUnbuckle(EntityUid buckleId, EntityUid user, bool force = false, BuckleComponent? buckle = null) - { - if (!Resolve(buckleId, ref buckle, false) || - buckle.BuckledTo is not { } oldBuckledTo) - { - return false; - } - - if (!force) - { - if (_gameTiming.CurTime < buckle.BuckleTime + buckle.UnbuckleDelay) - return false; - - if (!_interactions.InRangeUnobstructed(user, oldBuckledTo.Owner, buckle.Range, popup: true)) - return false; - - if (HasComp(buckleId) && buckleId == user) - return false; - - // If the strap is a vehicle and the rider is not the person unbuckling, return. - if (TryComp(oldBuckledTo.Owner, out VehicleComponent? vehicle) && - vehicle.Rider != user) - return false; - } - - // 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(buckleId, out var mobState) && _mobState.IsIncapacitated(buckleId, mobState)) || - HasComp(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; - } - - /// - /// Makes an entity toggle the buckling status of the owner to a - /// specific entity. - /// - /// The entity to buckle/unbuckle from . - /// The entity doing the buckling/unbuckling. - /// - /// The entity to toggle the buckle status of the owner to. - /// - /// - /// Whether to force the unbuckling or not, if it happens. Does not - /// guarantee true to be returned, but guarantees the owner to be - /// unbuckled afterwards. - /// - /// The buckle component of the entity to buckle/unbuckle from . - /// true if the buckling status was changed, false otherwise. - public bool ToggleBuckle( - EntityUid buckleId, - EntityUid user, - EntityUid to, - bool force = false, - BuckleComponent? buckle = null) - { - if (!Resolve(buckleId, ref buckle, false)) - return false; - - if (buckle.BuckledTo?.Owner == to) - { - return TryUnbuckle(buckleId, user, force, buckle); - } - - return TryBuckle(buckleId, user, to, buckle); - } -} diff --git a/Content.Server/Buckle/Systems/BuckleSystem.Strap.cs b/Content.Server/Buckle/Systems/BuckleSystem.Strap.cs deleted file mode 100644 index 1ca2acdef3..0000000000 --- a/Content.Server/Buckle/Systems/BuckleSystem.Strap.cs +++ /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(OnStrapShutdown); - SubscribeLocalEvent((_, c, _) => StrapRemoveAll(c)); - SubscribeLocalEvent(OnStrapGetState); - SubscribeLocalEvent(ContainerModifiedStrap); - SubscribeLocalEvent(ContainerModifiedStrap); - SubscribeLocalEvent>(AddStrapVerbs); - SubscribeLocalEvent(OnStrapInsertAttempt); - SubscribeLocalEvent(OnStrapInteractHand); - SubscribeLocalEvent((_,c,_) => StrapRemoveAll(c)); - SubscribeLocalEvent((_, c, _) => StrapRemoveAll(c)); - SubscribeLocalEvent((_, c, _) => StrapRemoveAll(c)); - SubscribeLocalEvent(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(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 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(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(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(@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(@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); - } -} diff --git a/Content.Server/Buckle/Systems/BuckleSystem.cs b/Content.Server/Buckle/Systems/BuckleSystem.cs index f550208e96..50de302fb0 100644 --- a/Content.Server/Buckle/Systems/BuckleSystem.cs +++ b/Content.Server/Buckle/Systems/BuckleSystem.cs @@ -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(); - } } diff --git a/Content.Server/Climbing/ClimbSystem.cs b/Content.Server/Climbing/ClimbSystem.cs index 65759e2a3b..6f1a4ca8e3 100644 --- a/Content.Server/Climbing/ClimbSystem.cs +++ b/Content.Server/Climbing/ClimbSystem.cs @@ -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; diff --git a/Content.Server/Disease/Cures/DiseaseBedrestCure.cs b/Content.Server/Disease/Cures/DiseaseBedrestCure.cs index e5abe041dc..8e7177f81a 100644 --- a/Content.Server/Disease/Cures/DiseaseBedrestCure.cs +++ b/Content.Server/Disease/Cures/DiseaseBedrestCure.cs @@ -25,7 +25,7 @@ namespace Content.Server.Disease.Cures public override bool Cure(DiseaseEffectArgs args) { if (!args.EntityManager.TryGetComponent(args.DiseasedEntity, out var buckle) || - !args.EntityManager.HasComponent(buckle.BuckledTo?.Owner)) + !args.EntityManager.HasComponent(buckle.BuckledTo)) return false; var ticks = 1; diff --git a/Content.Server/Foldable/FoldableSystem.cs b/Content.Server/Foldable/FoldableSystem.cs index 04a967f650..e21ac65727 100644 --- a/Content.Server/Foldable/FoldableSystem.cs +++ b/Content.Server/Foldable/FoldableSystem.cs @@ -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 index 83ab63f623..0000000000 --- a/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Actions.ActionTypes; -using Content.Shared.Light; -using Robust.Shared.Audio; - -namespace Content.Server.Light.Components -{ - /// - /// This is simplified version of . - /// It doesn't consume any power and can be toggle only by verb. - /// - [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(); - } -} diff --git a/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs b/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs index 7bb1ec52eb..558ed39a2c 100644 --- a/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs +++ b/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs @@ -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; diff --git a/Content.Server/NPC/HTN/Preconditions/BuckledPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/BuckledPrecondition.cs index e53ee579fa..b7a07572a6 100644 --- a/Content.Server/NPC/HTN/Preconditions/BuckledPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/BuckledPrecondition.cs @@ -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; /// 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(); + _buckle = sysManager.GetEntitySystem(); } public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/PDA/PDASystem.cs b/Content.Server/PDA/PDASystem.cs index 92bacf95b7..f0448003fc 100644 --- a/Content.Server/PDA/PDASystem.cs +++ b/Content.Server/PDA/PDASystem.cs @@ -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 { diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index c08a92be17..621eafb3b6 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -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!; diff --git a/Content.Server/Toilet/ToiletSystem.cs b/Content.Server/Toilet/ToiletSystem.cs index d158b71616..1177fdf1d7 100644 --- a/Content.Server/Toilet/ToiletSystem.cs +++ b/Content.Server/Toilet/ToiletSystem.cs @@ -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(OnInit); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnInteractHand, new []{typeof(BuckleSystem)}); + SubscribeLocalEvent(OnInteractHand, new []{typeof(SharedBuckleSystem)}); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnSuicide); SubscribeLocalEvent(OnToiletPried); diff --git a/Content.Server/Vehicle/VehicleSystem.Rider.cs b/Content.Server/Vehicle/VehicleSystem.Rider.cs deleted file mode 100644 index eb835e3b9c..0000000000 --- a/Content.Server/Vehicle/VehicleSystem.Rider.cs +++ /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(OnRiderGetState); - SubscribeLocalEvent(OnVirtualItemDeleted); - SubscribeLocalEvent(OnFallDown); - SubscribeLocalEvent(OnMobStateChanged); - } - - private void OnRiderGetState(EntityUid uid, RiderComponent component, ref ComponentGetState args) - { - args.State = new RiderComponentState() - { - Entity = component.Vehicle, - }; - } - - /// - /// Kick the rider off the vehicle if they press q / drop the virtual item - /// - private void OnVirtualItemDeleted(EntityUid uid, RiderComponent component, VirtualItemDeletedEvent args) - { - if (args.BlockingEntity == component.Vehicle) - { - UnbuckleFromVehicle(uid); - } - } - - /// - /// Kick the rider off the vehicle if they get stunned - /// - private void OnFallDown(EntityUid uid, RiderComponent rider, FellDownEvent args) - { - UnbuckleFromVehicle(uid); - } - - /// - /// Kick the rider off the vehicle if they go into crit or die. - /// - 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); - } - } -} diff --git a/Content.Server/Vehicle/VehicleSystem.cs b/Content.Server/Vehicle/VehicleSystem.cs index 737c7ff359..f0eea36ffb 100644 --- a/Content.Server/Vehicle/VehicleSystem.cs +++ b/Content.Server/Vehicle/VehicleSystem.cs @@ -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(OnHonk); - SubscribeLocalEvent(OnBuckleChange); - } - - /// - /// This fires when the rider presses the honk action - /// - 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; - } - - /// - /// This just controls whether the wheels are turning. - /// - public override void Update(float frameTime) - { - foreach (var (vehicle, mover) in EntityQuery()) - { - if (_mover.GetVelocityInput(mover).Sprinting == Vector2.Zero) - { - UpdateAutoAnimate(vehicle.Owner, false); - continue; - } - UpdateAutoAnimate(vehicle.Owner, true); - } - } - - /// - /// Give the user the rider component if they're buckling to the vehicle, - /// otherwise remove it. - /// - 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(uid); - var rider = EnsureComp(args.BuckledEntity); - component.Rider = args.BuckledEntity; - - var relay = EnsureComp(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(uid, out var mover)) - UpdateDrawDepth(uid, GetDrawDepth(Transform(uid), component, mover.RelativeRotation.Degrees)); - - if (TryComp(args.BuckledEntity, out var actions) && TryComp(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(args.BuckledEntity); - RemComp(args.BuckledEntity); - - // Reset component - component.Rider = null; - } - } +public sealed class VehicleSystem : SharedVehicleSystem +{ } diff --git a/Content.Shared/Buckle/Components/BuckleComponent.cs b/Content.Shared/Buckle/Components/BuckleComponent.cs index 3d7269ce96..1e4cc3c196 100644 --- a/Content.Shared/Buckle/Components/BuckleComponent.cs +++ b/Content.Shared/Buckle/Components/BuckleComponent.cs @@ -5,76 +5,99 @@ using Robust.Shared.Serialization; namespace Content.Shared.Buckle.Components; [RegisterComponent, NetworkedComponent] +[Access(typeof(SharedBuckleSystem))] public sealed class BuckleComponent : Component { /// - /// The range from which this entity can buckle to a . + /// The range from which this entity can buckle to a . + /// Separated from normal interaction range to fix the "someone buckled to a strap + /// across a table two tiles away" problem. /// [DataField("range")] + [ViewVariables(VVAccess.ReadWrite)] public float Range = SharedInteractionSystem.InteractionRange / 1.4f; /// - /// True if the entity is buckled, false otherwise. + /// True if the entity is buckled, false otherwise. /// - 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; } + /// + /// Whether or not collisions should be possible with the entity we are strapped to + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("dontCollide")] + public bool DontCollide; + + /// + /// Whether or not we should be allowed to pull the entity we are strapped to + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("pullStrap")] + public bool PullStrap; /// - /// 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. /// [DataField("delay")] + [ViewVariables(VVAccess.ReadWrite)] public TimeSpan UnbuckleDelay = TimeSpan.FromSeconds(0.25f); /// - /// The time that this entity buckled at. + /// The time that this entity buckled at. /// - [ViewVariables] public TimeSpan BuckleTime; + [ViewVariables] + public TimeSpan BuckleTime; /// - /// The strap that this component is buckled to. + /// The strap that this component is buckled to. /// [ViewVariables] - public StrapComponent? BuckledTo { get; set; } + public EntityUid? BuckledTo; /// - /// The amount of space that this entity occupies in a - /// . + /// The amount of space that this entity occupies in a + /// . /// [DataField("size")] + [ViewVariables(VVAccess.ReadWrite)] public int Size = 100; /// /// Used for client rendering /// - 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 diff --git a/Content.Shared/Buckle/Components/StrapComponent.cs b/Content.Shared/Buckle/Components/StrapComponent.cs index 6d070a10af..5d19cb5883 100644 --- a/Content.Shared/Buckle/Components/StrapComponent.cs +++ b/Content.Shared/Buckle/Components/StrapComponent.cs @@ -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 { /// - /// (Default) Makes no change to the buckled mob + /// The entities that are currently buckled /// - None = 0, + [ViewVariables] + public readonly HashSet BuckledEntities = new(); /// - /// Makes the mob stand up + /// Entities that this strap accepts and can buckle + /// If null it accepts any entity /// - Stand, + [DataField("allowedEntities")] + [ViewVariables] + public EntityWhitelist? AllowedEntities; - /// - /// Makes the mob lie down - /// - Down -} - -[RegisterComponent, NetworkedComponent] -public sealed class StrapComponent : Component -{ /// /// The change in position to the strapped mob /// [DataField("position")] - public StrapPosition Position { get; set; } = StrapPosition.None; - - /// - /// The entity that is currently buckled here - /// - public readonly HashSet BuckledEntities = new(); + [ViewVariables(VVAccess.ReadWrite)] + public StrapPosition Position = StrapPosition.None; /// /// The distance above which a buckled entity will be automatically unbuckled. /// Don't change it unless you really have to /// + /// + /// Dont set this below 0.2 because that causes audio issues with + /// 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. + /// [DataField("maxBuckleDistance", required: false)] - public float MaxBuckleDistance = 0.1f; + [ViewVariables(VVAccess.ReadWrite)] + public float MaxBuckleDistance = 0.2f; /// /// Gets and clamps the buckle offset to MaxBuckleDistance /// + [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 /// [DataField("buckleOffset", required: false)] - [Access(Other = AccessPermissions.ReadWrite)] + [ViewVariables(VVAccess.ReadWrite)] public Vector2 BuckleOffsetUnclamped = Vector2.Zero; - /// /// The angle in degrees to rotate the player by when they get strapped /// [DataField("rotation")] - public int Rotation { get; set; } + [ViewVariables(VVAccess.ReadWrite)] + public int Rotation; /// /// The size of the strap which is compared against when buckling entities /// [DataField("size")] - public int Size { get; set; } = 100; + [ViewVariables(VVAccess.ReadWrite)] + public int Size = 100; /// /// If disabled, nothing can be buckled on this object, and it will unbuckle anything that's already buckled /// - public bool Enabled { get; set; } = true; + [ViewVariables] + public bool Enabled = true; /// /// You can specify the offset the entity will have after unbuckling. /// [DataField("unbuckleOffset", required: false)] + [ViewVariables(VVAccess.ReadWrite)] public Vector2 UnbuckleOffset = Vector2.Zero; + /// /// The sound to be played when a mob is buckled /// [DataField("buckleSound")] - public SoundSpecifier BuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/buckle.ogg"); + [ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier BuckleSound = new SoundPathSpecifier("/Audio/Effects/buckle.ogg"); /// /// The sound to be played when a mob is unbuckled /// [DataField("unbuckleSound")] - public SoundSpecifier UnbuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg"); + [ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier UnbuckleSound = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg"); /// /// ID of the alert to show when buckled /// [DataField("buckledAlertType")] - public AlertType BuckledAlertType { get; } = AlertType.Buckled; + [ViewVariables(VVAccess.ReadWrite)] + public AlertType BuckledAlertType = AlertType.Buckled; /// /// The sum of the sizes of all the buckled entities in this strap /// - public int OccupiedSize { get; set; } + [ViewVariables] + public int OccupiedSize; } [Serializable, NetSerializable] public sealed class StrapComponentState : ComponentState { - /// - /// The change in position that this strap makes to the strapped mob - /// - public StrapPosition Position; - - public float MaxBuckleDistance; - public Vector2 BuckleOffsetClamped; - public HashSet BuckledEntities; - - public StrapComponentState(StrapPosition position, Vector2 offset, HashSet buckled, float maxBuckleDistance) + public readonly StrapPosition Position; + public readonly float MaxBuckleDistance; + public readonly Vector2 BuckleOffsetClamped; + public readonly HashSet BuckledEntities; + public readonly int OccupiedSize; + + public StrapComponentState(StrapPosition position, Vector2 offset, HashSet buckled, + float maxBuckleDistance, int occupiedSize) { Position = position; BuckleOffsetClamped = offset; BuckledEntities = buckled; MaxBuckleDistance = maxBuckleDistance; + OccupiedSize = occupiedSize; } } +public enum StrapPosition +{ + /// + /// (Default) Makes no change to the buckled mob + /// + None = 0, + + /// + /// Makes the mob stand up + /// + Stand, + + /// + /// Makes the mob lie down + /// + Down +} + [Serializable, NetSerializable] public enum StrapVisuals : byte { diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 972db5b7c9..1716e03818 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -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(PreventCollision); - SubscribeLocalEvent(HandleDown); - SubscribeLocalEvent(HandleStand); - SubscribeLocalEvent(HandleThrowPushback); - SubscribeLocalEvent(HandleMove); + SubscribeLocalEvent(OnBuckleComponentStartup); + SubscribeLocalEvent(OnBuckleComponentShutdown); + SubscribeLocalEvent(OnBuckleComponentGetState); + SubscribeLocalEvent(OnBuckleMove); + SubscribeLocalEvent(OnBuckleInteractHand); + SubscribeLocalEvent>(AddUnbuckleVerb); + SubscribeLocalEvent(OnBuckleInsertIntoEntityStorageAttempt); + + SubscribeLocalEvent(OnBucklePreventCollide); + SubscribeLocalEvent(OnBuckleDownAttempt); + SubscribeLocalEvent(OnBuckleStandAttempt); + SubscribeLocalEvent(OnBuckleThrowPushbackAttempt); + SubscribeLocalEvent(OnBuckleUpdateCanMove); SubscribeLocalEvent(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(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 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(Transform(uid).ParentUid)) // buckle+vehicle shitcode + !HasComp(component.BuckledTo)) // buckle+vehicle shitcode args.Cancel(); } @@ -70,38 +161,369 @@ public abstract partial class SharedBuckleSystem } /// - /// 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. /// - /// The entity to reattach. - /// The strap to reattach to. - /// The buckle component of the entity to reattach. - public void ReAttach(EntityUid buckleId, StrapComponent strap, BuckleComponent? buckle = null) + /// Entity that we want to show the alert + /// buckle component of the entity + /// strap component of the thing we are strapping to + 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); + /// + /// Sets the field in the component to a value + /// + /// Value tat with be assigned to the field + private void SetBuckledTo(EntityUid buckleUid, EntityUid? strapUid, StrapComponent? strapComp, BuckleComponent buckleComp) + { + buckleComp.BuckledTo = strapUid; - // Buckle subscribes to move for 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) + /// + /// Checks whether or not buckling is possible + /// + /// Uid of the owner of BuckleComponent + /// + /// Uid of a third party entity, + /// i.e, the uid of someone else you are dragging to a chair. + /// Can equal buckleUid sometimes + /// + /// Uid of the owner of strap component + 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(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; + } + + /// + /// Attempts to buckle an entity to a strap + /// + /// Uid of the owner of BuckleComponent + /// + /// Uid of a third party entity, + /// i.e, the uid of someone else you are dragging to a chair. + /// Can equal buckleUid sometimes + /// + /// Uid of the owner of strap component + 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(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(buckleUid, out var ownerPullable)) + { + if (ownerPullable.Puller != null) + { + _pullingSystem.TryStopPull(ownerPullable); + } + } + + if (!buckleComp.PullStrap && TryComp(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; + } + + /// + /// Tries to unbuckle the Owner of this component from its current strap. + /// + /// The entity to unbuckle. + /// The entity doing the unbuckling. + /// + /// Whether to force the unbuckling or not. Does not guarantee true to + /// be returned, but guarantees the owner to be unbuckled afterwards. + /// + /// The buckle component of the entity to unbuckle. + /// + /// true if the owner was unbuckled, otherwise false even if the owner + /// was previously already unbuckled. + /// + public bool TryUnbuckle(EntityUid 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(buckleUid) && buckleUid == userUid) + return false; + + // If the strap is a vehicle and the rider is not the person unbuckling, return. + if (TryComp(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(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(buckleUid, out var mobState) + && _mobStateSystem.IsIncapacitated(buckleUid, mobState) + || HasComp(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; + } + + /// + /// Makes an entity toggle the buckling status of the owner to a + /// specific entity. + /// + /// The entity to buckle/unbuckle from . + /// The entity doing the buckling/unbuckling. + /// + /// The entity to toggle the buckle status of the owner to. + /// + /// + /// Whether to force the unbuckling or not, if it happens. Does not + /// guarantee true to be returned, but guarantees the owner to be + /// unbuckled afterwards. + /// + /// The buckle component of the entity to buckle/unbuckle from . + /// true if the buckling status was changed, false otherwise. + public bool ToggleBuckle( + EntityUid 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); + } + } } diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Strap.cs b/Content.Shared/Buckle/SharedBuckleSystem.Strap.cs index 9e9ffa1583..a87f9fea14 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Strap.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Strap.cs @@ -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(OnStrapRotate); + SubscribeLocalEvent(OnStrapShutdown); + SubscribeLocalEvent((_, c, _) => StrapRemoveAll(c)); + + SubscribeLocalEvent(OnStrapGetState); SubscribeLocalEvent(OnStrapHandleState); - SubscribeLocalEvent(OnStrapCanDropOn); + + SubscribeLocalEvent(OnStrapEntModifiedFromContainer); + SubscribeLocalEvent(OnStrapEntModifiedFromContainer); + SubscribeLocalEvent>(AddStrapVerbs); + SubscribeLocalEvent(OnStrapContainerGettingInsertedAttempt); + SubscribeLocalEvent(OnStrapInteractHand); + SubscribeLocalEvent((_,c,_) => StrapRemoveAll(c)); + SubscribeLocalEvent((_, c, _) => StrapRemoveAll(c)); + + SubscribeLocalEvent(OnStrapDragDropTarget); + SubscribeLocalEvent(OnCanDropTarget); + + SubscribeLocalEvent(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(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(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 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(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(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 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(@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(@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(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) + /// + /// Remove everything attached to the strap + /// + 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; + } + + /// + /// Try to add an entity to the strap + /// + 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; + } + + /// + /// Sets the enabled field in the strap component to a value + /// + 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); } } diff --git a/Content.Shared/Buckle/SharedBuckleSystem.cs b/Content.Shared/Buckle/SharedBuckleSystem.cs index 9ad83acbfa..49b86cb968 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.cs @@ -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!; + /// public override void Initialize() { base.Initialize(); + UpdatesAfter.Add(typeof(SharedInteractionSystem)); + UpdatesAfter.Add(typeof(SharedInputSystem)); + InitializeBuckle(); InitializeStrap(); } + + /// + /// Reattaches this entity to the strap, modifying its position and rotation. + /// + /// The entity to reattach. + /// The entity to reattach the buckleUid entity to. + 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 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 index 0000000000..f9d572e69d --- /dev/null +++ b/Content.Shared/Light/Component/UnpoweredFlashlightComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Actions.ActionTypes; +using Robust.Shared.Audio; + +namespace Content.Shared.Light.Component; + +/// +/// This is simplified version of . +/// It doesn't consume any power and can be toggle only by verb. +/// +[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(); +} diff --git a/Content.Shared/Pulling/Systems/SharedPullingSystem.Actions.cs b/Content.Shared/Pulling/Systems/SharedPullingSystem.Actions.cs index 9fbd04382b..c0893a4079 100644 --- a/Content.Shared/Pulling/Systems/SharedPullingSystem.Actions.cs +++ b/Content.Shared/Pulling/Systems/SharedPullingSystem.Actions.cs @@ -64,7 +64,7 @@ namespace Content.Shared.Pulling if (EntityManager.TryGetComponent(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; } diff --git a/Content.Shared/Vehicle/Components/RiderComponent.cs b/Content.Shared/Vehicle/Components/RiderComponent.cs index 3fd559e2d6..76bbabe325 100644 --- a/Content.Shared/Vehicle/Components/RiderComponent.cs +++ b/Content.Shared/Vehicle/Components/RiderComponent.cs @@ -1,19 +1,25 @@ using Robust.Shared.GameStates; +using Robust.Shared.Serialization; -namespace Content.Shared.Vehicle.Components +namespace Content.Shared.Vehicle.Components; + +/// +/// Added to people when they are riding in a vehicle +/// used mostly to keep track of them for entityquery. +/// +[RegisterComponent, NetworkedComponent] +public sealed class RiderComponent : Component { /// - /// 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. /// - [RegisterComponent, NetworkedComponent] - public sealed class RiderComponent : Component - { - /// - /// The vehicle this rider is currently riding. - /// - [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; } diff --git a/Content.Shared/Vehicle/Components/VehicleComponent.cs b/Content.Shared/Vehicle/Components/VehicleComponent.cs index 30dfabc4bf..01053f6c65 100644 --- a/Content.Shared/Vehicle/Components/VehicleComponent.cs +++ b/Content.Shared/Vehicle/Components/VehicleComponent.cs @@ -1,92 +1,113 @@ 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; + +/// +/// 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. +/// +[AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedVehicleSystem))] +public sealed partial class VehicleComponent : Component { /// - /// 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. + /// + [ViewVariables] + [AutoNetworkedField] + public EntityUid? Rider; + + [ViewVariables] + [AutoNetworkedField] + public EntityUid? LastRider; + + /// + /// The base offset for the vehicle (when facing east) + /// + [ViewVariables] + public Vector2 BaseBuckleOffset = Vector2.Zero; + + /// + /// The sound that the horn makes /// - [RegisterComponent] - public sealed class VehicleComponent : Component + [DataField("hornSound")] + [ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier? HornSound = new SoundPathSpecifier("/Audio/Effects/Vehicle/carhorn.ogg") { - /// - /// Whether someone is currently riding the vehicle - /// - public bool HasRider => Rider != null; - - /// - /// The entity currently riding the vehicle. - /// - [ViewVariables] - public EntityUid? Rider; - - /// - /// The base offset for the vehicle (when facing east) - /// - public Vector2 BaseBuckleOffset = Vector2.Zero; - - /// - /// The sound that the horn makes - /// - [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. - - /// - /// The action for the horn (if any) - /// - [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(), - }; - - /// - /// Whether the vehicle has a key currently inside it or not. - /// - [DataField("hasKey")] - public bool HasKey = false; - - /// - /// Determines from which side the vehicle will be displayed on top of the player. - /// - - [DataField("southOver")] - public bool SouthOver = false; - - [DataField("northOver")] - public bool NorthOver = false; - - [DataField("westOver")] - public bool WestOver = false; - - [DataField("eastOver")] - public bool EastOver = false; - - /// - /// What the y buckle offset should be in north / south - /// - [DataField("northOverride")] - public float NorthOverride = 0f; - - /// - /// What the y buckle offset should be in north / south - /// - [DataField("southOverride")] - public float SouthOverride = 0f; - } + Params = AudioParams.Default.WithVolume(-3f) + }; + + [ViewVariables] + public IPlayingAudioStream? HonkPlayingStream; + + /// Use ambient sound component for the idle sound. + + /// + /// The action for the horn (if any) + /// + [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(), + }; + + /// + /// Whether the vehicle has a key currently inside it or not. + /// + [DataField("hasKey")] + [ViewVariables(VVAccess.ReadWrite)] + public bool HasKey; + + /// + /// Determines from which side the vehicle will be displayed on top of the player. + /// + + [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; + + /// + /// What the y buckle offset should be in north / south + /// + [DataField("northOverride")] + [ViewVariables(VVAccess.ReadWrite)] + public float NorthOverride; + + /// + /// What the y buckle offset should be in north / south + /// + [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; } diff --git a/Content.Shared/Vehicle/SharedVehicleSystem.Rider.cs b/Content.Shared/Vehicle/SharedVehicleSystem.Rider.cs index b47be83f03..b4b566390c 100644 --- a/Content.Shared/Vehicle/SharedVehicleSystem.Rider.cs +++ b/Content.Shared/Vehicle/SharedVehicleSystem.Rider.cs @@ -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(OnRiderGetState); + SubscribeLocalEvent(OnVirtualItemDeleted); + SubscribeLocalEvent(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, + }; + } + + /// + /// Kick the rider off the vehicle if they press q / drop the virtual item + /// + 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; diff --git a/Content.Shared/Vehicle/SharedVehicleSystem.cs b/Content.Shared/Vehicle/SharedVehicleSystem.cs index 622b1bfdfb..d299945a21 100644 --- a/Content.Shared/Vehicle/SharedVehicleSystem.cs +++ b/Content.Shared/Vehicle/SharedVehicleSystem.cs @@ -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; /// 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"; + /// public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPickupAttempt); - SubscribeLocalEvent(OnRiderPull); - SubscribeLocalEvent(OnVehicleModifier); + InitializeRider(); + SubscribeLocalEvent(OnVehicleStartup); - SubscribeLocalEvent(OnVehicleRotate); + SubscribeLocalEvent(OnBuckleChange); + SubscribeLocalEvent(OnHonkAction); SubscribeLocalEvent(OnEntInserted); SubscribeLocalEvent(OnEntRemoved); + SubscribeLocalEvent(OnRefreshMovementSpeedModifiers); + SubscribeLocalEvent(OnMoveEvent); SubscribeLocalEvent(OnGetAdditionalAccess); + + SubscribeLocalEvent(OnGettingPickedUpAttempt); + } + + /// + /// This just controls whether the wheels are turning. + /// + public override void Update(float frameTime) + { + var vehicleQuery = EntityQueryEnumerator(); + 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(uid, out var strap)) + { + component.BaseBuckleOffset = strap.BuckleOffset; + strap.BuckleOffsetUnclamped = Vector2.Zero; + } + + _modifier.RefreshMovementSpeedModifiers(uid); + } + + /// + /// Give the user the rider component if they're buckling to the vehicle, + /// otherwise remove it. + /// + 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(uid); + var rider = EnsureComp(args.BuckledEntity); + component.Rider = args.BuckledEntity; + component.LastRider = component.Rider; + Dirty(component); + Appearance.SetData(uid, VehicleVisuals.HideRider, true); + + var relay = EnsureComp(args.BuckledEntity); + _mover.SetRelay(args.BuckledEntity, uid, relay); + rider.Vehicle = uid; + + // Update appearance stuff, add actions + UpdateBuckleOffset(uid, Transform(uid), component); + if (TryComp(uid, out var mover)) + UpdateDrawDepth(uid, GetDrawDepth(Transform(uid), component, mover.RelativeRotation.Degrees)); + + if (TryComp(args.BuckledEntity, out var actions) && TryComp(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(args.BuckledEntity); + RemComp(args.BuckledEntity); + + Appearance.SetData(uid, VehicleVisuals.HideRider, false); + // Reset component + component.Rider = null; + Dirty(component); + } + + /// + /// This fires when the rider presses the honk action + /// + 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; } /// @@ -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(uid)) + if (component.AutoAnimate && !HasComp(uid)) { UpdateAutoAnimate(uid, false); return; } - UpdateBuckleOffset(args.Component, component); + UpdateBuckleOffset(uid, args.Component, component); if (TryComp(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(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(); } /// @@ -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. /// - 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. /// - protected void UpdateBuckleOffset(TransformComponent xform, VehicleComponent component) + private void UpdateBuckleOffset(EntityUid uid, TransformComponent xform, VehicleComponent component) { - if (!TryComp(component.Owner, out var strap)) + if (!TryComp(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 /// /// Set the draw depth for the sprite. /// - 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 /// /// Set whether the vehicle's base layer is animating or not. /// - 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 /// /// Whether the wheels should be turning /// - AutoAnimate + AutoAnimate, + HideRider } + /// /// Raised when someone honks a vehicle horn /// -public sealed class HonkActionEvent : InstantActionEvent { } - +public sealed class HonkActionEvent : InstantActionEvent +{ +} diff --git a/Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml b/Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml index 9e925c158c..5cb739b1be 100644 --- a/Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml +++ b/Resources/Prototypes/Entities/Objects/Vehicles/buckleable.yml @@ -38,7 +38,6 @@ - MobMask layer: - TableLayer - - type: VehicleVisuals - type: Appearance - type: Repairable fuelcost: 20