Moves buckling and vehicles to shared, some cleanup (#15923)

This commit is contained in:
AJCM-git
2023-05-01 03:04:23 -04:00
committed by GitHub
parent 2343dd1d3c
commit 878c7c0b85
33 changed files with 1451 additions and 1405 deletions

View File

@@ -5,76 +5,99 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Buckle.Components;
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedBuckleSystem))]
public sealed class BuckleComponent : Component
{
/// <summary>
/// The range from which this entity can buckle to a <see cref="StrapComponent"/>.
/// The range from which this entity can buckle to a <see cref="StrapComponent"/>.
/// Separated from normal interaction range to fix the "someone buckled to a strap
/// across a table two tiles away" problem.
/// </summary>
[DataField("range")]
[ViewVariables(VVAccess.ReadWrite)]
public float Range = SharedInteractionSystem.InteractionRange / 1.4f;
/// <summary>
/// True if the entity is buckled, false otherwise.
/// True if the entity is buckled, false otherwise.
/// </summary>
public bool Buckled { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public bool Buckled;
public EntityUid? LastEntityBuckledTo { get; set; }
public bool DontCollide { get; set; }
[ViewVariables]
public EntityUid? LastEntityBuckledTo;
/// <summary>
/// The amount of time that must pass for this entity to
/// be able to unbuckle after recently buckling.
/// Whether or not collisions should be possible with the entity we are strapped to
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("dontCollide")]
public bool DontCollide;
/// <summary>
/// Whether or not we should be allowed to pull the entity we are strapped to
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("pullStrap")]
public bool PullStrap;
/// <summary>
/// The amount of time that must pass for this entity to
/// be able to unbuckle after recently buckling.
/// </summary>
[DataField("delay")]
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan UnbuckleDelay = TimeSpan.FromSeconds(0.25f);
/// <summary>
/// The time that this entity buckled at.
/// </summary>
[ViewVariables] public TimeSpan BuckleTime;
/// <summary>
/// The strap that this component is buckled to.
/// The time that this entity buckled at.
/// </summary>
[ViewVariables]
public StrapComponent? BuckledTo { get; set; }
public TimeSpan BuckleTime;
/// <summary>
/// The amount of space that this entity occupies in a
/// <see cref="StrapComponent"/>.
/// The strap that this component is buckled to.
/// </summary>
[ViewVariables]
public EntityUid? BuckledTo;
/// <summary>
/// The amount of space that this entity occupies in a
/// <see cref="StrapComponent"/>.
/// </summary>
[DataField("size")]
[ViewVariables(VVAccess.ReadWrite)]
public int Size = 100;
/// <summary>
/// Used for client rendering
/// </summary>
public int? OriginalDrawDepth { get; set; }
[ViewVariables]
public int? OriginalDrawDepth;
}
[Serializable, NetSerializable]
public sealed class BuckleComponentState : ComponentState
{
public BuckleComponentState(bool buckled, EntityUid? lastEntityBuckledTo, bool dontCollide)
public BuckleComponentState(bool buckled, EntityUid? buckledTo, EntityUid? lastEntityBuckledTo,
bool dontCollide)
{
Buckled = buckled;
BuckledTo = buckledTo;
LastEntityBuckledTo = lastEntityBuckledTo;
DontCollide = dontCollide;
}
public bool Buckled { get; }
public EntityUid? LastEntityBuckledTo { get; }
public bool DontCollide { get; }
public readonly bool Buckled;
public readonly EntityUid? BuckledTo;
public readonly EntityUid? LastEntityBuckledTo;
public readonly bool DontCollide;
}
public sealed class BuckleChangeEvent : EntityEventArgs
{
public EntityUid Strap;
[ByRefEvent]
public readonly record struct BuckleAttemptEvent(EntityUid StrapEntity, EntityUid BuckledEntity, bool Buckling, bool Cancelled = false);
public EntityUid BuckledEntity;
public bool Buckling;
}
[ByRefEvent]
public readonly record struct BuckleChangeEvent(EntityUid StrapEntity, EntityUid BuckledEntity, bool Buckling);
[Serializable, NetSerializable]
public enum BuckleVisuals

View File

@@ -1,10 +1,143 @@
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;
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedBuckleSystem), typeof(SharedVehicleSystem))]
public sealed class StrapComponent : Component
{
/// <summary>
/// The entities that are currently buckled
/// </summary>
[ViewVariables]
public readonly HashSet<EntityUid> BuckledEntities = new();
/// <summary>
/// Entities that this strap accepts and can buckle
/// If null it accepts any entity
/// </summary>
[DataField("allowedEntities")]
[ViewVariables]
public EntityWhitelist? AllowedEntities;
/// <summary>
/// The change in position to the strapped mob
/// </summary>
[DataField("position")]
[ViewVariables(VVAccess.ReadWrite)]
public StrapPosition Position = StrapPosition.None;
/// <summary>
/// The distance above which a buckled entity will be automatically unbuckled.
/// Don't change it unless you really have to
/// </summary>
/// <remarks>
/// Dont set this below 0.2 because that causes audio issues with <see cref="SharedBuckleSystem.OnBuckleMove"/>
/// My guess after testing is that the client sets BuckledTo to the strap in *some* ticks for some reason
/// whereas the server doesnt, thus the client tries to unbuckle like 15 times because it passes the strap null check
/// This is why this needs to be above 0.1 to make the InRange check fail in both client and server.
/// </remarks>
[DataField("maxBuckleDistance", required: false)]
[ViewVariables(VVAccess.ReadWrite)]
public float MaxBuckleDistance = 0.2f;
/// <summary>
/// Gets and clamps the buckle offset to MaxBuckleDistance
/// </summary>
[ViewVariables]
public Vector2 BuckleOffset => Vector2.Clamp(
BuckleOffsetUnclamped,
Vector2.One * -MaxBuckleDistance,
Vector2.One * MaxBuckleDistance);
/// <summary>
/// The buckled entity will be offset by this amount from the center of the strap object.
/// If this offset it too big, it will be clamped to <see cref="MaxBuckleDistance"/>
/// </summary>
[DataField("buckleOffset", required: false)]
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 BuckleOffsetUnclamped = Vector2.Zero;
/// <summary>
/// The angle in degrees to rotate the player by when they get strapped
/// </summary>
[DataField("rotation")]
[ViewVariables(VVAccess.ReadWrite)]
public int Rotation;
/// <summary>
/// The size of the strap which is compared against when buckling entities
/// </summary>
[DataField("size")]
[ViewVariables(VVAccess.ReadWrite)]
public int Size = 100;
/// <summary>
/// If disabled, nothing can be buckled on this object, and it will unbuckle anything that's already buckled
/// </summary>
[ViewVariables]
public bool Enabled = true;
/// <summary>
/// You can specify the offset the entity will have after unbuckling.
/// </summary>
[DataField("unbuckleOffset", required: false)]
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 UnbuckleOffset = Vector2.Zero;
/// <summary>
/// The sound to be played when a mob is buckled
/// </summary>
[DataField("buckleSound")]
[ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier BuckleSound = new SoundPathSpecifier("/Audio/Effects/buckle.ogg");
/// <summary>
/// The sound to be played when a mob is unbuckled
/// </summary>
[DataField("unbuckleSound")]
[ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier UnbuckleSound = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg");
/// <summary>
/// ID of the alert to show when buckled
/// </summary>
[DataField("buckledAlertType")]
[ViewVariables(VVAccess.ReadWrite)]
public AlertType BuckledAlertType = AlertType.Buckled;
/// <summary>
/// The sum of the sizes of all the buckled entities in this strap
/// </summary>
[ViewVariables]
public int OccupiedSize;
}
[Serializable, NetSerializable]
public sealed class StrapComponentState : ComponentState
{
public readonly StrapPosition Position;
public readonly float MaxBuckleDistance;
public readonly Vector2 BuckleOffsetClamped;
public readonly HashSet<EntityUid> BuckledEntities;
public readonly int OccupiedSize;
public StrapComponentState(StrapPosition position, Vector2 offset, HashSet<EntityUid> buckled,
float maxBuckleDistance, int occupiedSize)
{
Position = position;
BuckleOffsetClamped = offset;
BuckledEntities = buckled;
MaxBuckleDistance = maxBuckleDistance;
OccupiedSize = occupiedSize;
}
}
public enum StrapPosition
{
/// <summary>
@@ -23,111 +156,6 @@ public enum StrapPosition
Down
}
[RegisterComponent, NetworkedComponent]
public sealed class StrapComponent : Component
{
/// <summary>
/// The change in position to the strapped mob
/// </summary>
[DataField("position")]
public StrapPosition Position { get; set; } = StrapPosition.None;
/// <summary>
/// The entity that is currently buckled here
/// </summary>
public readonly HashSet<EntityUid> BuckledEntities = new();
/// <summary>
/// The distance above which a buckled entity will be automatically unbuckled.
/// Don't change it unless you really have to
/// </summary>
[DataField("maxBuckleDistance", required: false)]
public float MaxBuckleDistance = 0.1f;
/// <summary>
/// Gets and clamps the buckle offset to MaxBuckleDistance
/// </summary>
public Vector2 BuckleOffset => Vector2.Clamp(
BuckleOffsetUnclamped,
Vector2.One * -MaxBuckleDistance,
Vector2.One * MaxBuckleDistance);
/// <summary>
/// The buckled entity will be offset by this amount from the center of the strap object.
/// If this offset it too big, it will be clamped to <see cref="MaxBuckleDistance"/>
/// </summary>
[DataField("buckleOffset", required: false)]
[Access(Other = AccessPermissions.ReadWrite)]
public Vector2 BuckleOffsetUnclamped = Vector2.Zero;
/// <summary>
/// The angle in degrees to rotate the player by when they get strapped
/// </summary>
[DataField("rotation")]
public int Rotation { get; set; }
/// <summary>
/// The size of the strap which is compared against when buckling entities
/// </summary>
[DataField("size")]
public int Size { get; set; } = 100;
/// <summary>
/// If disabled, nothing can be buckled on this object, and it will unbuckle anything that's already buckled
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// You can specify the offset the entity will have after unbuckling.
/// </summary>
[DataField("unbuckleOffset", required: false)]
public Vector2 UnbuckleOffset = Vector2.Zero;
/// <summary>
/// The sound to be played when a mob is buckled
/// </summary>
[DataField("buckleSound")]
public SoundSpecifier BuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/buckle.ogg");
/// <summary>
/// The sound to be played when a mob is unbuckled
/// </summary>
[DataField("unbuckleSound")]
public SoundSpecifier UnbuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg");
/// <summary>
/// ID of the alert to show when buckled
/// </summary>
[DataField("buckledAlertType")]
public AlertType BuckledAlertType { get; } = AlertType.Buckled;
/// <summary>
/// The sum of the sizes of all the buckled entities in this strap
/// </summary>
public int OccupiedSize { get; set; }
}
[Serializable, NetSerializable]
public sealed class StrapComponentState : ComponentState
{
/// <summary>
/// The change in position that this strap makes to the strapped mob
/// </summary>
public StrapPosition Position;
public float MaxBuckleDistance;
public Vector2 BuckleOffsetClamped;
public HashSet<EntityUid> BuckledEntities;
public StrapComponentState(StrapPosition position, Vector2 offset, HashSet<EntityUid> buckled, float maxBuckleDistance)
{
Position = position;
BuckleOffsetClamped = offset;
BuckledEntities = buckled;
MaxBuckleDistance = maxBuckleDistance;
}
}
[Serializable, NetSerializable]
public enum StrapVisuals : byte
{

View File

@@ -1,11 +1,25 @@
using Content.Shared.Buckle.Components;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Alert;
using Content.Shared.Bed.Sleep;
using Content.Shared.Buckle.Components;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Popups;
using Content.Shared.Pulling.Components;
using Content.Shared.Standing;
using Content.Shared.Storage.Components;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Content.Shared.Vehicle.Components;
using Robust.Shared.Map;
using Content.Shared.Verbs;
using Robust.Shared.GameStates;
using Robust.Shared.Physics.Events;
using Robust.Shared.Utility;
namespace Content.Shared.Buckle;
@@ -13,48 +27,125 @@ public abstract partial class SharedBuckleSystem
{
private void InitializeBuckle()
{
SubscribeLocalEvent<BuckleComponent, PreventCollideEvent>(PreventCollision);
SubscribeLocalEvent<BuckleComponent, DownAttemptEvent>(HandleDown);
SubscribeLocalEvent<BuckleComponent, StandAttemptEvent>(HandleStand);
SubscribeLocalEvent<BuckleComponent, ThrowPushbackAttemptEvent>(HandleThrowPushback);
SubscribeLocalEvent<BuckleComponent, UpdateCanMoveEvent>(HandleMove);
SubscribeLocalEvent<BuckleComponent, ComponentStartup>(OnBuckleComponentStartup);
SubscribeLocalEvent<BuckleComponent, ComponentShutdown>(OnBuckleComponentShutdown);
SubscribeLocalEvent<BuckleComponent, ComponentGetState>(OnBuckleComponentGetState);
SubscribeLocalEvent<BuckleComponent, MoveEvent>(OnBuckleMove);
SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(OnBuckleInteractHand);
SubscribeLocalEvent<BuckleComponent, GetVerbsEvent<InteractionVerb>>(AddUnbuckleVerb);
SubscribeLocalEvent<BuckleComponent, InsertIntoEntityStorageAttemptEvent>(OnBuckleInsertIntoEntityStorageAttempt);
SubscribeLocalEvent<BuckleComponent, PreventCollideEvent>(OnBucklePreventCollide);
SubscribeLocalEvent<BuckleComponent, DownAttemptEvent>(OnBuckleDownAttempt);
SubscribeLocalEvent<BuckleComponent, StandAttemptEvent>(OnBuckleStandAttempt);
SubscribeLocalEvent<BuckleComponent, ThrowPushbackAttemptEvent>(OnBuckleThrowPushbackAttempt);
SubscribeLocalEvent<BuckleComponent, UpdateCanMoveEvent>(OnBuckleUpdateCanMove);
SubscribeLocalEvent<BuckleComponent, ChangeDirectionAttemptEvent>(OnBuckleChangeDirectionAttempt);
}
private void PreventCollision(EntityUid uid, BuckleComponent component, ref PreventCollideEvent args)
private void OnBuckleComponentStartup(EntityUid uid, BuckleComponent component, ComponentStartup args)
{
if (args.BodyB.Owner != component.LastEntityBuckledTo)
UpdateBuckleStatus(uid, component);
}
private void OnBuckleComponentShutdown(EntityUid uid, BuckleComponent component, ComponentShutdown args)
{
TryUnbuckle(uid, uid, true, component);
component.BuckleTime = default;
}
private void OnBuckleComponentGetState(EntityUid uid, BuckleComponent component, ref ComponentGetState args)
{
args.State = new BuckleComponentState(component.Buckled, component.BuckledTo, component.LastEntityBuckledTo, component.DontCollide);
}
private void OnBuckleMove(EntityUid uid, BuckleComponent component, ref MoveEvent ev)
{
if (component.BuckledTo is not {} strapUid)
return;
if (!TryComp<StrapComponent>(strapUid, out var strapComp))
return;
var strapPosition = Transform(strapUid).Coordinates;
if (ev.NewPosition.InRange(EntityManager, _transformSystem, strapPosition, strapComp.MaxBuckleDistance))
return;
TryUnbuckle(uid, uid, true, component);
}
private void OnBuckleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args)
{
if (!component.Buckled)
return;
if (TryUnbuckle(uid, args.User, buckleComp: component))
args.Handled = true;
}
private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract || !component.Buckled)
return;
InteractionVerb verb = new()
{
Act = () => TryUnbuckle(uid, args.User, buckleComp: component),
Text = Loc.GetString("verb-categories-unbuckle"),
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"))
};
if (args.Target == args.User && args.Using == null)
{
// A user is left clicking themselves with an empty hand, while buckled.
// It is very likely they are trying to unbuckle themselves.
verb.Priority = 1;
}
args.Verbs.Add(verb);
}
private void OnBuckleInsertIntoEntityStorageAttempt(EntityUid uid, BuckleComponent component, ref InsertIntoEntityStorageAttemptEvent args)
{
if (component.Buckled)
args.Cancelled = true;
}
private void OnBucklePreventCollide(EntityUid uid, BuckleComponent component, ref PreventCollideEvent args)
{
if (args.BodyB.Owner != component.BuckledTo)
return;
if (component.Buckled || component.DontCollide)
args.Cancelled = true;
}
private void HandleDown(EntityUid uid, BuckleComponent component, DownAttemptEvent args)
private void OnBuckleDownAttempt(EntityUid uid, BuckleComponent component, DownAttemptEvent args)
{
if (component.Buckled)
args.Cancel();
}
private void HandleStand(EntityUid uid, BuckleComponent component, StandAttemptEvent args)
private void OnBuckleStandAttempt(EntityUid uid, BuckleComponent component, StandAttemptEvent args)
{
if (component.Buckled)
args.Cancel();
}
private void HandleThrowPushback(EntityUid uid, BuckleComponent component, ThrowPushbackAttemptEvent args)
private void OnBuckleThrowPushbackAttempt(EntityUid uid, BuckleComponent component, ThrowPushbackAttemptEvent args)
{
if (component.Buckled)
args.Cancel();
}
private void HandleMove(EntityUid uid, BuckleComponent component, UpdateCanMoveEvent args)
private void OnBuckleUpdateCanMove(EntityUid uid, BuckleComponent component, UpdateCanMoveEvent args)
{
if (component.LifeStage > ComponentLifeStage.Running)
return;
if (component.Buckled &&
!HasComp<VehicleComponent>(Transform(uid).ParentUid)) // buckle+vehicle shitcode
!HasComp<VehicleComponent>(component.BuckledTo)) // buckle+vehicle shitcode
args.Cancel();
}
@@ -70,38 +161,369 @@ public abstract partial class SharedBuckleSystem
}
/// <summary>
/// Reattaches this entity to the strap, modifying its position and rotation.
/// Shows or hides the buckled status effect depending on if the
/// entity is buckled or not.
/// </summary>
/// <param name="buckleId">The entity to reattach.</param>
/// <param name="strap">The strap to reattach to.</param>
/// <param name="buckle">The buckle component of the entity to reattach.</param>
public void ReAttach(EntityUid buckleId, StrapComponent strap, BuckleComponent? buckle = null)
/// <param name="uid"> Entity that we want to show the alert </param>
/// <param name="buckleComp"> buckle component of the entity </param>
/// <param name="strapComp"> strap component of the thing we are strapping to </param>
private void UpdateBuckleStatus(EntityUid uid, BuckleComponent buckleComp, StrapComponent? strapComp = null)
{
if (!Resolve(buckleId, ref buckle, false))
return;
var ownTransform = Transform(buckleId);
var strapTransform = Transform(strap.Owner);
ownTransform.Coordinates = new EntityCoordinates(strapTransform.Owner, strap.BuckleOffset);
// Buckle subscribes to move for <reasons> so this might fail.
// TODO: Make buckle not do that.
if (ownTransform.ParentUid != strapTransform.Owner)
return;
ownTransform.LocalRotation = Angle.Zero;
switch (strap.Position)
if (buckleComp.BuckledTo != null)
{
case StrapPosition.None:
break;
case StrapPosition.Stand:
_standing.Stand(buckleId);
break;
case StrapPosition.Down:
_standing.Down(buckleId, false, false);
break;
if (!Resolve(buckleComp.BuckledTo.Value, ref strapComp))
return;
var alertType = strapComp.BuckledAlertType;
_alertsSystem.ShowAlert(uid, alertType);
}
else
{
_alertsSystem.ClearAlertCategory(uid, AlertCategory.Buckled);
}
}
/// <summary>
/// Sets the <see cref="BuckleComponent.BuckledTo"/> field in the component to a value
/// </summary>
/// <param name="strapUid"> Value tat with be assigned to the field </param>
private void SetBuckledTo(EntityUid buckleUid, EntityUid? strapUid, StrapComponent? strapComp, BuckleComponent buckleComp)
{
buckleComp.BuckledTo = strapUid;
if (strapUid == null)
{
buckleComp.Buckled = false;
}
else
{
buckleComp.LastEntityBuckledTo = strapUid;
buckleComp.DontCollide = true;
buckleComp.Buckled = true;
buckleComp.BuckleTime = _gameTiming.CurTime;
}
ActionBlockerSystem.UpdateCanMove(buckleUid);
UpdateBuckleStatus(buckleUid, buckleComp, strapComp);
Dirty(buckleComp);
}
/// <summary>
/// Checks whether or not buckling is possible
/// </summary>
/// <param name="buckleUid"> Uid of the owner of BuckleComponent </param>
/// <param name="userUid">
/// Uid of a third party entity,
/// i.e, the uid of someone else you are dragging to a chair.
/// Can equal buckleUid sometimes
/// </param>
/// <param name="strapUid"> Uid of the owner of strap component </param>
private bool CanBuckle(
EntityUid buckleUid,
EntityUid userUid,
EntityUid strapUid,
[NotNullWhen(true)] out StrapComponent? strapComp,
BuckleComponent? buckleComp = null)
{
strapComp = null;
if (userUid == strapUid ||
!Resolve(buckleUid, ref buckleComp, false) ||
!Resolve(strapUid, ref strapComp, false))
{
return false;
}
// Does it pass the Whitelist
if (strapComp.AllowedEntities != null &&
!strapComp.AllowedEntities.IsValid(userUid, EntityManager))
{
if (_netManager.IsServer)
_popupSystem.PopupEntity(Loc.GetString("buckle-component-cannot-fit-message"), userUid, buckleUid, PopupType.Medium);
return false;
}
// Is it within range
bool Ignored(EntityUid entity) => entity == buckleUid || entity == userUid || entity == strapUid;
if (!_interactionSystem.InRangeUnobstructed(buckleUid, strapUid, buckleComp.Range, predicate: Ignored,
popup: true))
{
return false;
}
// If in a container
if (_containerSystem.TryGetContainingContainer(buckleUid, out var ownerContainer))
{
// And not in the same container as the strap
if (!_containerSystem.TryGetContainingContainer(strapUid, out var strapContainer) ||
ownerContainer != strapContainer)
{
return false;
}
}
if (!HasComp<HandsComponent>(userUid))
{
// PopupPredicted when
if (_netManager.IsServer)
_popupSystem.PopupEntity(Loc.GetString("buckle-component-no-hands-message"), userUid, userUid);
return false;
}
if (buckleComp.Buckled)
{
var message = Loc.GetString(buckleUid == userUid
? "buckle-component-already-buckled-message"
: "buckle-component-other-already-buckled-message",
("owner", Identity.Entity(buckleUid, EntityManager)));
if (_netManager.IsServer)
_popupSystem.PopupEntity(message, userUid, userUid);
return false;
}
var parent = Transform(strapUid).ParentUid;
while (parent.IsValid())
{
if (parent == userUid)
{
var message = Loc.GetString(buckleUid == userUid
? "buckle-component-cannot-buckle-message"
: "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
if (_netManager.IsServer)
_popupSystem.PopupEntity(message, userUid, userUid);
return false;
}
parent = Transform(parent).ParentUid;
}
if (!StrapHasSpace(strapUid, buckleComp, strapComp))
{
var message = Loc.GetString(buckleUid == userUid
? "buckle-component-cannot-fit-message"
: "buckle-component-other-cannot-fit-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
if (_netManager.IsServer)
_popupSystem.PopupEntity(message, userUid, userUid);
return false;
}
return true;
}
/// <summary>
/// Attempts to buckle an entity to a strap
/// </summary>
/// <param name="buckleUid"> Uid of the owner of BuckleComponent </param>
/// <param name="userUid">
/// Uid of a third party entity,
/// i.e, the uid of someone else you are dragging to a chair.
/// Can equal buckleUid sometimes
/// </param>
/// <param name="strapUid"> Uid of the owner of strap component </param>
public bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUid, BuckleComponent? buckleComp = null)
{
if (!Resolve(buckleUid, ref buckleComp, false))
return false;
if (!CanBuckle(buckleUid, userUid, strapUid, out var strapComp, buckleComp))
return false;
var attemptEvent = new BuckleAttemptEvent(strapUid, buckleUid, true);
RaiseLocalEvent(attemptEvent.BuckledEntity, ref attemptEvent);
RaiseLocalEvent(attemptEvent.StrapEntity, ref attemptEvent);
if (attemptEvent.Cancelled)
return false;
if (!StrapTryAdd(strapUid, buckleUid, buckleComp, false, strapComp))
{
var message = Loc.GetString(buckleUid == userUid
? "buckle-component-cannot-buckle-message"
: "buckle-component-other-cannot-buckle-message", ("owner", Identity.Entity(buckleUid, EntityManager)));
if (_netManager.IsServer)
_popupSystem.PopupEntity(message, userUid, userUid);
return false;
}
if (TryComp<AppearanceComponent>(buckleUid, out var appearance))
AppearanceSystem.SetData(buckleUid, BuckleVisuals.Buckled, true, appearance);
ReAttach(buckleUid, strapUid, buckleComp, strapComp);
SetBuckledTo(buckleUid,strapUid, strapComp, buckleComp);
_audioSystem.PlayPredicted(strapComp.BuckleSound, strapUid, buckleUid);
var ev = new BuckleChangeEvent(strapUid, buckleUid, true);
RaiseLocalEvent(ev.BuckledEntity, ref ev);
RaiseLocalEvent(ev.StrapEntity, ref ev);
if (TryComp<SharedPullableComponent>(buckleUid, out var ownerPullable))
{
if (ownerPullable.Puller != null)
{
_pullingSystem.TryStopPull(ownerPullable);
}
}
if (!buckleComp.PullStrap && TryComp<SharedPullableComponent>(strapUid, out var toPullable))
{
if (toPullable.Puller == buckleUid)
{
// can't pull it and buckle to it at the same time
_pullingSystem.TryStopPull(toPullable);
}
}
// Logging
if (userUid != buckleUid)
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} buckled {ToPrettyString(buckleUid)} to {ToPrettyString(strapUid)}");
else
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} buckled themselves to {ToPrettyString(strapUid)}");
return true;
}
/// <summary>
/// Tries to unbuckle the Owner of this component from its current strap.
/// </summary>
/// <param name="buckleUid">The entity to unbuckle.</param>
/// <param name="userUid">The entity doing the unbuckling.</param>
/// <param name="force">
/// Whether to force the unbuckling or not. Does not guarantee true to
/// be returned, but guarantees the owner to be unbuckled afterwards.
/// </param>
/// <param name="buckleComp">The buckle component of the entity to unbuckle.</param>
/// <returns>
/// true if the owner was unbuckled, otherwise false even if the owner
/// was previously already unbuckled.
/// </returns>
public bool TryUnbuckle(EntityUid buckleUid, EntityUid userUid, bool force = false, BuckleComponent? buckleComp = null)
{
if (!Resolve(buckleUid, ref buckleComp, false) ||
buckleComp.BuckledTo is not { } strapUid)
return false;
var attemptEvent = new BuckleAttemptEvent(strapUid, buckleUid, false);
RaiseLocalEvent(attemptEvent.BuckledEntity, ref attemptEvent);
RaiseLocalEvent(attemptEvent.StrapEntity, ref attemptEvent);
if (attemptEvent.Cancelled)
return false;
Logger.Debug($"{force}");
if (!force)
{
if (_gameTiming.CurTime < buckleComp.BuckleTime + buckleComp.UnbuckleDelay)
return false;
if (!_interactionSystem.InRangeUnobstructed(userUid, strapUid, buckleComp.Range, popup: true))
return false;
if (HasComp<SleepingComponent>(buckleUid) && buckleUid == userUid)
return false;
// If the strap is a vehicle and the rider is not the person unbuckling, return.
if (TryComp<VehicleComponent>(strapUid, out var vehicle) &&
vehicle.Rider != userUid)
return false;
}
// Logging
if (userUid != buckleUid)
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} unbuckled {ToPrettyString(buckleUid)} from {ToPrettyString(strapUid)}");
else
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(userUid):player} unbuckled themselves from {ToPrettyString(strapUid)}");
SetBuckledTo(buckleUid, null, null, buckleComp);
if (!TryComp<StrapComponent>(strapUid, out var strapComp))
return false;
var buckleXform = Transform(buckleUid);
var oldBuckledXform = Transform(strapUid);
if (buckleXform.ParentUid == strapUid && !Terminating(buckleXform.ParentUid))
{
_containerSystem.AttachParentToContainerOrGrid(buckleXform);
var oldBuckledToWorldRot = _transformSystem.GetWorldRotation(strapUid);
_transformSystem.SetWorldRotation(buckleXform, oldBuckledToWorldRot);
if (strapComp.UnbuckleOffset != Vector2.Zero)
buckleXform.Coordinates = oldBuckledXform.Coordinates.Offset(strapComp.UnbuckleOffset);
}
if (TryComp(buckleUid, out AppearanceComponent? appearance))
AppearanceSystem.SetData(buckleUid, BuckleVisuals.Buckled, false, appearance);
if (TryComp<MobStateComponent>(buckleUid, out var mobState)
&& _mobStateSystem.IsIncapacitated(buckleUid, mobState)
|| HasComp<KnockedDownComponent>(buckleUid))
{
_standingSystem.Down(buckleUid);
}
else
{
_standingSystem.Stand(buckleUid);
}
if (_mobStateSystem.IsIncapacitated(buckleUid, mobState))
{
_standingSystem.Down(buckleUid);
}
// Sync StrapComponent data
AppearanceSystem.SetData(strapUid, StrapVisuals.State, false);
if (strapComp.BuckledEntities.Remove(buckleUid))
{
strapComp.OccupiedSize -= buckleComp.Size;
//Dirty(strapUid);
Dirty(strapComp);
}
_audioSystem.PlayPredicted(strapComp.UnbuckleSound, strapUid, buckleUid);
var ev = new BuckleChangeEvent(strapUid, buckleUid, false);
RaiseLocalEvent(buckleUid, ref ev);
RaiseLocalEvent(strapUid, ref ev);
return true;
}
/// <summary>
/// Makes an entity toggle the buckling status of the owner to a
/// specific entity.
/// </summary>
/// <param name="buckleUid">The entity to buckle/unbuckle from <see cref="to"/>.</param>
/// <param name="userUid">The entity doing the buckling/unbuckling.</param>
/// <param name="strapUid">
/// The entity to toggle the buckle status of the owner to.
/// </param>
/// <param name="force">
/// Whether to force the unbuckling or not, if it happens. Does not
/// guarantee true to be returned, but guarantees the owner to be
/// unbuckled afterwards.
/// </param>
/// <param name="buckle">The buckle component of the entity to buckle/unbuckle from <see cref="to"/>.</param>
/// <returns>true if the buckling status was changed, false otherwise.</returns>
public bool ToggleBuckle(
EntityUid buckleUid,
EntityUid userUid,
EntityUid strapUid,
bool force = false,
BuckleComponent? buckle = null)
{
if (!Resolve(buckleUid, ref buckle, false))
return false;
if (!buckle.Buckled)
{
return TryBuckle(buckleUid, userUid, strapUid, buckle);
}
else
{
return TryUnbuckle(buckleUid, userUid, force, buckle);
}
}
}

View File

@@ -1,19 +1,50 @@
using Content.Shared.Buckle.Components;
using System.Linq;
using Content.Shared.Buckle.Components;
using Content.Shared.Destructible;
using Content.Shared.DragDrop;
using Content.Shared.Interaction;
using Content.Shared.Storage;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
namespace Content.Shared.Buckle;
public abstract partial class SharedBuckleSystem
{
[Dependency] private readonly SharedInteractionSystem _interactions = default!;
private void InitializeStrap()
{
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapRotate);
SubscribeLocalEvent<StrapComponent, ComponentShutdown>(OnStrapShutdown);
SubscribeLocalEvent<StrapComponent, ComponentRemove>((_, c, _) => StrapRemoveAll(c));
SubscribeLocalEvent<StrapComponent, ComponentGetState>(OnStrapGetState);
SubscribeLocalEvent<StrapComponent, ComponentHandleState>(OnStrapHandleState);
SubscribeLocalEvent<StrapComponent, CanDropTargetEvent>(OnStrapCanDropOn);
SubscribeLocalEvent<StrapComponent, EntInsertedIntoContainerMessage>(OnStrapEntModifiedFromContainer);
SubscribeLocalEvent<StrapComponent, EntRemovedFromContainerMessage>(OnStrapEntModifiedFromContainer);
SubscribeLocalEvent<StrapComponent, GetVerbsEvent<InteractionVerb>>(AddStrapVerbs);
SubscribeLocalEvent<StrapComponent, ContainerGettingInsertedAttemptEvent>(OnStrapContainerGettingInsertedAttempt);
SubscribeLocalEvent<StrapComponent, InteractHandEvent>(OnStrapInteractHand);
SubscribeLocalEvent<StrapComponent, DestructionEventArgs>((_,c,_) => StrapRemoveAll(c));
SubscribeLocalEvent<StrapComponent, BreakageEventArgs>((_, c, _) => StrapRemoveAll(c));
SubscribeLocalEvent<StrapComponent, DragDropTargetEvent>(OnStrapDragDropTarget);
SubscribeLocalEvent<StrapComponent, CanDropTargetEvent>(OnCanDropTarget);
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
}
private void OnStrapShutdown(EntityUid uid, StrapComponent component, ComponentShutdown args)
{
if (LifeStage(uid) > EntityLifeStage.MapInitialized)
return;
StrapRemoveAll(component);
}
private void OnStrapGetState(EntityUid uid, StrapComponent component, ref ComponentGetState args)
{
args.State = new StrapComponentState(component.Position, component.BuckleOffset, component.BuckledEntities, component.MaxBuckleDistance, component.OccupiedSize);
}
private void OnStrapHandleState(EntityUid uid, StrapComponent component, ref ComponentHandleState args)
@@ -26,9 +57,151 @@ public abstract partial class SharedBuckleSystem
component.BuckledEntities.Clear();
component.BuckledEntities.UnionWith(state.BuckledEntities);
component.MaxBuckleDistance = state.MaxBuckleDistance;
component.OccupiedSize = state.OccupiedSize;
}
private void OnStrapRotate(EntityUid uid, StrapComponent component, ref MoveEvent args)
private void OnStrapEntModifiedFromContainer(EntityUid uid, StrapComponent component, ContainerModifiedMessage message)
{
if (_gameTiming.ApplyingState)
return;
foreach (var buckledEntity in component.BuckledEntities)
{
if (!TryComp<BuckleComponent>(buckledEntity, out var buckleComp))
{
continue;
}
ContainerModifiedReAttach(buckledEntity, uid, buckleComp, component);
}
}
private void ContainerModifiedReAttach(EntityUid buckleUid, EntityUid strapUid, BuckleComponent? buckleComp = null, StrapComponent? strapComp = null)
{
if (!Resolve(buckleUid, ref buckleComp, false) ||
!Resolve(strapUid, ref strapComp, false))
return;
var contained = _containerSystem.TryGetContainingContainer(buckleUid, out var ownContainer);
var strapContained = _containerSystem.TryGetContainingContainer(strapUid, out var strapContainer);
if (contained != strapContained || ownContainer != strapContainer)
{
TryUnbuckle(buckleUid, buckleUid, true, buckleComp);
return;
}
if (!contained)
{
ReAttach(buckleUid, strapUid, buckleComp, strapComp);
}
}
private void OnStrapContainerGettingInsertedAttempt(EntityUid uid, StrapComponent component, ContainerGettingInsertedAttemptEvent args)
{
// If someone is attempting to put this item inside of a backpack, ensure that it has no entities strapped to it.
if (HasComp<SharedStorageComponent>(args.Container.Owner) && component.BuckledEntities.Count != 0)
args.Cancel();
}
private void OnStrapInteractHand(EntityUid uid, StrapComponent component, InteractHandEvent args)
{
if (args.Handled)
return;
ToggleBuckle(args.User, args.User, uid);
}
private void AddStrapVerbs(EntityUid uid, StrapComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract || !component.Enabled)
return;
// Note that for whatever bloody reason, buckle component has its own interaction range. Additionally, this
// range can be set per-component, so we have to check a modified InRangeUnobstructed for every verb.
// Add unstrap verbs for every strapped entity.
foreach (var entity in component.BuckledEntities)
{
var buckledComp = Comp<BuckleComponent>(entity);
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckledComp.Range))
continue;
var verb = new InteractionVerb()
{
Act = () => TryUnbuckle(entity, args.User, buckleComp: buckledComp),
Category = VerbCategory.Unbuckle,
Text = entity == args.User
? Loc.GetString("verb-self-target-pronoun")
: Comp<MetaDataComponent>(entity).EntityName
};
// In the event that you have more than once entity with the same name strapped to the same object,
// these two verbs will be identical according to Verb.CompareTo, and only one with actually be added to
// the verb list. However this should rarely ever be a problem. If it ever is, it could be fixed by
// appending an integer to verb.Text to distinguish the verbs.
args.Verbs.Add(verb);
}
// Add a verb to buckle the user.
if (TryComp<BuckleComponent>(args.User, out var buckle) &&
buckle.BuckledTo != uid &&
args.User != uid &&
StrapHasSpace(uid, buckle, component) &&
_interactionSystem.InRangeUnobstructed(args.User, args.Target, range: buckle.Range))
{
InteractionVerb verb = new()
{
Act = () => TryBuckle(args.User, args.User, args.Target, buckle),
Category = VerbCategory.Buckle,
Text = Loc.GetString("verb-self-target-pronoun")
};
args.Verbs.Add(verb);
}
// If the user is currently holding/pulling an entity that can be buckled, add a verb for that.
if (args.Using is {Valid: true} @using &&
TryComp<BuckleComponent>(@using, out var usingBuckle) &&
StrapHasSpace(uid, usingBuckle, component) &&
_interactionSystem.InRangeUnobstructed(@using, args.Target, range: usingBuckle.Range))
{
// Check that the entity is unobstructed from the target (ignoring the user).
bool Ignored(EntityUid entity) => entity == args.User || entity == args.Target || entity == @using;
if (!_interactionSystem.InRangeUnobstructed(@using, args.Target, usingBuckle.Range, predicate: Ignored))
return;
var isPlayer = _playerManager.TryGetSessionByEntity(@using, out var _);
InteractionVerb verb = new()
{
Act = () => TryBuckle(@using, args.User, args.Target, usingBuckle),
Category = VerbCategory.Buckle,
Text = Comp<MetaDataComponent>(@using).EntityName,
// just a held object, the user is probably just trying to sit down.
// If the used entity is a person being pulled, prioritize this verb. Conversely, if it is
Priority = isPlayer ? 1 : -1
};
args.Verbs.Add(verb);
}
}
private void OnCanDropTarget(EntityUid uid, StrapComponent component, ref CanDropTargetEvent args)
{
args.CanDrop = StrapCanDragDropOn(uid, args.User, uid, args.Dragged, component);
args.Handled = true;
}
private void OnStrapDragDropTarget(EntityUid uid, StrapComponent component, ref DragDropTargetEvent args)
{
if (!StrapCanDragDropOn(uid, args.User, uid, args.Dragged, component))
return;
args.Handled = TryBuckle(args.Dragged, args.User, uid);
}
private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)
{
// TODO: This looks dirty af.
// On rotation of a strap, reattach all buckled entities.
@@ -45,15 +218,13 @@ public abstract partial class SharedBuckleSystem
// One option is to just never trigger re-buckles during state application.
// another is to.. just not do this? Like wtf is this code. But I CBF with buckle atm.
if (GameTiming.ApplyingState || args.NewRotation == args.OldRotation)
if (_gameTiming.ApplyingState || args.NewRotation == args.OldRotation)
return;
foreach (var buckledEntity in component.BuckledEntities)
{
if (!EntityManager.TryGetComponent(buckledEntity, out BuckleComponent? buckled))
{
if (!TryComp<BuckleComponent>(buckledEntity, out var buckled))
continue;
}
if (!buckled.Buckled || buckled.LastEntityBuckledTo != uid)
{
@@ -61,33 +232,90 @@ public abstract partial class SharedBuckleSystem
continue;
}
ReAttach(buckledEntity, component, buckle: buckled);
ReAttach(buckledEntity, uid, buckled, component);
Dirty(buckled);
}
}
protected bool StrapCanDragDropOn(
EntityUid strapId,
EntityUid user,
EntityUid target,
EntityUid buckleId,
StrapComponent? strap = null,
BuckleComponent? buckle = null)
private bool StrapCanDragDropOn(
EntityUid strapUid,
EntityUid userUid,
EntityUid targetUid,
EntityUid buckleUid,
StrapComponent? strapComp = null,
BuckleComponent? buckleComp = null)
{
if (!Resolve(strapId, ref strap, false) ||
!Resolve(buckleId, ref buckle, false))
if (!Resolve(strapUid, ref strapComp, false) ||
!Resolve(buckleUid, ref buckleComp, false))
{
return false;
}
bool Ignored(EntityUid entity) => entity == user || entity == buckleId || entity == target;
bool Ignored(EntityUid entity) => entity == userUid || entity == buckleUid || entity == targetUid;
return _interactions.InRangeUnobstructed(target, buckleId, buckle.Range, predicate: Ignored);
return _interactionSystem.InRangeUnobstructed(targetUid, buckleUid, buckleComp.Range, predicate: Ignored);
}
private void OnStrapCanDropOn(EntityUid uid, StrapComponent strap, ref CanDropTargetEvent args)
/// <summary>
/// Remove everything attached to the strap
/// </summary>
private void StrapRemoveAll(StrapComponent strapComp)
{
args.CanDrop = StrapCanDragDropOn(uid, args.User, uid, args.Dragged, strap);
args.Handled = true;
foreach (var entity in strapComp.BuckledEntities.ToArray())
{
TryUnbuckle(entity, entity, true);
}
strapComp.BuckledEntities.Clear();
strapComp.OccupiedSize = 0;
Dirty(strapComp);
}
private bool StrapHasSpace(EntityUid strapUid, BuckleComponent buckleComp, StrapComponent? strapComp = null)
{
if (!Resolve(strapUid, ref strapComp, false))
return false;
return strapComp.OccupiedSize + buckleComp.Size <= strapComp.Size;
}
/// <summary>
/// Try to add an entity to the strap
/// </summary>
private bool StrapTryAdd(EntityUid strapUid, EntityUid buckleUid, BuckleComponent buckleComp, bool force = false, StrapComponent? strapComp = null)
{
if (!Resolve(strapUid, ref strapComp, false) ||
!strapComp.Enabled)
return false;
if (!force && !StrapHasSpace(strapUid, buckleComp, strapComp))
return false;
if (!strapComp.BuckledEntities.Add(buckleUid))
return false;
strapComp.OccupiedSize += buckleComp.Size;
AppearanceSystem.SetData(buckleUid, StrapVisuals.RotationAngle, strapComp.Rotation);
AppearanceSystem.SetData(strapUid, StrapVisuals.State, true);
Dirty(strapComp);
return true;
}
/// <summary>
/// Sets the enabled field in the strap component to a value
/// </summary>
public void StrapSetEnabled(EntityUid strapUid, bool enabled, StrapComponent? strapComp = null)
{
if (!Resolve(strapUid, ref strapComp, false) ||
strapComp.Enabled == enabled)
return;
strapComp.Enabled = enabled;
if (!enabled)
StrapRemoveAll(strapComp);
}
}

View File

@@ -1,19 +1,87 @@
using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
using Content.Shared.Alert;
using Content.Shared.Buckle.Components;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Content.Shared.Pulling;
using Content.Shared.Standing;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Timing;
namespace Content.Shared.Buckle;
public abstract partial class SharedBuckleSystem : EntitySystem
{
[Dependency] protected readonly IGameTiming GameTiming = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
[Dependency] private readonly StandingStateSystem _standing = default!;
[Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
[Dependency] protected readonly ActionBlockerSystem ActionBlockerSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedPullingSystem _pullingSystem = default!;
[Dependency] private readonly StandingStateSystem _standingSystem = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
UpdatesAfter.Add(typeof(SharedInteractionSystem));
UpdatesAfter.Add(typeof(SharedInputSystem));
InitializeBuckle();
InitializeStrap();
}
/// <summary>
/// Reattaches this entity to the strap, modifying its position and rotation.
/// </summary>
/// <param name="buckleUid">The entity to reattach.</param>
/// <param name="strapUid">The entity to reattach the buckleUid entity to.</param>
private void ReAttach(
EntityUid buckleUid,
EntityUid strapUid,
BuckleComponent? buckleComp = null,
StrapComponent? strapComp = null)
{
if (!Resolve(strapUid, ref strapComp, false)
|| !Resolve(buckleUid, ref buckleComp, false))
return;
var buckleTransform = Transform(buckleUid);
buckleTransform.Coordinates = new EntityCoordinates(strapUid, strapComp.BuckleOffset);
// Buckle subscribes to move for <reasons> so this might fail.
// TODO: Make buckle not do that.
if (buckleTransform.ParentUid != strapUid)
return;
_transformSystem.SetLocalRotation(buckleUid, Angle.Zero, buckleTransform);
switch (strapComp.Position)
{
case StrapPosition.None:
break;
case StrapPosition.Stand:
_standingSystem.Stand(buckleUid);
break;
case StrapPosition.Down:
_standingSystem.Down(buckleUid, false, false);
break;
}
}
}