2021-02-06 03:11:21 +11:00
using System ;
2020-09-13 14:23:52 +02:00
using System.Collections.Generic ;
using System.Linq ;
2021-06-09 22:19:39 +02:00
using Content.Server.DoAfter ;
using Content.Server.Hands.Components ;
2020-11-09 20:22:19 -08:00
using Content.Shared.Alert ;
2021-06-09 22:19:39 +02:00
using Content.Shared.Cuffs.Components ;
2022-02-17 15:40:03 +13:00
using Content.Shared.Interaction ;
2021-06-09 22:19:39 +02:00
using Content.Shared.Interaction.Helpers ;
2021-09-26 15:18:45 +02:00
using Content.Shared.Popups ;
2020-09-13 14:23:52 +02:00
using Robust.Server.GameObjects ;
2021-03-21 09:12:03 -07:00
using Robust.Shared.Audio ;
2021-03-01 15:24:46 -08:00
using Robust.Shared.Containers ;
2020-08-25 08:54:23 -04:00
using Robust.Shared.GameObjects ;
2021-12-03 11:11:52 +01:00
using Robust.Shared.IoC ;
2020-08-25 08:54:23 -04:00
using Robust.Shared.Localization ;
using Robust.Shared.Log ;
using Robust.Shared.Maths ;
2021-03-21 09:12:03 -07:00
using Robust.Shared.Player ;
2020-09-13 14:23:52 +02:00
using Robust.Shared.ViewVariables ;
2020-08-25 08:54:23 -04:00
2021-06-09 22:19:39 +02:00
namespace Content.Server.Cuffs.Components
2020-08-25 08:54:23 -04:00
{
[RegisterComponent]
2021-06-17 00:37:05 +10:00
[ComponentReference(typeof(SharedCuffableComponent))]
2022-02-16 00:23:23 -07:00
public sealed class CuffableComponent : SharedCuffableComponent
2020-08-25 08:54:23 -04:00
{
2021-12-08 17:17:12 +01:00
[Dependency] private readonly IEntityManager _entMan = default ! ;
2020-08-25 08:54:23 -04:00
/// <summary>
/// How many of this entity's hands are currently cuffed.
/// </summary>
[ViewVariables]
2021-02-21 13:39:54 +01:00
public int CuffedHandCount = > Container . ContainedEntities . Count * 2 ;
2020-08-25 08:54:23 -04:00
2022-02-21 14:41:50 +11:00
private EntityUid LastAddedCuffs = > Container . ContainedEntities [ ^ 1 ] ;
2020-08-25 08:54:23 -04:00
2021-12-05 18:09:01 +01:00
public IReadOnlyList < EntityUid > StoredEntities = > Container . ContainedEntities ;
2020-08-25 08:54:23 -04:00
/// <summary>
/// Container of various handcuffs currently applied to the entity.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
2021-02-21 13:39:54 +01:00
public Container Container { get ; set ; } = default ! ;
2020-08-25 08:54:23 -04:00
2021-02-06 03:11:21 +11:00
// TODO: Make a component message
public event Action ? OnCuffedStateChanged ;
2020-08-25 08:54:23 -04:00
2021-02-06 03:11:21 +11:00
private bool _uncuffing ;
2020-08-25 08:54:23 -04:00
2021-06-19 19:41:26 -07:00
protected override void Initialize ( )
2020-08-25 08:54:23 -04:00
{
base . Initialize ( ) ;
2021-03-01 15:24:46 -08:00
Container = ContainerHelpers . EnsureContainer < Container > ( Owner , Name ) ;
2021-02-06 03:11:21 +11:00
Owner . EnsureComponentWarn < HandsComponent > ( ) ;
2020-08-25 08:54:23 -04:00
}
2021-11-30 15:20:38 +01:00
public override ComponentState GetComponentState ( )
2020-08-25 08:54:23 -04:00
{
// there are 2 approaches i can think of to handle the handcuff overlay on players
// 1 - make the current RSI the handcuff type that's currently active. all handcuffs on the player will appear the same.
// 2 - allow for several different player overlays for each different cuff type.
// approach #2 would be more difficult/time consuming to do and the payoff doesn't make it worth it.
// right now we're doing approach #1.
if ( CuffedHandCount > 0 )
{
2021-12-08 17:17:12 +01:00
if ( _entMan . TryGetComponent < HandcuffComponent ? > ( LastAddedCuffs , out var cuffs ) )
2020-08-25 08:54:23 -04:00
{
return new CuffableComponentState ( CuffedHandCount ,
CanStillInteract ,
cuffs . CuffedRSI ,
$"{cuffs.OverlayIconState}-{CuffedHandCount}" ,
cuffs . Color ) ;
// the iconstate is formatted as blah-2, blah-4, blah-6, etc.
// the number corresponds to how many hands are cuffed.
}
}
return new CuffableComponentState ( CuffedHandCount ,
CanStillInteract ,
"/Objects/Misc/handcuffs.rsi" ,
"body-overlay-2" ,
Color . White ) ;
}
/// <summary>
/// Add a set of cuffs to an existing CuffedComponent.
/// </summary>
/// <param name="prototype"></param>
2021-12-05 18:09:01 +01:00
public bool TryAddNewCuffs ( EntityUid user , EntityUid handcuff )
2020-08-25 08:54:23 -04:00
{
2021-12-08 17:17:12 +01:00
if ( ! _entMan . HasComponent < HandcuffComponent > ( handcuff ) )
2020-08-25 08:54:23 -04:00
{
Logger . Warning ( $"Handcuffs being applied to player are missing a {nameof(HandcuffComponent)}!" ) ;
2021-02-06 03:11:21 +11:00
return false ;
2020-08-25 08:54:23 -04:00
}
2022-02-17 15:40:03 +13:00
if ( ! EntitySystem . Get < SharedInteractionSystem > ( ) . InRangeUnobstructed ( handcuff , Owner ) )
2020-08-25 08:54:23 -04:00
{
Logger . Warning ( "Handcuffs being applied to player are obstructed or too far away! This should not happen!" ) ;
2021-02-06 03:11:21 +11:00
return true ;
}
// Success!
2021-12-08 17:17:12 +01:00
if ( _entMan . TryGetComponent ( user , out HandsComponent ? handsComponent ) & & handsComponent . IsHolding ( handcuff ) )
2021-02-06 03:11:21 +11:00
{
// Good lord handscomponent is scuffed, I hope some smug person will fix it someday
handsComponent . Drop ( handcuff ) ;
2020-08-25 08:54:23 -04:00
}
2021-02-21 13:39:54 +01:00
Container . Insert ( handcuff ) ;
2021-12-08 17:17:12 +01:00
CanStillInteract = _entMan . TryGetComponent ( Owner , out HandsComponent ? ownerHands ) & & ownerHands . HandNames . Count ( ) > CuffedHandCount ;
2020-08-25 08:54:23 -04:00
2020-11-18 15:30:36 +01:00
OnCuffedStateChanged ? . Invoke ( ) ;
2020-11-09 20:22:19 -08:00
UpdateAlert ( ) ;
2020-08-25 08:54:23 -04:00
UpdateHeldItems ( ) ;
Dirty ( ) ;
2021-02-06 03:11:21 +11:00
return true ;
2020-08-25 08:54:23 -04:00
}
2021-02-21 13:39:54 +01:00
public void CuffedStateChanged ( )
2020-08-25 08:54:23 -04:00
{
2021-02-21 13:39:54 +01:00
UpdateAlert ( ) ;
OnCuffedStateChanged ? . Invoke ( ) ;
2020-08-27 10:33:10 -04:00
}
2020-08-25 08:54:23 -04:00
/// <summary>
/// Check how many items the user is holding and if it's more than the number of cuffed hands, drop some items.
/// </summary>
public void UpdateHeldItems ( )
{
2021-12-08 17:17:12 +01:00
if ( ! _entMan . TryGetComponent ( Owner , out HandsComponent ? handsComponent ) ) return ;
2021-02-06 03:11:21 +11:00
var itemCount = handsComponent . GetAllHeldItems ( ) . Count ( ) ;
2021-06-21 02:21:20 -07:00
var freeHandCount = handsComponent . HandNames . Count ( ) - CuffedHandCount ;
2020-08-25 08:54:23 -04:00
if ( freeHandCount < itemCount )
{
2021-02-06 03:11:21 +11:00
foreach ( var item in handsComponent . GetAllHeldItems ( ) )
2020-08-25 08:54:23 -04:00
{
if ( freeHandCount < itemCount )
{
freeHandCount + + ;
2021-02-06 03:11:21 +11:00
handsComponent . Drop ( item . Owner , false ) ;
2020-08-25 08:54:23 -04:00
}
else
{
break ;
}
}
}
}
/// <summary>
/// Updates the status effect indicator on the HUD.
/// </summary>
2020-11-09 20:22:19 -08:00
private void UpdateAlert ( )
2020-08-25 08:54:23 -04:00
{
2022-01-05 00:19:23 -08:00
if ( CanStillInteract )
2020-08-25 08:54:23 -04:00
{
2022-01-05 00:19:23 -08:00
EntitySystem . Get < AlertsSystem > ( ) . ClearAlert ( Owner , AlertType . Handcuffed ) ;
}
else
{
EntitySystem . Get < AlertsSystem > ( ) . ShowAlert ( Owner , AlertType . Handcuffed ) ;
2020-08-25 08:54:23 -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="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>
2021-12-13 06:53:02 +13:00
public async void TryUncuff ( EntityUid user , EntityUid ? cuffsToRemove = null )
2020-08-25 08:54:23 -04:00
{
2021-02-06 03:11:21 +11:00
if ( _uncuffing ) return ;
2020-08-25 08:54:23 -04:00
var isOwner = user = = Owner ;
if ( cuffsToRemove = = null )
{
2021-02-21 13:39:54 +01:00
if ( Container . ContainedEntities . Count = = 0 )
{
return ;
}
2020-08-25 08:54:23 -04:00
cuffsToRemove = LastAddedCuffs ;
}
else
{
2021-12-13 06:53:02 +13:00
if ( ! Container . ContainedEntities . Contains ( cuffsToRemove . Value ) )
2020-08-25 08:54:23 -04:00
{
Logger . Warning ( "A user is trying to remove handcuffs that aren't in the owner's container. This should never happen!" ) ;
}
}
2021-12-08 17:17:12 +01:00
if ( ! _entMan . TryGetComponent < HandcuffComponent ? > ( cuffsToRemove , out var cuff ) )
2020-08-25 08:54:23 -04:00
{
Logger . Warning ( $"A user is trying to remove handcuffs without a {nameof(HandcuffComponent)}. This should never happen!" ) ;
return ;
}
2021-12-03 15:53:09 +01:00
var attempt = new UncuffAttemptEvent ( user , Owner ) ;
2021-12-08 17:17:12 +01:00
_entMan . EventBus . RaiseLocalEvent ( user , attempt ) ;
2021-10-01 23:17:36 +01:00
if ( attempt . Cancelled )
2020-08-25 08:54:23 -04:00
{
return ;
}
2022-02-17 15:40:03 +13:00
if ( ! isOwner & & ! EntitySystem . Get < SharedInteractionSystem > ( ) . InRangeUnobstructed ( user , Owner ) )
2020-08-25 08:54:23 -04:00
{
2021-06-21 02:13:54 +02:00
user . PopupMessage ( Loc . GetString ( "cuffable-component-cannot-remove-cuffs-too-far-message" ) ) ;
2020-08-25 08:54:23 -04:00
return ;
}
2021-06-21 02:13:54 +02:00
user . PopupMessage ( Loc . GetString ( "cuffable-component-start-removing-cuffs-message" ) ) ;
2020-08-25 08:54:23 -04:00
2021-02-06 03:11:21 +11:00
if ( isOwner )
{
2021-07-31 19:52:33 +02:00
SoundSystem . Play ( Filter . Pvs ( Owner ) , cuff . StartBreakoutSound . GetSound ( ) , Owner ) ;
2021-02-06 03:11:21 +11:00
}
else
{
2021-07-31 19:52:33 +02:00
SoundSystem . Play ( Filter . Pvs ( Owner ) , cuff . StartUncuffSound . GetSound ( ) , Owner ) ;
2021-02-06 03:11:21 +11:00
}
2021-05-24 16:55:48 +02:00
2020-08-25 08:54:23 -04:00
var uncuffTime = isOwner ? cuff . BreakoutTime : cuff . UncuffTime ;
var doAfterEventArgs = new DoAfterEventArgs ( user , uncuffTime )
{
BreakOnUserMove = true ,
BreakOnDamage = true ,
BreakOnStun = true ,
NeedHand = true
} ;
var doAfterSystem = EntitySystem . Get < DoAfterSystem > ( ) ;
2021-02-06 03:11:21 +11:00
_uncuffing = true ;
2021-07-04 13:32:24 +02:00
var result = await doAfterSystem . WaitDoAfter ( doAfterEventArgs ) ;
2020-08-25 08:54:23 -04:00
2021-02-06 03:11:21 +11:00
_uncuffing = false ;
2020-08-25 08:54:23 -04:00
if ( result ! = DoAfterStatus . Cancelled )
{
2021-07-31 19:52:33 +02:00
SoundSystem . Play ( Filter . Pvs ( Owner ) , cuff . EndUncuffSound . GetSound ( ) , Owner ) ;
2020-08-25 08:54:23 -04:00
2021-12-13 06:53:02 +13:00
Container . ForceRemove ( cuffsToRemove . Value ) ;
var transform = _entMan . GetComponent < TransformComponent > ( cuffsToRemove . Value ) ;
transform . AttachToGridOrMap ( ) ;
transform . WorldPosition = _entMan . GetComponent < TransformComponent > ( Owner ) . WorldPosition ;
2020-08-25 08:54:23 -04:00
if ( cuff . BreakOnRemove )
{
cuff . Broken = true ;
2021-12-13 06:53:02 +13:00
var meta = _entMan . GetComponent < MetaDataComponent > ( cuffsToRemove . Value ) ;
meta . EntityName = cuff . BrokenName ;
meta . EntityDescription = cuff . BrokenDesc ;
2020-08-25 08:54:23 -04:00
2021-12-08 17:17:12 +01:00
if ( _entMan . TryGetComponent < SpriteComponent ? > ( cuffsToRemove , out var sprite ) & & cuff . BrokenState ! = null )
2020-08-25 08:54:23 -04:00
{
sprite . LayerSetState ( 0 , cuff . BrokenState ) ; // TODO: safety check to see if RSI contains the state?
}
}
2021-12-08 17:17:12 +01:00
CanStillInteract = _entMan . TryGetComponent ( Owner , out HandsComponent ? handsComponent ) & & handsComponent . HandNames . Count ( ) > CuffedHandCount ;
2021-02-06 03:11:21 +11:00
OnCuffedStateChanged ? . Invoke ( ) ;
2020-11-09 20:22:19 -08:00
UpdateAlert ( ) ;
2020-08-25 08:54:23 -04:00
Dirty ( ) ;
if ( CuffedHandCount = = 0 )
{
2021-06-21 02:13:54 +02:00
user . PopupMessage ( Loc . GetString ( "cuffable-component-remove-cuffs-success-message" ) ) ;
2020-08-25 08:54:23 -04:00
if ( ! isOwner )
{
2021-07-31 19:52:33 +02:00
user . PopupMessage ( Owner , Loc . GetString ( "cuffable-component-remove-cuffs-by-other-success-message" , ( "otherName" , user ) ) ) ;
2020-08-25 08:54:23 -04:00
}
}
else
{
if ( ! isOwner )
{
2021-06-21 02:13:54 +02:00
user . PopupMessage ( Loc . GetString ( "cuffable-component-remove-cuffs-partial-success-message" ,
( "cuffedHandCount" , CuffedHandCount ) ,
( "otherName" , user ) ) ) ;
user . PopupMessage ( Owner , Loc . GetString ( "cuffable-component-remove-cuffs-by-other-partial-success-message" ,
( "otherName" , user ) ,
( "cuffedHandCount" , CuffedHandCount ) ) ) ;
2020-08-25 08:54:23 -04:00
}
else
{
2021-07-31 19:52:33 +02:00
user . PopupMessage ( Loc . GetString ( "cuffable-component-remove-cuffs-partial-success-message" , ( "cuffedHandCount" , CuffedHandCount ) ) ) ;
2020-08-25 08:54:23 -04:00
}
}
}
else
{
2021-06-21 02:13:54 +02:00
user . PopupMessage ( Loc . GetString ( "cuffable-component-remove-cuffs-fail-message" ) ) ;
2020-08-25 08:54:23 -04:00
}
return ;
}
}
}