2021-02-06 03:11:21 +11:00
#nullable enable
using System ;
2020-09-13 14:23:52 +02:00
using System.Collections.Generic ;
using System.Linq ;
using Content.Server.GameObjects.Components.GUI ;
using Content.Server.GameObjects.Components.Mobs ;
using Content.Server.GameObjects.EntitySystems.DoAfter ;
2020-11-09 20:22:19 -08:00
using Content.Shared.Alert ;
2020-09-13 14:23:52 +02:00
using Content.Shared.GameObjects.Components.ActionBlocking ;
2020-12-20 04:26:21 +01:00
using Content.Shared.GameObjects.EntitySystems.ActionBlocker ;
2020-09-13 14:23:52 +02:00
using Content.Shared.GameObjects.Verbs ;
2020-08-25 08:54:23 -04:00
using Content.Shared.Interfaces ;
2020-09-13 14:23:52 +02:00
using Content.Shared.Utility ;
using Robust.Server.GameObjects ;
2020-08-25 08:54:23 -04:00
using Robust.Shared.GameObjects ;
using Robust.Shared.Localization ;
using Robust.Shared.Log ;
using Robust.Shared.Maths ;
2021-02-18 09:09:07 +01:00
using Robust.Shared.Players ;
2020-09-13 14:23:52 +02:00
using Robust.Shared.ViewVariables ;
2020-08-25 08:54:23 -04:00
namespace Content.Server.GameObjects.Components.ActionBlocking
{
[RegisterComponent]
public class CuffableComponent : SharedCuffableComponent
{
/// <summary>
/// How many of this entity's hands are currently cuffed.
/// </summary>
[ViewVariables]
public int CuffedHandCount = > _container . ContainedEntities . Count * 2 ;
protected IEntity LastAddedCuffs = > _container . ContainedEntities [ _container . ContainedEntities . Count - 1 ] ;
public IReadOnlyList < IEntity > StoredEntities = > _container . ContainedEntities ;
/// <summary>
/// Container of various handcuffs currently applied to the entity.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
private Container _container = default ! ;
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
public override void Initialize ( )
{
base . Initialize ( ) ;
_container = ContainerManagerComponent . Ensure < Container > ( Name , Owner ) ;
2020-08-27 10:33:10 -04:00
Owner . EntityManager . EventBus . SubscribeEvent < HandCountChangedEvent > ( EventSource . Local , this , HandleHandCountChange ) ;
2021-02-06 03:11:21 +11:00
Owner . EnsureComponentWarn < HandsComponent > ( ) ;
2020-08-25 08:54:23 -04:00
}
2021-02-18 09:09:07 +01:00
public override ComponentState GetComponentState ( ICommonSession player )
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 )
{
if ( LastAddedCuffs . TryGetComponent < HandcuffComponent > ( out var cuffs ) )
{
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-02-06 03:11:21 +11:00
public bool TryAddNewCuffs ( IEntity user , IEntity handcuff )
2020-08-25 08:54:23 -04:00
{
if ( ! handcuff . HasComponent < HandcuffComponent > ( ) )
{
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
}
2021-02-06 03:11:21 +11:00
if ( ! handcuff . InRangeUnobstructed ( 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!
if ( user . TryGetComponent ( out HandsComponent ? handsComponent ) & & handsComponent . IsHolding ( handcuff ) )
{
// 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
}
_container . Insert ( handcuff ) ;
2021-02-06 03:11:21 +11:00
CanStillInteract = Owner . TryGetComponent ( out HandsComponent ? ownerHands ) & & ownerHands . Hands . 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
}
/// <summary>
/// Check the current amount of hands the owner has, and if there's less hands than active cuffs we remove some cuffs.
/// </summary>
2020-08-30 11:37:06 +02:00
private void UpdateHandCount ( )
2020-08-25 08:54:23 -04:00
{
2020-08-27 10:33:10 -04:00
var dirty = false ;
2021-02-06 03:11:21 +11:00
var handCount = Owner . TryGetComponent ( out HandsComponent ? handsComponent ) ? handsComponent . Hands . Count ( ) : 0 ;
2020-08-25 08:54:23 -04:00
while ( CuffedHandCount > handCount & & CuffedHandCount > 0 )
{
2020-08-27 10:33:10 -04:00
dirty = true ;
2020-08-25 08:54:23 -04:00
var entity = _container . ContainedEntities [ _container . ContainedEntities . Count - 1 ] ;
_container . Remove ( entity ) ;
2020-09-06 16:11:53 +02:00
entity . Transform . WorldPosition = Owner . Transform . Coordinates . Position ;
2020-08-25 08:54:23 -04:00
}
2020-08-27 10:33:10 -04:00
if ( dirty )
2020-08-25 08:54:23 -04:00
{
CanStillInteract = handCount > CuffedHandCount ;
2021-02-06 03:11:21 +11:00
OnCuffedStateChanged ? . Invoke ( ) ;
2020-08-25 08:54:23 -04:00
Dirty ( ) ;
}
}
2020-08-27 10:33:10 -04:00
private void HandleHandCountChange ( HandCountChangedEvent message )
{
if ( message . Sender = = Owner )
{
UpdateHandCount ( ) ;
}
}
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-02-06 03:11:21 +11:00
if ( ! Owner . TryGetComponent ( out HandsComponent ? handsComponent ) ) return ;
var itemCount = handsComponent . GetAllHeldItems ( ) . Count ( ) ;
var freeHandCount = handsComponent . Hands . 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
{
2021-02-06 03:11:21 +11:00
if ( Owner . TryGetComponent ( out ServerAlertsComponent ? status ) )
2020-08-25 08:54:23 -04:00
{
2020-08-29 13:33:38 +02:00
if ( CanStillInteract )
{
2020-11-09 20:22:19 -08:00
status . ClearAlert ( AlertType . Handcuffed ) ;
2020-08-29 13:33:38 +02:00
}
else
{
2020-11-09 20:22:19 -08:00
status . ShowAlert ( AlertType . Handcuffed ) ;
2020-08-29 13:33:38 +02:00
}
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-02-06 03:11:21 +11:00
public async void TryUncuff ( IEntity user , IEntity ? 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 )
{
cuffsToRemove = LastAddedCuffs ;
}
else
{
if ( ! _container . ContainedEntities . Contains ( cuffsToRemove ) )
{
Logger . Warning ( "A user is trying to remove handcuffs that aren't in the owner's container. This should never happen!" ) ;
}
}
if ( ! cuffsToRemove . TryGetComponent < HandcuffComponent > ( out var cuff ) )
{
Logger . Warning ( $"A user is trying to remove handcuffs without a {nameof(HandcuffComponent)}. This should never happen!" ) ;
return ;
}
if ( ! isOwner & & ! ActionBlockerSystem . CanInteract ( user ) )
{
2020-09-01 12:34:53 +02:00
user . PopupMessage ( Loc . GetString ( "You can't do that!" ) ) ;
2020-08-25 08:54:23 -04:00
return ;
}
2021-02-06 03:11:21 +11:00
if ( ! isOwner & & ! user . InRangeUnobstructed ( Owner ) )
2020-08-25 08:54:23 -04:00
{
2020-09-01 12:34:53 +02:00
user . PopupMessage ( Loc . GetString ( "You are too far away to remove the cuffs." ) ) ;
2020-08-25 08:54:23 -04:00
return ;
}
2021-02-06 03:11:21 +11:00
if ( ! cuffsToRemove . InRangeUnobstructed ( Owner ) )
2020-08-25 08:54:23 -04:00
{
Logger . Warning ( "Handcuffs being removed from player are obstructed or too far away! This should not happen!" ) ;
return ;
}
2020-09-01 12:34:53 +02:00
user . PopupMessage ( Loc . GetString ( "You start removing the cuffs." ) ) ;
2020-08-25 08:54:23 -04:00
var audio = EntitySystem . Get < AudioSystem > ( ) ;
2021-02-06 03:11:21 +11:00
if ( isOwner )
{
if ( cuff . StartBreakoutSound ! = null )
audio . PlayFromEntity ( cuff . StartBreakoutSound , Owner ) ;
}
else
{
if ( cuff . StartUncuffSound ! = null )
audio . PlayFromEntity ( cuff . StartUncuffSound , Owner ) ;
}
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 ;
2020-08-25 08:54:23 -04:00
var result = await doAfterSystem . DoAfter ( doAfterEventArgs ) ;
2021-02-06 03:11:21 +11:00
_uncuffing = false ;
2020-08-25 08:54:23 -04:00
if ( result ! = DoAfterStatus . Cancelled )
{
2021-02-06 03:11:21 +11:00
if ( cuff . EndUncuffSound ! = null )
audio . PlayFromEntity ( cuff . EndUncuffSound , Owner ) ;
2020-08-25 08:54:23 -04:00
_container . ForceRemove ( cuffsToRemove ) ;
cuffsToRemove . Transform . AttachToGridOrMap ( ) ;
cuffsToRemove . Transform . WorldPosition = Owner . Transform . WorldPosition ;
if ( cuff . BreakOnRemove )
{
cuff . Broken = true ;
cuffsToRemove . Name = cuff . BrokenName ;
cuffsToRemove . Description = cuff . BrokenDesc ;
2021-02-06 03:11:21 +11:00
if ( cuffsToRemove . TryGetComponent < SpriteComponent > ( 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-02-06 03:11:21 +11:00
CanStillInteract = Owner . TryGetComponent ( out HandsComponent ? handsComponent ) & & handsComponent . Hands . Count ( ) > CuffedHandCount ;
OnCuffedStateChanged ? . Invoke ( ) ;
2020-11-09 20:22:19 -08:00
UpdateAlert ( ) ;
2020-08-25 08:54:23 -04:00
Dirty ( ) ;
if ( CuffedHandCount = = 0 )
{
2020-09-01 12:34:53 +02:00
user . PopupMessage ( Loc . GetString ( "You successfully remove the cuffs." ) ) ;
2020-08-25 08:54:23 -04:00
if ( ! isOwner )
{
2020-09-01 12:34:53 +02:00
user . PopupMessage ( Owner , Loc . GetString ( "{0:theName} uncuffs your hands." , user ) ) ;
2020-08-25 08:54:23 -04:00
}
}
else
{
if ( ! isOwner )
{
2020-09-01 12:34:53 +02:00
user . PopupMessage ( Loc . GetString ( "You successfully remove the cuffs. {0} of {1:theName}'s hands remain cuffed." , CuffedHandCount , user ) ) ;
user . PopupMessage ( Owner , Loc . GetString ( "{0:theName} removes your cuffs. {1} of your hands remain cuffed." , user , CuffedHandCount ) ) ;
2020-08-25 08:54:23 -04:00
}
else
{
2020-09-01 12:34:53 +02:00
user . PopupMessage ( Loc . GetString ( "You successfully remove the cuffs. {0} of your hands remain cuffed." , CuffedHandCount ) ) ;
2020-08-25 08:54:23 -04:00
}
}
}
else
{
2020-09-01 12:34:53 +02:00
user . PopupMessage ( Loc . GetString ( "You fail to remove the cuffs." ) ) ;
2020-08-25 08:54:23 -04:00
}
return ;
}
/// <summary>
/// Allows the uncuffing of a cuffed person. Used by other people and by the component owner to break out of cuffs.
/// </summary>
[Verb]
private sealed class UncuffVerb : Verb < CuffableComponent >
{
protected override void GetData ( IEntity user , CuffableComponent component , VerbData data )
{
if ( ( user ! = component . Owner & & ! ActionBlockerSystem . CanInteract ( user ) ) | | component . CuffedHandCount = = 0 )
{
data . Visibility = VerbVisibility . Invisible ;
return ;
}
data . Text = Loc . GetString ( "Uncuff" ) ;
}
protected override void Activate ( IEntity user , CuffableComponent component )
{
if ( component . CuffedHandCount > 0 )
{
component . TryUncuff ( user ) ;
}
}
}
}
}