2023-03-13 19:34:26 -04:00
using System.Linq ;
2022-04-10 16:48:11 +12:00
using Content.Shared.ActionBlocker ;
2023-03-13 19:34:26 -04:00
using Content.Shared.Administration.Components ;
using Content.Shared.Administration.Logs ;
2023-01-21 02:48:19 +13:00
using Content.Shared.Alert ;
2023-06-28 23:19:56 -04:00
using Content.Shared.Buckle.Components ;
2021-06-17 00:37:05 +10:00
using Content.Shared.Cuffs.Components ;
2023-07-04 05:30:09 +08:00
using Content.Shared.Damage ;
2023-03-13 19:34:26 -04:00
using Content.Shared.Database ;
using Content.Shared.DoAfter ;
2023-08-01 19:02:54 +03:00
using Content.Shared.Effects ;
2023-02-14 00:29:34 +11:00
using Content.Shared.Hands ;
2023-01-21 02:48:19 +13:00
using Content.Shared.Hands.Components ;
2023-03-13 19:34:26 -04:00
using Content.Shared.Hands.EntitySystems ;
using Content.Shared.IdentityManagement ;
using Content.Shared.Interaction ;
using Content.Shared.Interaction.Components ;
2021-10-21 13:03:14 +11:00
using Content.Shared.Interaction.Events ;
using Content.Shared.Inventory.Events ;
using Content.Shared.Item ;
2023-03-13 19:34:26 -04:00
using Content.Shared.Mobs.Systems ;
2022-06-24 17:44:30 +10:00
using Content.Shared.Movement.Events ;
2022-04-10 16:48:11 +12:00
using Content.Shared.Physics.Pull ;
2023-03-13 19:34:26 -04:00
using Content.Shared.Popups ;
2021-06-17 00:37:05 +10:00
using Content.Shared.Pulling.Components ;
2022-06-15 21:10:03 -04:00
using Content.Shared.Pulling.Events ;
2023-01-21 02:48:19 +13:00
using Content.Shared.Rejuvenate ;
2023-03-13 19:34:26 -04:00
using Content.Shared.Stunnable ;
using Content.Shared.Verbs ;
using Content.Shared.Weapons.Melee.Events ;
2023-11-27 22:12:34 +11:00
using Robust.Shared.Audio.Systems ;
2023-05-25 02:13:10 +06:00
using Content.Shared.White.EndOfRoundStats.CuffedTime ;
2023-01-21 02:48:19 +13:00
using Robust.Shared.Containers ;
2023-03-13 19:34:26 -04:00
using Robust.Shared.Network ;
using Robust.Shared.Player ;
2023-04-03 13:13:48 +12:00
using Robust.Shared.Serialization ;
2023-05-25 02:13:10 +06:00
using Robust.Shared.Timing ;
2021-06-17 00:37:05 +10:00
namespace Content.Shared.Cuffs
{
2023-04-03 13:13:48 +12:00
// TODO remove all the IsServer() checks.
2023-08-22 18:14:33 -07:00
public abstract partial class SharedCuffableSystem : EntitySystem
2021-06-17 00:37:05 +10:00
{
2023-03-13 19:34:26 -04:00
[Dependency] private readonly IComponentFactory _componentFactory = default ! ;
[Dependency] private readonly INetManager _net = default ! ;
[Dependency] private readonly ISharedAdminLogManager _adminLog = default ! ;
2023-03-27 00:15:32 +11:00
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default ! ;
[Dependency] private readonly AlertsSystem _alerts = default ! ;
2023-08-11 03:44:52 +10:00
[Dependency] private readonly SharedColorFlashEffectSystem _color = default ! ;
2023-07-04 05:30:09 +08:00
[Dependency] private readonly DamageableSystem _damageSystem = default ! ;
2023-03-13 19:34:26 -04:00
[Dependency] private readonly MobStateSystem _mobState = default ! ;
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
[Dependency] private readonly SharedContainerSystem _container = default ! ;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default ! ;
[Dependency] private readonly SharedHandsSystem _hands = default ! ;
[Dependency] private readonly SharedHandVirtualItemSystem _handVirtualItem = default ! ;
[Dependency] private readonly SharedInteractionSystem _interaction = default ! ;
[Dependency] private readonly SharedPopupSystem _popup = default ! ;
[Dependency] private readonly SharedTransformSystem _transform = default ! ;
2023-05-25 02:13:10 +06:00
[Dependency] private readonly IGameTiming _gameTiming = default ! ; // Parkstation-EndOfRoundStats
2022-04-10 16:48:11 +12:00
2021-06-17 00:37:05 +10:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2023-03-13 19:34:26 -04:00
SubscribeLocalEvent < HandCountChangedEvent > ( OnHandCountChanged ) ;
SubscribeLocalEvent < UncuffAttemptEvent > ( OnUncuffAttempt ) ;
SubscribeLocalEvent < CuffableComponent , EntRemovedFromContainerMessage > ( OnCuffsRemovedFromContainer ) ;
SubscribeLocalEvent < CuffableComponent , EntInsertedIntoContainerMessage > ( OnCuffsInsertedIntoContainer ) ;
SubscribeLocalEvent < CuffableComponent , RejuvenateEvent > ( OnRejuvenate ) ;
SubscribeLocalEvent < CuffableComponent , ComponentInit > ( OnStartup ) ;
SubscribeLocalEvent < CuffableComponent , StopPullingEvent > ( HandleStopPull ) ;
SubscribeLocalEvent < CuffableComponent , UpdateCanMoveEvent > ( HandleMoveAttempt ) ;
SubscribeLocalEvent < CuffableComponent , IsEquippingAttemptEvent > ( OnEquipAttempt ) ;
SubscribeLocalEvent < CuffableComponent , IsUnequippingAttemptEvent > ( OnUnequipAttempt ) ;
SubscribeLocalEvent < CuffableComponent , BeingPulledAttemptEvent > ( OnBeingPulledAttempt ) ;
2023-06-28 23:19:56 -04:00
SubscribeLocalEvent < CuffableComponent , BuckleAttemptEvent > ( OnBuckleAttemptEvent ) ;
2023-03-13 19:34:26 -04:00
SubscribeLocalEvent < CuffableComponent , GetVerbsEvent < Verb > > ( AddUncuffVerb ) ;
2023-04-03 13:13:48 +12:00
SubscribeLocalEvent < CuffableComponent , UnCuffDoAfterEvent > ( OnCuffableDoAfter ) ;
2023-03-13 19:34:26 -04:00
SubscribeLocalEvent < CuffableComponent , PullStartedMessage > ( OnPull ) ;
SubscribeLocalEvent < CuffableComponent , PullStoppedMessage > ( OnPull ) ;
SubscribeLocalEvent < CuffableComponent , DropAttemptEvent > ( CheckAct ) ;
SubscribeLocalEvent < CuffableComponent , PickupAttemptEvent > ( CheckAct ) ;
SubscribeLocalEvent < CuffableComponent , AttackAttemptEvent > ( CheckAct ) ;
SubscribeLocalEvent < CuffableComponent , UseAttemptEvent > ( CheckAct ) ;
SubscribeLocalEvent < CuffableComponent , InteractionAttemptEvent > ( CheckAct ) ;
SubscribeLocalEvent < HandcuffComponent , AfterInteractEvent > ( OnCuffAfterInteract ) ;
SubscribeLocalEvent < HandcuffComponent , MeleeHitEvent > ( OnCuffMeleeHit ) ;
2023-04-03 13:13:48 +12:00
SubscribeLocalEvent < HandcuffComponent , AddCuffDoAfterEvent > ( OnAddCuffDoAfter ) ;
2023-10-16 01:31:03 -04:00
SubscribeLocalEvent < HandcuffComponent , VirtualItemDeletedEvent > ( OnCuffVirtualItemDeleted ) ;
2023-03-13 19:34:26 -04:00
}
private void OnUncuffAttempt ( ref UncuffAttemptEvent args )
{
if ( args . Cancelled )
{
return ;
}
if ( ! Exists ( args . User ) | | Deleted ( args . User ) )
{
// Should this even be possible?
args . Cancelled = true ;
return ;
}
// If the user is the target, special logic applies.
// This is because the CanInteract blocking of the cuffs prevents self-uncuff.
if ( args . User = = args . Target )
{
// This UncuffAttemptEvent check should probably be In MobStateSystem, not here?
if ( _mobState . IsIncapacitated ( args . User ) )
{
args . Cancelled = true ;
}
else
{
// TODO Find a way for cuffable to check ActionBlockerSystem.CanInteract() without blocking itself
}
}
else
{
// Check if the user can interact.
if ( ! _actionBlocker . CanInteract ( args . User , args . Target ) )
{
args . Cancelled = true ;
}
}
if ( args . Cancelled & & _net . IsServer )
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-cannot-interact-message" ) , args . Target , args . User ) ;
}
}
private void OnStartup ( EntityUid uid , CuffableComponent component , ComponentInit args )
{
component . Container = _container . EnsureContainer < Container > ( uid , _componentFactory . GetComponentName ( component . GetType ( ) ) ) ;
}
private void OnRejuvenate ( EntityUid uid , CuffableComponent component , RejuvenateEvent args )
2023-01-21 02:48:19 +13:00
{
2023-03-15 13:14:18 +13:00
_container . EmptyContainer ( component . Container , true ) ;
2023-01-21 02:48:19 +13:00
}
2023-03-13 19:34:26 -04:00
private void OnCuffsRemovedFromContainer ( EntityUid uid , CuffableComponent component , EntRemovedFromContainerMessage args )
{
2023-09-08 18:16:05 -07:00
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
if ( args . Container . ID ! = component . Container ? . ID )
2023-03-29 22:04:21 +11:00
return ;
_handVirtualItem . DeleteInHandsMatching ( uid , args . Entity ) ;
UpdateCuffState ( uid , component ) ;
2023-03-13 19:34:26 -04:00
}
private void OnCuffsInsertedIntoContainer ( EntityUid uid , CuffableComponent component , ContainerModifiedMessage args )
2023-01-21 02:48:19 +13:00
{
if ( args . Container = = component . Container )
UpdateCuffState ( uid , component ) ;
}
2023-03-13 19:34:26 -04:00
public void UpdateCuffState ( EntityUid uid , CuffableComponent component )
2023-01-21 02:48:19 +13:00
{
2023-04-07 11:21:12 -07:00
var canInteract = TryComp ( uid , out HandsComponent ? hands ) & & hands . Hands . Count > component . CuffedHandCount ;
2023-01-21 02:48:19 +13:00
if ( canInteract = = component . CanStillInteract )
return ;
component . CanStillInteract = canInteract ;
2023-10-16 01:31:03 -04:00
Dirty ( uid , component ) ;
2023-03-13 19:34:26 -04:00
_actionBlocker . UpdateCanMove ( uid ) ;
2023-01-21 02:48:19 +13:00
if ( component . CanStillInteract )
_alerts . ClearAlert ( uid , AlertType . Handcuffed ) ;
else
_alerts . ShowAlert ( uid , AlertType . Handcuffed ) ;
var ev = new CuffedStateChangeEvent ( ) ;
RaiseLocalEvent ( uid , ref ev ) ;
}
2022-06-15 21:10:03 -04:00
2023-03-13 19:34:26 -04:00
private void OnBeingPulledAttempt ( EntityUid uid , CuffableComponent component , BeingPulledAttemptEvent args )
2022-06-15 21:10:03 -04:00
{
if ( ! TryComp < SharedPullableComponent > ( uid , out var pullable ) )
return ;
if ( pullable . Puller ! = null & & ! component . CanStillInteract ) // If we are being pulled already and cuffed, we can't get pulled again.
args . Cancel ( ) ;
}
2023-03-13 19:34:26 -04:00
2023-06-28 23:19:56 -04:00
private void OnBuckleAttemptEvent ( EntityUid uid , CuffableComponent component , ref BuckleAttemptEvent args )
{
// if someone else is doing it, let it pass.
if ( args . UserEntity ! = uid )
return ;
if ( ! TryComp < HandsComponent > ( uid , out var hands ) | | component . CuffedHandCount ! = hands . Count )
return ;
args . Cancelled = true ;
var message = args . Buckling
? Loc . GetString ( "handcuff-component-cuff-interrupt-buckled-message" )
: Loc . GetString ( "handcuff-component-cuff-interrupt-unbuckled-message" ) ;
if ( _net . IsServer )
_popup . PopupEntity ( message , uid , args . UserEntity ) ;
}
2023-03-13 19:34:26 -04:00
private void OnPull ( EntityUid uid , CuffableComponent component , PullMessage args )
2022-04-10 16:48:11 +12:00
{
if ( ! component . CanStillInteract )
2023-03-13 19:34:26 -04:00
_actionBlocker . UpdateCanMove ( uid ) ;
2021-08-10 10:34:01 +10:00
}
2023-03-13 19:34:26 -04:00
private void HandleMoveAttempt ( EntityUid uid , CuffableComponent component , UpdateCanMoveEvent args )
2021-08-10 10:34:01 +10:00
{
2021-09-28 13:35:29 +02:00
if ( component . CanStillInteract | | ! EntityManager . TryGetComponent ( uid , out SharedPullableComponent ? pullable ) | | ! pullable . BeingPulled )
2021-08-10 10:34:01 +10:00
return ;
args . Cancel ( ) ;
2021-06-17 00:37:05 +10:00
}
2023-03-13 19:34:26 -04:00
private void HandleStopPull ( EntityUid uid , CuffableComponent component , StopPullingEvent args )
2021-06-17 00:37:05 +10:00
{
2023-03-13 19:34:26 -04:00
if ( args . User = = null | | ! Exists ( args . User . Value ) )
return ;
2021-06-17 00:37:05 +10:00
2023-03-13 19:34:26 -04:00
if ( args . User . Value = = uid & & ! component . CanStillInteract )
2021-06-17 00:37:05 +10:00
args . Cancel ( ) ;
2023-03-13 19:34:26 -04:00
}
private void AddUncuffVerb ( EntityUid uid , CuffableComponent component , GetVerbsEvent < Verb > args )
{
// Can the user access the cuffs, and is there even anything to uncuff?
if ( ! args . CanAccess | | component . CuffedHandCount = = 0 | | args . Hands = = null )
return ;
// We only check can interact if the user is not uncuffing themselves. As a result, the verb will show up
// when the user is incapacitated & trying to uncuff themselves, but TryUncuff() will still fail when
// attempted.
if ( args . User ! = args . Target & & ! args . CanInteract )
return ;
Verb verb = new ( )
{
Act = ( ) = > TryUncuff ( uid , args . User , cuffable : component ) ,
DoContactInteraction = true ,
Text = Loc . GetString ( "uncuff-verb-get-data-text" )
} ;
//TODO VERB ICON add uncuffing symbol? may re-use the alert symbol showing that you are currently cuffed?
args . Verbs . Add ( verb ) ;
}
2023-04-03 13:13:48 +12:00
private void OnCuffableDoAfter ( EntityUid uid , CuffableComponent component , UnCuffDoAfterEvent args )
2023-03-13 19:34:26 -04:00
{
if ( args . Args . Target is not { } target | | args . Args . Used is not { } used )
return ;
if ( args . Handled )
return ;
args . Handled = true ;
var user = args . Args . User ;
if ( ! args . Cancelled )
{
Uncuff ( target , user , used , component ) ;
}
else if ( _net . IsServer )
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-remove-cuffs-fail-message" ) , user , user ) ;
}
}
private void OnCuffAfterInteract ( EntityUid uid , HandcuffComponent component , AfterInteractEvent args )
{
2023-07-22 23:14:25 +01:00
if ( args . Target is not { Valid : true } target )
2023-03-13 19:34:26 -04:00
return ;
if ( ! args . CanReach )
{
if ( _net . IsServer )
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-too-far-away-error" ) , args . User , args . User ) ;
return ;
}
2023-06-02 02:48:42 +02:00
var result = TryCuffing ( args . User , target , uid , component ) ;
args . Handled = result ;
2023-03-13 19:34:26 -04:00
}
private void OnCuffMeleeHit ( EntityUid uid , HandcuffComponent component , MeleeHitEvent args )
{
if ( ! args . HitEntities . Any ( ) )
return ;
2023-03-17 07:13:04 +05:00
TryCuffing ( args . User , args . HitEntities . First ( ) , uid , component ) ;
2023-03-13 19:34:26 -04:00
args . Handled = true ;
}
2023-04-03 13:13:48 +12:00
private void OnAddCuffDoAfter ( EntityUid uid , HandcuffComponent component , AddCuffDoAfterEvent args )
2023-03-13 19:34:26 -04:00
{
var user = args . Args . User ;
2023-03-15 17:07:16 +13:00
if ( ! TryComp < CuffableComponent > ( args . Args . Target , out var cuffable ) )
2023-03-13 19:34:26 -04:00
return ;
2023-03-15 17:07:16 +13:00
var target = args . Args . Target . Value ;
2023-03-13 19:34:26 -04:00
if ( args . Handled )
return ;
args . Handled = true ;
if ( ! args . Cancelled & & TryAddNewCuffs ( target , user , uid , cuffable ) )
{
2023-04-03 13:13:48 +12:00
_audio . PlayPredicted ( component . EndCuffSound , uid , user ) ;
2023-03-13 19:34:26 -04:00
if ( ! _net . IsServer )
return ;
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-cuff-observer-success-message" ,
( "user" , Identity . Name ( user , EntityManager ) ) , ( "target" , Identity . Name ( target , EntityManager ) ) ) ,
target , Filter . Pvs ( target , entityManager : EntityManager )
. RemoveWhere ( e = > e . AttachedEntity = = target | | e . AttachedEntity = = user ) , true ) ;
if ( target = = user )
{
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-cuff-self-success-message" ) , user , user ) ;
_adminLog . Add ( LogType . Action , LogImpact . Medium ,
$"{ToPrettyString(user):player} has cuffed himself" ) ;
}
else
{
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-cuff-other-success-message" ,
( "otherName" , Identity . Name ( target , EntityManager , user ) ) ) , user , user ) ;
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-cuff-by-other-success-message" ,
( "otherName" , Identity . Name ( user , EntityManager , target ) ) ) , target , target ) ;
_adminLog . Add ( LogType . Action , LogImpact . Medium ,
$"{ToPrettyString(user):player} has cuffed {ToPrettyString(target):player}" ) ;
}
}
else
{
if ( ! _net . IsServer )
return ;
if ( target = = user )
{
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-cuff-interrupt-self-message" ) , user , user ) ;
}
else
{
2023-04-03 13:13:48 +12:00
// TODO Fix popup message wording
// This message assumes that the user being handcuffed is the one that caused the handcuff to fail.
2023-03-13 19:34:26 -04:00
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-cuff-interrupt-message" ,
( "targetName" , Identity . Name ( target , EntityManager , user ) ) ) , user , user ) ;
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-cuff-interrupt-other-message" ,
( "otherName" , Identity . Name ( user , EntityManager , target ) ) ) , target , target ) ;
}
}
2023-10-16 01:31:03 -04:00
}
2023-03-13 19:34:26 -04:00
2023-10-16 01:31:03 -04:00
private void OnCuffVirtualItemDeleted ( EntityUid uid , HandcuffComponent component , VirtualItemDeletedEvent args )
{
Uncuff ( args . User , null , uid , cuff : component ) ;
2023-03-13 19:34:26 -04:00
}
/// <summary>
/// Check the current amount of hands the owner has, and if there's less hands than active cuffs we remove some cuffs.
/// </summary>
private void OnHandCountChanged ( HandCountChangedEvent message )
{
var owner = message . Sender ;
if ( ! TryComp ( owner , out CuffableComponent ? cuffable ) | |
! cuffable . Initialized )
{
return ;
}
var dirty = false ;
2023-04-07 11:21:12 -07:00
var handCount = CompOrNull < HandsComponent > ( owner ) ? . Count ? ? 0 ;
2023-03-13 19:34:26 -04:00
while ( cuffable . CuffedHandCount > handCount & & cuffable . CuffedHandCount > 0 )
{
dirty = true ;
var container = cuffable . Container ;
var entity = container . ContainedEntities [ ^ 1 ] ;
2023-12-27 21:30:03 -08:00
_container . Remove ( entity , container ) ;
2023-03-13 19:34:26 -04:00
_transform . SetWorldPosition ( entity , _transform . GetWorldPosition ( owner ) ) ;
}
if ( dirty )
{
UpdateCuffState ( owner , cuffable ) ;
}
}
/// <summary>
/// Adds virtual cuff items to the user's hands.
/// </summary>
private void UpdateHeldItems ( EntityUid uid , EntityUid handcuff , CuffableComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return ;
// TODO we probably don't just want to use the generic virtual-item entity, and instead
// want to add our own item, so that use-in-hand triggers an uncuff attempt and the like.
2023-04-07 11:21:12 -07:00
if ( ! TryComp < HandsComponent > ( uid , out var handsComponent ) )
2023-03-13 19:34:26 -04:00
return ;
var freeHands = 0 ;
foreach ( var hand in _hands . EnumerateHands ( uid , handsComponent ) )
{
if ( hand . HeldEntity = = null )
{
freeHands + + ;
continue ;
}
// Is this entity removable? (it might be an existing handcuff blocker)
if ( HasComp < UnremoveableComponent > ( hand . HeldEntity ) )
continue ;
_hands . DoDrop ( uid , hand , true , handsComponent ) ;
freeHands + + ;
if ( freeHands = = 2 )
break ;
}
if ( _handVirtualItem . TrySpawnVirtualItemInHand ( handcuff , uid , out var virtItem1 ) )
EnsureComp < UnremoveableComponent > ( virtItem1 . Value ) ;
if ( _handVirtualItem . TrySpawnVirtualItemInHand ( handcuff , uid , out var virtItem2 ) )
EnsureComp < UnremoveableComponent > ( virtItem2 . Value ) ;
}
/// <summary>
/// Add a set of cuffs to an existing CuffedComponent.
/// </summary>
public bool TryAddNewCuffs ( EntityUid target , EntityUid user , EntityUid handcuff , CuffableComponent ? component = null , HandcuffComponent ? cuff = null )
{
if ( ! Resolve ( target , ref component ) | | ! Resolve ( handcuff , ref cuff ) )
return false ;
if ( ! _interaction . InRangeUnobstructed ( handcuff , target ) )
return false ;
// Success!
_hands . TryDrop ( user , handcuff ) ;
2023-12-27 21:30:03 -08:00
_container . Insert ( handcuff , component . Container ) ;
2023-03-13 19:34:26 -04:00
UpdateHeldItems ( target , handcuff , component ) ;
2023-05-25 02:13:10 +06:00
if ( _net . IsServer )
component . CuffedTime = _gameTiming . CurTime ;
2023-03-13 19:34:26 -04:00
return true ;
}
2023-06-02 02:48:42 +02:00
/// <returns>False if the target entity isn't cuffable.</returns>
public bool TryCuffing ( EntityUid user , EntityUid target , EntityUid handcuff , HandcuffComponent ? handcuffComponent = null , CuffableComponent ? cuffable = null )
2023-03-13 19:34:26 -04:00
{
if ( ! Resolve ( handcuff , ref handcuffComponent ) | | ! Resolve ( target , ref cuffable , false ) )
2023-06-02 02:48:42 +02:00
return false ;
2023-03-13 19:34:26 -04:00
2023-09-20 10:12:48 +10:00
if ( ! TryComp < HandsComponent > ( target , out var hands ) )
2023-03-13 19:34:26 -04:00
{
if ( _net . IsServer )
{
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-target-has-no-hands-error" ,
( "targetName" , Identity . Name ( target , EntityManager , user ) ) ) , user , user ) ;
}
2023-06-02 02:48:42 +02:00
return true ;
2023-03-13 19:34:26 -04:00
}
if ( cuffable . CuffedHandCount > = hands . Count )
{
if ( _net . IsServer )
{
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-target-has-no-free-hands-error" ,
( "targetName" , Identity . Name ( target , EntityManager , user ) ) ) , user , user ) ;
}
2023-06-02 02:48:42 +02:00
return true ;
2023-03-13 19:34:26 -04:00
}
2023-04-03 13:13:48 +12:00
var cuffTime = handcuffComponent . CuffTime ;
if ( HasComp < StunnedComponent > ( target ) )
cuffTime = MathF . Max ( 0.1f , cuffTime - handcuffComponent . StunBonus ) ;
if ( HasComp < DisarmProneComponent > ( target ) )
cuffTime = 0.0f ; // cuff them instantly.
2023-09-11 09:42:41 +10:00
var doAfterEventArgs = new DoAfterArgs ( EntityManager , user , cuffTime , new AddCuffDoAfterEvent ( ) , handcuff , target , handcuff )
2023-04-03 13:13:48 +12:00
{
BreakOnTargetMove = true ,
BreakOnUserMove = true ,
BreakOnDamage = true ,
NeedHand = true
} ;
if ( ! _doAfter . TryStartDoAfter ( doAfterEventArgs ) )
2023-06-02 02:48:42 +02:00
return true ;
2023-04-03 13:13:48 +12:00
2023-03-13 19:34:26 -04:00
if ( _net . IsServer )
{
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-start-cuffing-observer" ,
( "user" , Identity . Name ( user , EntityManager ) ) , ( "target" , Identity . Name ( target , EntityManager ) ) ) ,
target , Filter . Pvs ( target , entityManager : EntityManager )
. RemoveWhere ( e = > e . AttachedEntity = = target | | e . AttachedEntity = = user ) , true ) ;
if ( target = = user )
{
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-target-self" ) , user , user ) ;
}
else
{
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-start-cuffing-target-message" ,
( "targetName" , Identity . Name ( target , EntityManager , user ) ) ) , user , user ) ;
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-start-cuffing-by-other-message" ,
( "otherName" , Identity . Name ( user , EntityManager , target ) ) ) , target , target ) ;
}
}
2023-04-24 22:41:46 +02:00
_audio . PlayPredicted ( handcuffComponent . StartCuffSound , handcuff , user ) ;
2023-06-02 02:48:42 +02:00
return true ;
2023-03-13 19:34:26 -04:00
}
/// <summary>
/// Attempt to uncuff a cuffed entity. Can be called by the cuffed entity, or another entity trying to help uncuff them.
/// If the uncuffing succeeds, the cuffs will drop on the floor.
/// </summary>
/// <param name="target"></param>
/// <param name="user">The cuffed entity</param>
/// <param name="cuffsToRemove">Optional param for the handcuff entity to remove from the cuffed entity. If null, uses the most recently added handcuff entity.</param>
/// <param name="cuffable"></param>
/// <param name="cuff"></param>
public void TryUncuff ( EntityUid target , EntityUid user , EntityUid ? cuffsToRemove = null , CuffableComponent ? cuffable = null , HandcuffComponent ? cuff = null )
{
if ( ! Resolve ( target , ref cuffable ) )
return ;
var isOwner = user = = target ;
if ( cuffsToRemove = = null )
{
if ( cuffable . Container . ContainedEntities . Count = = 0 )
{
return ;
}
cuffsToRemove = cuffable . LastAddedCuffs ;
}
else
{
if ( ! cuffable . Container . ContainedEntities . Contains ( cuffsToRemove . Value ) )
{
2023-10-16 01:31:03 -04:00
Log . Warning ( "A user is trying to remove handcuffs that aren't in the owner's container. This should never happen!" ) ;
2023-03-13 19:34:26 -04:00
}
}
if ( ! Resolve ( cuffsToRemove . Value , ref cuff ) )
return ;
var attempt = new UncuffAttemptEvent ( user , target ) ;
RaiseLocalEvent ( user , ref attempt , true ) ;
if ( attempt . Cancelled )
{
return ;
}
if ( ! isOwner & & ! _interaction . InRangeUnobstructed ( user , target ) )
{
if ( _net . IsServer )
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-cannot-remove-cuffs-too-far-message" ) , user , user ) ;
return ;
}
var uncuffTime = isOwner ? cuff . BreakoutTime : cuff . UncuffTime ;
2023-09-11 09:42:41 +10:00
var doAfterEventArgs = new DoAfterArgs ( EntityManager , user , uncuffTime , new UnCuffDoAfterEvent ( ) , target , target , cuffsToRemove )
2023-03-13 19:34:26 -04:00
{
BreakOnUserMove = true ,
BreakOnTargetMove = true ,
BreakOnDamage = true ,
2023-04-03 13:13:48 +12:00
NeedHand = true ,
RequireCanInteract = false , // Trust in UncuffAttemptEvent
2023-03-13 19:34:26 -04:00
} ;
2023-04-03 13:13:48 +12:00
if ( ! _doAfter . TryStartDoAfter ( doAfterEventArgs ) )
return ;
2023-07-04 05:30:09 +08:00
_adminLog . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(user)} is trying to uncuff {ToPrettyString(target)}" ) ;
if ( isOwner )
{
2023-07-22 23:14:25 +01:00
_damageSystem . TryChangeDamage ( target , cuff . DamageOnResist , true , false ) ;
2023-07-04 05:30:09 +08:00
}
2023-05-02 12:14:55 +02:00
if ( _net . IsServer )
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-start-uncuffing-observer" ,
( "user" , Identity . Name ( user , EntityManager ) ) , ( "target" , Identity . Name ( target , EntityManager ) ) ) ,
target , Filter . Pvs ( target , entityManager : EntityManager )
. RemoveWhere ( e = > e . AttachedEntity = = target | | e . AttachedEntity = = user ) , true ) ;
if ( target = = user )
{
2023-08-11 03:44:52 +10:00
_color . RaiseEffect ( Color . Red , new List < EntityUid > ( ) { user } , Filter . Pvs ( user , entityManager : EntityManager ) ) ;
2023-05-02 12:14:55 +02:00
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-start-uncuffing-self" ) , user , user ) ;
}
else
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-start-uncuffing-target-message" ,
( "targetName" , Identity . Name ( target , EntityManager , user ) ) ) , user , user ) ;
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-start-uncuffing-by-other-message" ,
( "otherName" , Identity . Name ( user , EntityManager , target ) ) ) , target , target ) ;
}
}
2023-04-03 13:13:48 +12:00
_audio . PlayPredicted ( isOwner ? cuff . StartBreakoutSound : cuff . StartUncuffSound , target , user ) ;
2023-03-13 19:34:26 -04:00
}
2023-10-16 01:31:03 -04:00
public void Uncuff ( EntityUid target , EntityUid ? user , EntityUid cuffsToRemove , CuffableComponent ? cuffable = null , HandcuffComponent ? cuff = null )
2023-03-13 19:34:26 -04:00
{
if ( ! Resolve ( target , ref cuffable ) | | ! Resolve ( cuffsToRemove , ref cuff ) )
return ;
2023-12-28 18:50:08 -05:00
if ( cuff . Removing | | TerminatingOrDeleted ( cuffsToRemove ) | | TerminatingOrDeleted ( target ) )
2023-11-22 13:53:30 +13:00
return ;
2023-10-16 01:31:03 -04:00
if ( user ! = null )
{
var attempt = new UncuffAttemptEvent ( user . Value , target ) ;
RaiseLocalEvent ( user . Value , ref attempt ) ;
if ( attempt . Cancelled )
return ;
}
2023-04-03 13:13:48 +12:00
2023-12-28 18:50:08 -05:00
cuff . Removing = true ;
2023-04-24 22:41:46 +02:00
_audio . PlayPredicted ( cuff . EndUncuffSound , target , user ) ;
2023-03-13 19:34:26 -04:00
2023-12-27 21:30:03 -08:00
_container . Remove ( cuffsToRemove , cuffable . Container ) ;
2023-03-13 19:34:26 -04:00
2023-05-25 02:13:10 +06:00
if ( _net . IsServer & & cuffable . CuffedTime ! = null )
{
RaiseLocalEvent ( target , new CuffedTimeStatEvent ( _gameTiming . CurTime - cuffable . CuffedTime . Value ) ) ;
cuffable . CuffedTime = null ;
}
2023-03-13 19:34:26 -04:00
if ( _net . IsServer )
{
2023-05-02 12:14:55 +02:00
// Handles spawning broken cuffs on server to avoid client misprediction
if ( cuff . BreakOnRemove )
{
QueueDel ( cuffsToRemove ) ;
var trash = Spawn ( cuff . BrokenPrototype , Transform ( cuffsToRemove ) . Coordinates ) ;
_hands . PickupOrDrop ( user , trash ) ;
}
else
{
_hands . PickupOrDrop ( user , cuffsToRemove ) ;
}
// Only play popups on server because popups suck
2023-03-13 19:34:26 -04:00
if ( cuffable . CuffedHandCount = = 0 )
{
2023-10-16 01:31:03 -04:00
if ( user ! = null )
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-remove-cuffs-success-message" ) , user . Value , user . Value ) ;
2023-03-13 19:34:26 -04:00
2023-10-16 01:31:03 -04:00
if ( target ! = user & & user ! = null )
2023-03-13 19:34:26 -04:00
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-remove-cuffs-by-other-success-message" ,
2023-10-16 01:31:03 -04:00
( "otherName" , Identity . Name ( user . Value , EntityManager , user ) ) ) , target , target ) ;
2023-03-13 19:34:26 -04:00
_adminLog . Add ( LogType . Action , LogImpact . Medium ,
$"{ToPrettyString(user):player} has successfully uncuffed {ToPrettyString(target):player}" ) ;
}
else
{
_adminLog . Add ( LogType . Action , LogImpact . Medium ,
$"{ToPrettyString(user):player} has successfully uncuffed themselves" ) ;
}
}
2023-10-16 01:31:03 -04:00
else if ( user ! = null )
2023-03-13 19:34:26 -04:00
{
if ( user ! = target )
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-remove-cuffs-partial-success-message" ,
( "cuffedHandCount" , cuffable . CuffedHandCount ) ,
2023-10-16 01:31:03 -04:00
( "otherName" , Identity . Name ( user . Value , EntityManager , user . Value ) ) ) , user . Value , user . Value ) ;
2023-03-13 19:34:26 -04:00
_popup . PopupEntity ( Loc . GetString (
"cuffable-component-remove-cuffs-by-other-partial-success-message" ,
2023-10-16 01:31:03 -04:00
( "otherName" , Identity . Name ( user . Value , EntityManager , user . Value ) ) ,
2023-03-13 19:34:26 -04:00
( "cuffedHandCount" , cuffable . CuffedHandCount ) ) , target , target ) ;
}
else
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-remove-cuffs-partial-success-message" ,
2023-10-16 01:31:03 -04:00
( "cuffedHandCount" , cuffable . CuffedHandCount ) ) , user . Value , user . Value ) ;
2023-03-13 19:34:26 -04:00
}
}
2021-06-17 00:37:05 +10:00
}
2023-12-28 18:50:08 -05:00
cuff . Removing = false ;
2021-06-17 00:37:05 +10:00
}
2021-10-21 13:03:14 +11:00
#region ActionBlocker
2023-03-13 19:34:26 -04:00
private void CheckAct ( EntityUid uid , CuffableComponent component , CancellableEntityEventArgs args )
2021-10-21 13:03:14 +11:00
{
if ( ! component . CanStillInteract )
args . Cancel ( ) ;
}
2023-03-13 19:34:26 -04:00
private void OnEquipAttempt ( EntityUid uid , CuffableComponent component , IsEquippingAttemptEvent args )
2021-10-21 13:03:14 +11:00
{
2022-01-02 06:03:29 +13:00
// is this a self-equip, or are they being stripped?
if ( args . Equipee = = uid )
CheckAct ( uid , component , args ) ;
2021-10-21 13:03:14 +11:00
}
2023-03-13 19:34:26 -04:00
private void OnUnequipAttempt ( EntityUid uid , CuffableComponent component , IsUnequippingAttemptEvent args )
2021-10-21 13:03:14 +11:00
{
2022-01-02 06:03:29 +13:00
// is this a self-equip, or are they being stripped?
if ( args . Unequipee = = uid )
CheckAct ( uid , component , args ) ;
2021-10-21 13:03:14 +11:00
}
#endregion
2023-03-13 19:34:26 -04:00
public IReadOnlyList < EntityUid > GetAllCuffs ( CuffableComponent component )
{
return component . Container . ContainedEntities ;
}
2023-03-15 17:07:16 +13:00
2023-04-03 13:13:48 +12:00
[Serializable, NetSerializable]
2023-08-22 18:14:33 -07:00
private sealed partial class UnCuffDoAfterEvent : SimpleDoAfterEvent
2023-03-15 17:07:16 +13:00
{
}
2023-04-03 13:13:48 +12:00
[Serializable, NetSerializable]
2023-08-22 18:14:33 -07:00
private sealed partial class AddCuffDoAfterEvent : SimpleDoAfterEvent
2023-03-15 17:07:16 +13:00
{
}
2021-06-17 00:37:05 +10:00
}
}