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 ;
2021-06-17 00:37:05 +10:00
using Content.Shared.Cuffs.Components ;
2023-03-13 19:34:26 -04:00
using Content.Shared.Database ;
using Content.Shared.DoAfter ;
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-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 ;
using Robust.Shared.Timing ;
2021-06-17 00:37:05 +10:00
namespace Content.Shared.Cuffs
{
public abstract class SharedCuffableSystem : EntitySystem
{
2023-03-13 19:34:26 -04:00
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default ! ;
2023-01-21 02:48:19 +13:00
[Dependency] private readonly AlertsSystem _alerts = default ! ;
2023-03-13 19:34:26 -04:00
[Dependency] private readonly IComponentFactory _componentFactory = default ! ;
[Dependency] private readonly IGameTiming _timing = default ! ;
[Dependency] private readonly INetManager _net = default ! ;
[Dependency] private readonly ISharedAdminLogManager _adminLog = default ! ;
[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 ! ;
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 ) ;
SubscribeLocalEvent < CuffableComponent , GetVerbsEvent < Verb > > ( AddUncuffVerb ) ;
2023-03-15 17:07:16 +13:00
SubscribeLocalEvent < CuffableComponent , DoAfterEvent < UnCuffDoAfter > > ( 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-03-15 17:07:16 +13:00
SubscribeLocalEvent < HandcuffComponent , DoAfterEvent < AddCuffDoAfter > > ( OnAddCuffDoAfter ) ;
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 )
{
if ( args . Container . ID = = component . Container . ID )
{
_handVirtualItem . DeleteInHandsMatching ( uid , args . Entity ) ;
UpdateCuffState ( uid , component ) ;
}
}
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
{
var canInteract = TryComp ( uid , out SharedHandsComponent ? hands ) & & hands . Hands . Count > component . CuffedHandCount ;
if ( canInteract = = component . CanStillInteract )
return ;
component . CanStillInteract = canInteract ;
Dirty ( 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
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-03-15 17:07:16 +13:00
private void OnCuffableDoAfter ( EntityUid uid , CuffableComponent component , DoAfterEvent < UnCuffDoAfter > args )
2023-03-13 19:34:26 -04:00
{
2023-03-15 17:07:16 +13:00
component . Uncuffing = false ;
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 ;
Dirty ( component ) ;
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 )
{
if ( args . Target is not { Valid : true } target )
return ;
if ( ! args . CanReach )
{
if ( _net . IsServer )
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-too-far-away-error" ) , args . User , args . User ) ;
return ;
}
TryCuffing ( args . User , target , uid , component ) ;
args . Handled = true ;
}
private void OnCuffMeleeHit ( EntityUid uid , HandcuffComponent component , MeleeHitEvent args )
{
if ( ! args . HitEntities . Any ( ) )
return ;
TryCuffing ( uid , args . User , args . HitEntities . First ( ) , component ) ;
args . Handled = true ;
}
2023-03-15 17:07:16 +13:00
private void OnAddCuffDoAfter ( EntityUid uid , HandcuffComponent component , DoAfterEvent < AddCuffDoAfter > 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 ;
component . Cuffing = false ;
if ( ! args . Cancelled & & TryAddNewCuffs ( target , user , uid , cuffable ) )
{
_audio . PlayPvs ( component . EndCuffSound , uid ) ;
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
{
_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 ) ;
}
}
}
/// <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 ;
var handCount = CompOrNull < SharedHandsComponent > ( owner ) ? . Count ? ? 0 ;
while ( cuffable . CuffedHandCount > handCount & & cuffable . CuffedHandCount > 0 )
{
dirty = true ;
var container = cuffable . Container ;
var entity = container . ContainedEntities [ ^ 1 ] ;
container . Remove ( entity ) ;
_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.
if ( ! TryComp < SharedHandsComponent > ( uid , out var handsComponent ) )
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 ) ;
component . Container . Insert ( handcuff ) ;
UpdateHeldItems ( target , handcuff , component ) ;
return true ;
}
public void TryCuffing ( EntityUid user , EntityUid target , EntityUid handcuff , HandcuffComponent ? handcuffComponent = null , CuffableComponent ? cuffable = null )
{
if ( ! Resolve ( handcuff , ref handcuffComponent ) | | ! Resolve ( target , ref cuffable , false ) )
return ;
if ( handcuffComponent . Cuffing )
return ;
if ( ! TryComp < SharedHandsComponent ? > ( target , out var hands ) )
{
if ( _net . IsServer )
{
_popup . PopupEntity ( Loc . GetString ( "handcuff-component-target-has-no-hands-error" ,
( "targetName" , Identity . Name ( target , EntityManager , user ) ) ) , user , user ) ;
}
return ;
}
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 ) ;
}
return ;
}
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 ) ;
}
}
_audio . PlayPvs ( handcuffComponent . StartCuffSound , handcuff ) ;
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.
var doAfterEventArgs = new DoAfterEventArgs ( user , cuffTime , default , target , handcuff )
{
RaiseOnUser = false ,
RaiseOnTarget = false ,
RaiseOnUsed = true ,
BreakOnTargetMove = true ,
BreakOnUserMove = true ,
BreakOnDamage = true ,
BreakOnStun = true ,
NeedHand = true
} ;
handcuffComponent . Cuffing = true ;
if ( _net . IsServer )
2023-03-15 17:07:16 +13:00
_doAfter . DoAfter ( doAfterEventArgs , new AddCuffDoAfter ( ) ) ;
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 ;
if ( cuffable . Uncuffing )
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 ) )
{
Logger . Warning ( "A user is trying to remove handcuffs that aren't in the owner's container. This should never happen!" ) ;
}
}
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 ;
}
if ( _net . IsServer )
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-start-removing-cuffs-message" ) , user , user ) ;
_audio . PlayPredicted ( isOwner ? cuff . StartBreakoutSound : cuff . StartUncuffSound , target , user ) ;
var uncuffTime = isOwner ? cuff . BreakoutTime : cuff . UncuffTime ;
var doAfterEventArgs = new DoAfterEventArgs ( user , uncuffTime , default , target , cuffsToRemove )
{
RaiseOnTarget = true ,
RaiseOnUsed = false ,
RaiseOnUser = false ,
BreakOnUserMove = true ,
BreakOnTargetMove = true ,
BreakOnDamage = true ,
BreakOnStun = true ,
NeedHand = true
} ;
cuffable . Uncuffing = true ;
Dirty ( cuffable ) ;
if ( _net . IsServer )
2023-03-15 17:07:16 +13:00
_doAfter . DoAfter ( doAfterEventArgs , new UnCuffDoAfter ( ) ) ;
2023-03-13 19:34:26 -04:00
}
public void Uncuff ( EntityUid target , EntityUid user , EntityUid cuffsToRemove , CuffableComponent ? cuffable = null , HandcuffComponent ? cuff = null )
{
if ( ! Resolve ( target , ref cuffable ) | | ! Resolve ( cuffsToRemove , ref cuff ) )
return ;
_audio . PlayPvs ( cuff . EndUncuffSound , target ) ;
cuffable . Container . Remove ( cuffsToRemove ) ;
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
if ( _net . IsServer )
{
if ( cuffable . CuffedHandCount = = 0 )
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-remove-cuffs-success-message" ) , user , user ) ;
if ( target ! = user )
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-remove-cuffs-by-other-success-message" ,
( "otherName" , Identity . Name ( user , EntityManager , user ) ) ) , target , target ) ;
_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" ) ;
}
}
else
{
if ( user ! = target )
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-remove-cuffs-partial-success-message" ,
( "cuffedHandCount" , cuffable . CuffedHandCount ) ,
( "otherName" , Identity . Name ( user , EntityManager , user ) ) ) , user , user ) ;
_popup . PopupEntity ( Loc . GetString (
"cuffable-component-remove-cuffs-by-other-partial-success-message" ,
( "otherName" , Identity . Name ( user , EntityManager , user ) ) ,
( "cuffedHandCount" , cuffable . CuffedHandCount ) ) , target , target ) ;
}
else
{
_popup . PopupEntity ( Loc . GetString ( "cuffable-component-remove-cuffs-partial-success-message" ,
( "cuffedHandCount" , cuffable . CuffedHandCount ) ) , user , user ) ;
}
}
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
private struct UnCuffDoAfter
{
}
private struct AddCuffDoAfter
{
}
2021-06-17 00:37:05 +10:00
}
}