2023-08-04 22:24:20 +00:00
using System.Diagnostics.CodeAnalysis ;
2023-07-08 14:08:32 +10:00
using System.Numerics ;
2023-05-01 03:04:23 -04:00
using Content.Shared.Alert ;
using Content.Shared.Buckle.Components ;
2024-08-09 08:43:02 -07:00
using Content.Shared.Cuffs.Components ;
2023-05-01 03:04:23 -04:00
using Content.Shared.Database ;
2024-08-09 08:43:02 -07:00
using Content.Shared.DoAfter ;
2023-05-01 03:04:23 -04:00
using Content.Shared.Hands.Components ;
using Content.Shared.IdentityManagement ;
2022-11-18 22:08:28 +01:00
using Content.Shared.Movement.Events ;
2024-06-20 03:14:18 +12:00
using Content.Shared.Movement.Pulling.Events ;
2023-05-01 03:04:23 -04:00
using Content.Shared.Popups ;
2024-06-20 03:14:18 +12:00
using Content.Shared.Pulling.Events ;
2022-11-18 22:08:28 +01:00
using Content.Shared.Standing ;
2023-05-01 03:04:23 -04:00
using Content.Shared.Storage.Components ;
using Content.Shared.Stunnable ;
2022-11-18 22:08:28 +01:00
using Content.Shared.Throwing ;
2024-06-03 14:40:03 -07:00
using Content.Shared.Whitelist ;
2024-06-20 03:14:18 +12:00
using Robust.Shared.Containers ;
using Robust.Shared.Map ;
2023-09-16 07:15:05 +03:00
using Robust.Shared.Physics.Components ;
2022-11-18 22:08:28 +01:00
using Robust.Shared.Physics.Events ;
2024-06-20 03:14:18 +12:00
using Robust.Shared.Prototypes ;
2023-05-01 03:04:23 -04:00
using Robust.Shared.Utility ;
2022-11-18 22:08:28 +01:00
namespace Content.Shared.Buckle ;
public abstract partial class SharedBuckleSystem
{
2024-06-20 03:14:18 +12:00
public static ProtoId < AlertCategoryPrototype > BuckledAlertCategory = "Buckled" ;
2024-06-03 14:40:03 -07:00
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default ! ;
2022-11-18 22:08:28 +01:00
private void InitializeBuckle ( )
{
2023-05-01 03:04:23 -04:00
SubscribeLocalEvent < BuckleComponent , ComponentShutdown > ( OnBuckleComponentShutdown ) ;
SubscribeLocalEvent < BuckleComponent , MoveEvent > ( OnBuckleMove ) ;
2024-06-20 03:14:18 +12:00
SubscribeLocalEvent < BuckleComponent , EntParentChangedMessage > ( OnParentChanged ) ;
SubscribeLocalEvent < BuckleComponent , EntGotInsertedIntoContainerMessage > ( OnInserted ) ;
SubscribeLocalEvent < BuckleComponent , StartPullAttemptEvent > ( OnPullAttempt ) ;
SubscribeLocalEvent < BuckleComponent , BeingPulledAttemptEvent > ( OnBeingPulledAttempt ) ;
SubscribeLocalEvent < BuckleComponent , PullStartedMessage > ( OnPullStarted ) ;
2024-08-07 01:15:35 -04:00
SubscribeLocalEvent < BuckleComponent , UnbuckleAlertEvent > ( OnUnbuckleAlert ) ;
2024-06-20 03:14:18 +12:00
2023-05-01 03:04:23 -04:00
SubscribeLocalEvent < BuckleComponent , InsertIntoEntityStorageAttemptEvent > ( OnBuckleInsertIntoEntityStorageAttempt ) ;
SubscribeLocalEvent < BuckleComponent , PreventCollideEvent > ( OnBucklePreventCollide ) ;
SubscribeLocalEvent < BuckleComponent , DownAttemptEvent > ( OnBuckleDownAttempt ) ;
SubscribeLocalEvent < BuckleComponent , StandAttemptEvent > ( OnBuckleStandAttempt ) ;
SubscribeLocalEvent < BuckleComponent , ThrowPushbackAttemptEvent > ( OnBuckleThrowPushbackAttempt ) ;
SubscribeLocalEvent < BuckleComponent , UpdateCanMoveEvent > ( OnBuckleUpdateCanMove ) ;
2022-11-18 22:08:28 +01:00
2024-08-09 08:43:02 -07:00
SubscribeLocalEvent < BuckleComponent , BuckleDoAfterEvent > ( OnBuckleDoafter ) ;
SubscribeLocalEvent < BuckleComponent , DoAfterAttemptEvent < BuckleDoAfterEvent > > ( ( uid , comp , ev ) = >
{
BuckleDoafterEarly ( ( uid , comp ) , ev . Event , ev ) ;
} ) ;
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
private void OnBuckleComponentShutdown ( Entity < BuckleComponent > ent , ref ComponentShutdown args )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
Unbuckle ( ent ! , null ) ;
}
#region Pulling
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
private void OnPullAttempt ( Entity < BuckleComponent > ent , ref StartPullAttemptEvent args )
{
// Prevent people pulling the chair they're on, etc.
if ( ent . Comp . BuckledTo = = args . Pulled & & ! ent . Comp . PullStrap )
args . Cancel ( ) ;
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
private void OnBeingPulledAttempt ( Entity < BuckleComponent > ent , ref BeingPulledAttemptEvent args )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
if ( args . Cancelled | | ! ent . Comp . Buckled )
2023-05-01 03:04:23 -04:00
return ;
2024-06-20 03:14:18 +12:00
if ( ! CanUnbuckle ( ent ! , args . Puller , false ) )
args . Cancel ( ) ;
}
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
private void OnPullStarted ( Entity < BuckleComponent > ent , ref PullStartedMessage args )
{
Unbuckle ( ent ! , args . PullerUid ) ;
}
2024-08-07 01:15:35 -04:00
private void OnUnbuckleAlert ( Entity < BuckleComponent > ent , ref UnbuckleAlertEvent args )
{
if ( args . Handled )
return ;
args . Handled = TryUnbuckle ( ent , ent , ent ) ;
}
2024-06-20 03:14:18 +12:00
#endregion
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
#region Transform
private void OnParentChanged ( Entity < BuckleComponent > ent , ref EntParentChangedMessage args )
{
BuckleTransformCheck ( ent , args . Transform ) ;
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
private void OnInserted ( Entity < BuckleComponent > ent , ref EntGotInsertedIntoContainerMessage args )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
BuckleTransformCheck ( ent , Transform ( ent ) ) ;
}
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
private void OnBuckleMove ( Entity < BuckleComponent > ent , ref MoveEvent ev )
{
BuckleTransformCheck ( ent , ev . Component ) ;
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
/// <summary>
/// Check if the entity should get unbuckled as a result of transform or container changes.
/// </summary>
private void BuckleTransformCheck ( Entity < BuckleComponent > buckle , TransformComponent xform )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
if ( _gameTiming . ApplyingState )
2023-05-01 03:04:23 -04:00
return ;
2024-06-20 03:14:18 +12:00
if ( buckle . Comp . BuckledTo is not { } strapUid )
return ;
if ( ! TryComp < StrapComponent > ( strapUid , out var strapComp ) )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
Log . Error ( $"Encountered buckle entity {ToPrettyString(buckle)} without a valid strap entity {ToPrettyString(strapUid)}" ) ;
SetBuckledTo ( buckle , null ) ;
return ;
}
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
if ( xform . ParentUid ! = strapUid | | _container . IsEntityInContainer ( buckle ) )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
Unbuckle ( buckle , ( strapUid , strapComp ) , null ) ;
return ;
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
var delta = ( xform . LocalPosition - strapComp . BuckleOffset ) . LengthSquared ( ) ;
if ( delta > 1e-5 )
Unbuckle ( buckle , ( strapUid , strapComp ) , null ) ;
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
#endregion
2023-05-01 03:04:23 -04:00
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 )
2022-11-18 22:08:28 +01:00
{
2024-06-20 03:14:18 +12:00
if ( args . OtherEntity = = component . BuckledTo & & component . DontCollide )
2022-11-18 22:08:28 +01:00
args . Cancelled = true ;
}
2023-05-01 03:04:23 -04:00
private void OnBuckleDownAttempt ( EntityUid uid , BuckleComponent component , DownAttemptEvent args )
2022-11-18 22:08:28 +01:00
{
if ( component . Buckled )
args . Cancel ( ) ;
}
2023-05-01 03:04:23 -04:00
private void OnBuckleStandAttempt ( EntityUid uid , BuckleComponent component , StandAttemptEvent args )
2022-11-18 22:08:28 +01:00
{
if ( component . Buckled )
args . Cancel ( ) ;
}
2023-05-01 03:04:23 -04:00
private void OnBuckleThrowPushbackAttempt ( EntityUid uid , BuckleComponent component , ThrowPushbackAttemptEvent args )
2022-11-18 22:08:28 +01:00
{
if ( component . Buckled )
args . Cancel ( ) ;
}
2023-05-01 03:04:23 -04:00
private void OnBuckleUpdateCanMove ( EntityUid uid , BuckleComponent component , UpdateCanMoveEvent args )
2022-11-18 22:08:28 +01:00
{
2024-06-20 03:14:18 +12:00
if ( component . Buckled )
2022-11-18 22:08:28 +01:00
args . Cancel ( ) ;
}
2022-12-23 23:55:31 -05:00
public bool IsBuckled ( EntityUid uid , BuckleComponent ? component = null )
2022-11-18 22:08:28 +01:00
{
return Resolve ( uid , ref component , false ) & & component . Buckled ;
}
2024-06-20 03:14:18 +12:00
protected void SetBuckledTo ( Entity < BuckleComponent > buckle , Entity < StrapComponent ? > ? strap )
2022-11-18 22:08:28 +01:00
{
2024-06-20 03:14:18 +12:00
if ( TryComp ( buckle . Comp . BuckledTo , out StrapComponent ? old ) )
2024-09-22 18:21:40 +10:00
{
2024-06-20 03:14:18 +12:00
old . BuckledEntities . Remove ( buckle ) ;
2024-09-22 18:21:40 +10:00
Dirty ( buckle . Comp . BuckledTo . Value , old ) ;
}
2022-11-18 22:08:28 +01:00
2024-06-20 03:14:18 +12:00
if ( strap is { } strapEnt & & Resolve ( strapEnt . Owner , ref strapEnt . Comp ) )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
strapEnt . Comp . BuckledEntities . Add ( buckle ) ;
2024-09-22 18:21:40 +10:00
Dirty ( strapEnt ) ;
2025-09-05 02:45:48 -07:00
_alerts . ShowAlert ( buckle . Owner , strapEnt . Comp . BuckledAlertType ) ;
2023-05-01 03:04:23 -04:00
}
else
{
2025-09-05 02:45:48 -07:00
_alerts . ClearAlertCategory ( buckle . Owner , BuckledAlertCategory ) ;
2023-05-01 03:04:23 -04:00
}
2022-11-18 22:08:28 +01:00
2024-06-20 03:14:18 +12:00
buckle . Comp . BuckledTo = strap ;
buckle . Comp . BuckleTime = _gameTiming . CurTime ;
ActionBlocker . UpdateCanMove ( buckle ) ;
Appearance . SetData ( buckle , StrapVisuals . State , buckle . Comp . Buckled ) ;
Dirty ( buckle ) ;
2023-05-01 03:04:23 -04:00
}
2022-11-18 22:08:28 +01:00
2023-05-01 03:04:23 -04:00
/// <summary>
/// Checks whether or not buckling is possible
/// </summary>
/// <param name="buckleUid"> Uid of the owner of BuckleComponent </param>
2024-06-20 03:14:18 +12:00
/// <param name="user">
/// Uid of a third party entity,
/// i.e, the uid of someone else you are dragging to a chair.
/// Can equal buckleUid sometimes
2023-05-01 03:04:23 -04:00
/// </param>
/// <param name="strapUid"> Uid of the owner of strap component </param>
2024-06-20 03:14:18 +12:00
/// <param name="strapComp"></param>
/// <param name="buckleComp"></param>
private bool CanBuckle ( EntityUid buckleUid ,
EntityUid ? user ,
2023-05-01 03:04:23 -04:00
EntityUid strapUid ,
2024-06-20 03:14:18 +12:00
bool popup ,
2023-05-01 03:04:23 -04:00
[NotNullWhen(true)] out StrapComponent ? strapComp ,
2024-06-20 03:14:18 +12:00
BuckleComponent buckleComp )
2023-05-01 03:04:23 -04:00
{
strapComp = null ;
2024-06-20 03:14:18 +12:00
if ( ! Resolve ( strapUid , ref strapComp , false ) )
2023-05-01 03:04:23 -04:00
return false ;
// Does it pass the Whitelist
2024-06-03 14:40:03 -07:00
if ( _whitelistSystem . IsWhitelistFail ( strapComp . Whitelist , buckleUid ) | |
_whitelistSystem . IsBlacklistPass ( strapComp . Blacklist , buckleUid ) )
2023-05-01 03:04:23 -04:00
{
2024-09-15 05:08:54 +03:00
if ( popup )
_popup . PopupClient ( Loc . GetString ( "buckle-component-cannot-fit-message" ) , user , PopupType . Medium ) ;
2023-05-01 03:04:23 -04:00
return false ;
}
2024-06-20 03:14:18 +12:00
if ( ! _interaction . InRangeUnobstructed ( buckleUid ,
strapUid ,
buckleComp . Range ,
predicate : entity = > entity = = buckleUid | | entity = = user | | entity = = strapUid ,
2023-05-01 03:04:23 -04:00
popup : true ) )
2022-11-18 22:08:28 +01:00
{
2023-05-01 03:04:23 -04:00
return false ;
2022-11-18 22:08:28 +01:00
}
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
if ( ! _container . IsInSameOrNoContainer ( ( buckleUid , null , null ) , ( strapUid , null , null ) ) )
return false ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
if ( user ! = null & & ! HasComp < HandsComponent > ( user ) )
2023-05-01 03:04:23 -04:00
{
2024-09-15 05:08:54 +03:00
if ( popup )
_popup . PopupClient ( Loc . GetString ( "buckle-component-no-hands-message" ) , user ) ;
2023-05-01 03:04:23 -04:00
return false ;
}
2024-09-19 07:08:33 -07:00
if ( buckleComp . Buckled & & ! TryUnbuckle ( buckleUid , user , buckleComp ) )
2023-05-01 03:04:23 -04:00
{
2024-09-15 05:08:54 +03:00
if ( popup )
{
var message = Loc . GetString ( buckleUid = = user
2023-05-01 03:04:23 -04:00
? "buckle-component-already-buckled-message"
: "buckle-component-other-already-buckled-message" ,
( "owner" , Identity . Entity ( buckleUid , EntityManager ) ) ) ;
2024-09-15 05:08:54 +03:00
_popup . PopupClient ( message , user ) ;
}
2023-05-01 03:04:23 -04:00
return false ;
}
2024-06-20 03:14:18 +12:00
// Check whether someone is attempting to buckle something to their own child
2023-05-01 03:04:23 -04:00
var parent = Transform ( strapUid ) . ParentUid ;
while ( parent . IsValid ( ) )
{
2024-06-20 03:14:18 +12:00
if ( parent ! = buckleUid )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
parent = Transform ( parent ) . ParentUid ;
continue ;
}
2023-05-01 03:04:23 -04:00
2024-09-15 05:08:54 +03:00
if ( popup )
{
var message = Loc . GetString ( buckleUid = = user
2024-06-20 03:14:18 +12:00
? "buckle-component-cannot-buckle-message"
: "buckle-component-other-cannot-buckle-message" ,
( "owner" , Identity . Entity ( buckleUid , EntityManager ) ) ) ;
2024-09-15 05:08:54 +03:00
_popup . PopupClient ( message , user ) ;
}
2024-06-20 03:14:18 +12:00
return false ;
2023-05-01 03:04:23 -04:00
}
if ( ! StrapHasSpace ( strapUid , buckleComp , strapComp ) )
{
2024-09-15 05:08:54 +03:00
if ( popup )
{
var message = Loc . GetString ( buckleUid = = user
? "buckle-component-cannot-buckle-message"
: "buckle-component-other-cannot-buckle-message" ,
2024-06-20 03:14:18 +12:00
( "owner" , Identity . Entity ( buckleUid , EntityManager ) ) ) ;
2024-09-15 05:08:54 +03:00
_popup . PopupClient ( message , user ) ;
}
2023-05-01 03:04:23 -04:00
return false ;
}
2024-06-20 03:14:18 +12:00
var buckleAttempt = new BuckleAttemptEvent ( ( strapUid , strapComp ) , ( buckleUid , buckleComp ) , user , popup ) ;
RaiseLocalEvent ( buckleUid , ref buckleAttempt ) ;
if ( buckleAttempt . Cancelled )
return false ;
var strapAttempt = new StrapAttemptEvent ( ( strapUid , strapComp ) , ( buckleUid , buckleComp ) , user , popup ) ;
RaiseLocalEvent ( strapUid , ref strapAttempt ) ;
if ( strapAttempt . Cancelled )
2023-06-28 23:19:56 -04:00
return false ;
2023-05-01 03:04:23 -04:00
return true ;
}
/// <summary>
/// Attempts to buckle an entity to a strap
/// </summary>
2024-06-20 03:14:18 +12:00
/// <param name="buckle"> Uid of the owner of BuckleComponent </param>
/// <param name="user">
2023-05-01 03:04:23 -04:00
/// Uid of a third party entity,
/// i.e, the uid of someone else you are dragging to a chair.
/// Can equal buckleUid sometimes
/// </param>
2024-06-20 03:14:18 +12:00
/// <param name="strap"> Uid of the owner of strap component </param>
public bool TryBuckle ( EntityUid buckle , EntityUid ? user , EntityUid strap , BuckleComponent ? buckleComp = null , bool popup = true )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
if ( ! Resolve ( buckle , ref buckleComp , false ) )
2023-05-01 03:04:23 -04:00
return false ;
2024-06-20 03:14:18 +12:00
if ( ! CanBuckle ( buckle , user , strap , popup , out var strapComp , buckleComp ) )
2023-09-16 07:15:05 +03:00
return false ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
Buckle ( ( buckle , buckleComp ) , ( strap , strapComp ) , user ) ;
return true ;
}
2023-09-16 07:15:05 +03:00
2024-06-20 03:14:18 +12:00
private void Buckle ( Entity < BuckleComponent > buckle , Entity < StrapComponent > strap , EntityUid ? user )
{
if ( user = = buckle . Owner )
_adminLogger . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(user):player} buckled themselves to {ToPrettyString(strap)}" ) ;
else if ( user ! = null )
_adminLogger . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(user):player} buckled {ToPrettyString(buckle)} to {ToPrettyString(strap)}" ) ;
2023-09-16 07:15:05 +03:00
2024-06-20 03:14:18 +12:00
_audio . PlayPredicted ( strap . Comp . BuckleSound , strap , user ) ;
2023-12-26 18:32:25 -05:00
2024-06-20 03:14:18 +12:00
SetBuckledTo ( buckle , strap ! ) ;
Appearance . SetData ( strap , StrapVisuals . State , true ) ;
Appearance . SetData ( buckle , BuckleVisuals . Buckled , true ) ;
2023-09-16 07:15:05 +03:00
2024-06-20 03:14:18 +12:00
_rotationVisuals . SetHorizontalAngle ( buckle . Owner , strap . Comp . Rotation ) ;
2023-09-16 07:15:05 +03:00
2024-06-20 03:14:18 +12:00
var xform = Transform ( buckle ) ;
var coords = new EntityCoordinates ( strap , strap . Comp . BuckleOffset ) ;
_transform . SetCoordinates ( buckle , xform , coords , rotation : Angle . Zero ) ;
2023-09-16 07:15:05 +03:00
2024-06-20 03:14:18 +12:00
_joints . SetRelay ( buckle , strap ) ;
2023-09-16 07:15:05 +03:00
2024-06-20 03:14:18 +12:00
switch ( strap . Comp . Position )
2023-09-16 07:15:05 +03:00
{
2024-06-20 03:14:18 +12:00
case StrapPosition . Stand :
2024-07-07 02:20:53 -04:00
_standing . Stand ( buckle , force : true ) ;
2024-06-20 03:14:18 +12:00
break ;
case StrapPosition . Down :
2024-07-07 02:20:53 -04:00
_standing . Down ( buckle , false , false , force : true ) ;
2024-06-20 03:14:18 +12:00
break ;
2023-09-16 07:15:05 +03:00
}
2024-06-20 03:14:18 +12:00
var ev = new StrappedEvent ( strap , buckle ) ;
RaiseLocalEvent ( strap , ref ev ) ;
2023-09-16 07:15:05 +03:00
2024-06-20 03:14:18 +12:00
var gotEv = new BuckledEvent ( strap , buckle ) ;
RaiseLocalEvent ( buckle , ref gotEv ) ;
if ( TryComp < PhysicsComponent > ( buckle , out var physics ) )
_physics . ResetDynamics ( buckle , physics ) ;
DebugTools . AssertEqual ( xform . ParentUid , strap . Owner ) ;
2023-05-01 03:04:23 -04:00
}
/// <summary>
/// Tries to unbuckle the Owner of this component from its current strap.
/// </summary>
/// <param name="buckleUid">The entity to unbuckle.</param>
2024-06-20 03:14:18 +12:00
/// <param name="user">The entity doing the unbuckling.</param>
2023-05-01 03:04:23 -04:00
/// <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>
2024-06-20 03:14:18 +12:00
public bool TryUnbuckle ( EntityUid buckleUid ,
EntityUid ? user ,
BuckleComponent ? buckleComp = null ,
bool popup = true )
{
return TryUnbuckle ( ( buckleUid , buckleComp ) , user , popup ) ;
}
public bool TryUnbuckle ( Entity < BuckleComponent ? > buckle , EntityUid ? user , bool popup )
2023-05-01 03:04:23 -04:00
{
2025-03-12 19:48:45 +01:00
if ( ! Resolve ( buckle . Owner , ref buckle . Comp , false ) )
2023-05-01 03:04:23 -04:00
return false ;
2024-06-20 03:14:18 +12:00
if ( ! CanUnbuckle ( buckle , user , popup , out var strap ) )
return false ;
2023-06-28 23:19:56 -04:00
2024-06-20 03:14:18 +12:00
Unbuckle ( buckle ! , strap , user ) ;
return true ;
}
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
public void Unbuckle ( Entity < BuckleComponent ? > buckle , EntityUid ? user )
{
if ( ! Resolve ( buckle . Owner , ref buckle . Comp , false ) )
return ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
if ( buckle . Comp . BuckledTo is not { } strap )
return ;
2023-11-16 03:39:04 +01:00
2024-06-20 03:14:18 +12:00
if ( ! TryComp ( strap , out StrapComponent ? strapComp ) )
{
Log . Error ( $"Encountered buckle {ToPrettyString(buckle.Owner)} with invalid strap entity {ToPrettyString(strap)}" ) ;
SetBuckledTo ( buckle ! , null ) ;
return ;
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
Unbuckle ( buckle ! , ( strap , strapComp ) , user ) ;
}
private void Unbuckle ( Entity < BuckleComponent > buckle , Entity < StrapComponent > strap , EntityUid ? user )
{
if ( user = = buckle . Owner )
2025-08-15 01:45:41 -07:00
_adminLogger . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(user):user} unbuckled themselves from {ToPrettyString(strap):strap}" ) ;
2024-06-20 03:14:18 +12:00
else if ( user ! = null )
2025-08-15 01:45:41 -07:00
_adminLogger . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(user):user} unbuckled {ToPrettyString(buckle):target} from {ToPrettyString(strap):strap}" ) ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
_audio . PlayPredicted ( strap . Comp . UnbuckleSound , strap , user ) ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
SetBuckledTo ( buckle , null ) ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
var buckleXform = Transform ( buckle ) ;
var oldBuckledXform = Transform ( strap ) ;
2023-05-01 03:04:23 -04:00
2025-05-07 19:25:17 -04:00
if ( buckleXform . ParentUid = = strap . Owner & & ! Terminating ( oldBuckledXform . ParentUid ) )
2023-05-01 03:04:23 -04:00
{
2024-09-22 18:21:40 +10:00
_transform . PlaceNextTo ( ( buckle , buckleXform ) , ( strap . Owner , oldBuckledXform ) ) ;
buckleXform . ActivelyLerping = false ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
var oldBuckledToWorldRot = _transform . GetWorldRotation ( strap ) ;
2024-09-22 18:21:40 +10:00
_transform . SetWorldRotationNoLerp ( ( buckle , buckleXform ) , oldBuckledToWorldRot ) ;
2023-05-01 03:04:23 -04:00
2024-09-22 18:21:40 +10:00
// TODO: This is doing 4 moveevents this is why I left the warning in, if you're going to remove it make it only do 1 moveevent.
if ( strap . Comp . BuckleOffset ! = Vector2 . Zero )
{
buckleXform . Coordinates = oldBuckledXform . Coordinates . Offset ( strap . Comp . BuckleOffset ) ;
}
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
_rotationVisuals . ResetHorizontalAngle ( buckle . Owner ) ;
Appearance . SetData ( strap , StrapVisuals . State , strap . Comp . BuckledEntities . Count ! = 0 ) ;
Appearance . SetData ( buckle , BuckleVisuals . Buckled , false ) ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
if ( HasComp < KnockedDownComponent > ( buckle ) | | _mobState . IsIncapacitated ( buckle ) )
2024-07-07 02:20:53 -04:00
_standing . Down ( buckle , playSound : false ) ;
2023-05-01 03:04:23 -04:00
else
2024-06-20 03:14:18 +12:00
_standing . Stand ( buckle ) ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
_joints . RefreshRelay ( buckle ) ;
2023-11-27 22:12:34 +11:00
2024-06-20 03:14:18 +12:00
var buckleEv = new UnbuckledEvent ( strap , buckle ) ;
RaiseLocalEvent ( buckle , ref buckleEv ) ;
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
var strapEv = new UnstrappedEvent ( strap , buckle ) ;
RaiseLocalEvent ( strap , ref strapEv ) ;
}
2023-05-01 03:04:23 -04:00
2024-06-20 03:14:18 +12:00
public bool CanUnbuckle ( Entity < BuckleComponent ? > buckle , EntityUid user , bool popup )
{
return CanUnbuckle ( buckle , user , popup , out _ ) ;
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
private bool CanUnbuckle ( Entity < BuckleComponent ? > buckle , EntityUid ? user , bool popup , out Entity < StrapComponent > strap )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
strap = default ;
if ( ! Resolve ( buckle . Owner , ref buckle . Comp ) )
2023-05-01 03:04:23 -04:00
return false ;
2024-06-20 03:14:18 +12:00
if ( buckle . Comp . BuckledTo is not { } strapUid )
return false ;
if ( ! TryComp ( strapUid , out StrapComponent ? strapComp ) )
2023-05-01 03:04:23 -04:00
{
2024-06-20 03:14:18 +12:00
Log . Error ( $"Encountered buckle {ToPrettyString(buckle.Owner)} with invalid strap entity {ToPrettyString(strap)}" ) ;
SetBuckledTo ( buckle ! , null ) ;
return false ;
2023-05-01 03:04:23 -04:00
}
2024-06-20 03:14:18 +12:00
strap = ( strapUid , strapComp ) ;
if ( _gameTiming . CurTime < buckle . Comp . BuckleTime + buckle . Comp . Delay )
return false ;
2025-06-02 13:06:45 -04:00
if ( user ! = null )
{
if ( ! _interaction . InRangeUnobstructed ( user . Value , strap . Owner , buckle . Comp . Range , popup : popup ) )
return false ;
2025-06-06 12:11:52 -04:00
if ( user . Value ! = buckle . Owner & & ! ActionBlocker . CanComplexInteract ( user . Value ) )
2025-06-02 13:06:45 -04:00
return false ;
}
2024-06-20 03:14:18 +12:00
var unbuckleAttempt = new UnbuckleAttemptEvent ( strap , buckle ! , user , popup ) ;
RaiseLocalEvent ( buckle , ref unbuckleAttempt ) ;
if ( unbuckleAttempt . Cancelled )
return false ;
var unstrapAttempt = new UnstrapAttemptEvent ( strap , buckle ! , user , popup ) ;
RaiseLocalEvent ( strap , ref unstrapAttempt ) ;
return ! unstrapAttempt . Cancelled ;
2022-11-18 22:08:28 +01:00
}
2024-08-09 08:43:02 -07:00
/// <summary>
/// Once the do-after is complete, try to buckle target to chair/bed
/// </summary>
/// <param name="args.Target"> The person being put in the chair/bed</param>
/// <param name="args.User"> The person putting a person in a chair/bed</param>
/// <param name="args.Used"> The chair/bed </param>
private void OnBuckleDoafter ( Entity < BuckleComponent > entity , ref BuckleDoAfterEvent args )
{
if ( args . Cancelled | | args . Handled | | args . Target = = null | | args . Used = = null )
return ;
args . Handled = TryBuckle ( args . Target . Value , args . User , args . Used . Value , popup : false ) ;
}
/// <summary>
/// If the target being buckled to a chair/bed goes crit or is cuffed
/// Cancel the do-after time and try to buckle the target immediately
/// </summary>
/// <param name="args.Target"> The person being put in the chair/bed</param>
/// <param name="args.User"> The person putting a person in a chair/bed</param>
/// <param name="args.Used"> The chair/bed </param>
private void BuckleDoafterEarly ( Entity < BuckleComponent > entity , BuckleDoAfterEvent args , CancellableEntityEventArgs ev )
{
if ( args . Target = = null | | args . Used = = null )
return ;
if ( TryComp < CuffableComponent > ( args . Target , out var targetCuffableComp ) & & targetCuffableComp . CuffedHandCount > 0
| | _mobState . IsIncapacitated ( args . Target . Value ) )
{
ev . Cancel ( ) ;
TryBuckle ( args . Target . Value , args . User , args . Used . Value , popup : false ) ;
}
}
2022-11-18 22:08:28 +01:00
}