2021-12-30 22:56:10 +01:00
using Content.Server.Cuffs.Components ;
2022-05-13 14:59:57 +10:00
using Content.Server.DoAfter ;
2022-08-24 10:50:31 -04:00
using Content.Server.Ensnaring ;
using Content.Server.Ensnaring.Components ;
2021-12-30 22:56:10 +01:00
using Content.Server.Hands.Components ;
2022-10-16 06:00:04 +13:00
using Content.Shared.CombatMode ;
2021-12-30 22:56:10 +01:00
using Content.Shared.Hands.Components ;
2022-05-13 14:59:57 +10:00
using Content.Shared.Hands.EntitySystems ;
2022-07-10 18:36:53 -07:00
using Content.Shared.IdentityManagement ;
2022-05-13 14:59:57 +10:00
using Content.Shared.Interaction.Events ;
2021-12-30 22:56:10 +01:00
using Content.Shared.Inventory ;
2022-05-13 14:59:57 +10:00
using Content.Shared.Popups ;
2021-12-30 22:56:10 +01:00
using Content.Shared.Strip.Components ;
2021-10-05 14:29:03 +11:00
using Content.Shared.Verbs ;
using Robust.Server.GameObjects ;
2022-06-22 19:58:53 -04:00
using Robust.Shared.Player ;
2022-10-16 06:00:04 +13:00
using System.Threading ;
2022-10-16 03:32:00 -03:00
using Content.Server.Administration.Logs ;
using Content.Shared.Database ;
2021-10-05 14:29:03 +11:00
namespace Content.Server.Strip
{
public sealed class StrippableSystem : EntitySystem
{
2022-05-13 14:59:57 +10:00
[Dependency] private readonly SharedHandsSystem _handsSystem = default ! ;
2021-12-30 22:56:10 +01:00
[Dependency] private readonly InventorySystem _inventorySystem = default ! ;
2022-05-13 14:59:57 +10:00
[Dependency] private readonly DoAfterSystem _doAfterSystem = default ! ;
2022-06-22 19:58:53 -04:00
[Dependency] private readonly SharedPopupSystem _popupSystem = default ! ;
2022-08-24 10:50:31 -04:00
[Dependency] private readonly EnsnareableSystem _ensnaring = default ! ;
2022-09-05 22:15:49 -04:00
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default ! ;
2022-10-16 03:32:00 -03:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2022-05-13 14:59:57 +10:00
// TODO: ECS popups. Not all of these have ECS equivalents yet.
2021-10-05 14:29:03 +11:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2022-02-10 15:30:59 +13:00
SubscribeLocalEvent < StrippableComponent , GetVerbsEvent < Verb > > ( AddStripVerb ) ;
2022-05-13 14:59:57 +10:00
// BUI
2022-10-16 06:00:04 +13:00
SubscribeLocalEvent < StrippableComponent , StrippingSlotButtonPressed > ( OnStripButtonPressed ) ;
SubscribeLocalEvent < EnsnareableComponent , StrippingEnsnareButtonPressed > ( OnStripEnsnareMessage ) ;
2022-05-13 14:59:57 +10:00
}
2022-10-16 06:00:04 +13:00
private void OnStripEnsnareMessage ( EntityUid uid , EnsnareableComponent component , StrippingEnsnareButtonPressed args )
2022-08-24 10:50:31 -04:00
{
if ( args . Session . AttachedEntity is not { Valid : true } user )
return ;
2022-10-16 06:00:04 +13:00
foreach ( var entity in component . Container . ContainedEntities )
2022-08-24 10:50:31 -04:00
{
if ( ! TryComp < EnsnaringComponent > ( entity , out var ensnaring ) )
continue ;
_ensnaring . TryFree ( component . Owner , ensnaring , user ) ;
return ;
}
}
2022-10-16 06:00:04 +13:00
private void OnStripButtonPressed ( EntityUid uid , StrippableComponent component , StrippingSlotButtonPressed args )
2022-05-13 14:59:57 +10:00
{
if ( args . Session . AttachedEntity is not { Valid : true } user | |
! TryComp < HandsComponent > ( user , out var userHands ) )
return ;
2022-10-16 06:00:04 +13:00
if ( args . IsHand )
2022-05-13 14:59:57 +10:00
{
2022-10-16 06:00:04 +13:00
StripHand ( uid , user , args . Slot , component , userHands ) ;
return ;
2022-05-13 14:59:57 +10:00
}
2022-10-16 06:00:04 +13:00
if ( ! TryComp < InventoryComponent > ( component . Owner , out var inventory ) )
return ;
var hasEnt = _inventorySystem . TryGetSlotEntity ( component . Owner , args . Slot , out _ , inventory ) ;
if ( userHands . ActiveHandEntity ! = null & & ! hasEnt )
PlaceActiveHandItemInInventory ( user , args . Slot , component ) ;
else if ( userHands . ActiveHandEntity = = null & & hasEnt )
TakeItemFromInventory ( user , args . Slot , component ) ;
2022-05-13 14:59:57 +10:00
}
2022-10-16 06:00:04 +13:00
private void StripHand ( EntityUid target , EntityUid user , string handId , StrippableComponent component , HandsComponent userHands )
2022-05-13 14:59:57 +10:00
{
2022-10-16 06:00:04 +13:00
if ( ! TryComp < HandsComponent > ( target , out var targetHands )
| | ! targetHands . Hands . TryGetValue ( handId , out var hand ) )
2022-05-13 14:59:57 +10:00
return ;
2022-10-16 06:00:04 +13:00
// is the target a handcuff?
if ( TryComp ( hand . HeldEntity , out HandVirtualItemComponent ? virt )
& & TryComp ( target , out CuffableComponent ? cuff )
& & cuff . Container . Contains ( virt . BlockingEntity ) )
2022-05-13 14:59:57 +10:00
{
2022-10-16 06:00:04 +13:00
cuff . TryUncuff ( user , virt . BlockingEntity ) ;
return ;
2022-05-13 14:59:57 +10:00
}
2022-10-16 06:00:04 +13:00
if ( hand . IsEmpty & & userHands . ActiveHandEntity ! = null )
PlaceActiveHandItemInHands ( user , handId , component ) ;
else if ( ! hand . IsEmpty & & userHands . ActiveHandEntity = = null )
TakeItemFromHands ( user , handId , component ) ;
2022-05-13 14:59:57 +10:00
}
2022-09-05 22:15:49 -04:00
public void StartOpeningStripper ( EntityUid user , StrippableComponent component , bool openInCombat = false )
2022-05-13 14:59:57 +10:00
{
2022-09-05 22:15:49 -04:00
if ( TryComp < SharedCombatModeComponent > ( user , out var mode ) & & mode . IsInCombatMode & & ! openInCombat )
return ;
2022-05-13 14:59:57 +10:00
if ( TryComp < ActorComponent > ( user , out var actor ) )
{
2022-09-05 22:15:49 -04:00
if ( _userInterfaceSystem . SessionHasOpenUi ( component . Owner , StrippingUiKey . Key , actor . PlayerSession ) )
2022-05-13 14:59:57 +10:00
return ;
2022-09-05 22:15:49 -04:00
_userInterfaceSystem . TryOpen ( component . Owner , StrippingUiKey . Key , actor . PlayerSession ) ;
2022-05-13 14:59:57 +10:00
}
2021-12-30 22:56:10 +01:00
}
2022-02-10 15:30:59 +13:00
private void AddStripVerb ( EntityUid uid , StrippableComponent component , GetVerbsEvent < Verb > args )
2021-10-05 14:29:03 +11:00
{
if ( args . Hands = = null | | ! args . CanAccess | | ! args . CanInteract | | args . Target = = args . User )
return ;
2021-12-08 13:00:43 +01:00
if ( ! EntityManager . TryGetComponent ( args . User , out ActorComponent ? actor ) )
2021-10-05 14:29:03 +11:00
return ;
2022-05-13 14:59:57 +10:00
Verb verb = new ( )
{
Text = Loc . GetString ( "strip-verb-get-data-text" ) ,
IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png" ,
2022-09-05 22:15:49 -04:00
Act = ( ) = > StartOpeningStripper ( args . User , component , true ) ,
2022-05-13 14:59:57 +10:00
} ;
2021-10-05 14:29:03 +11:00
args . Verbs . Add ( verb ) ;
}
2022-05-13 14:59:57 +10:00
/// <summary>
/// Places item in user's active hand to an inventory slot.
/// </summary>
private async void PlaceActiveHandItemInInventory ( EntityUid user , string slot , StrippableComponent component )
{
var userHands = Comp < HandsComponent > ( user ) ;
bool Check ( )
{
if ( userHands . ActiveHand ? . HeldEntity is not { } held )
{
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-not-holding-anything" ) ) ;
return false ;
}
if ( ! _handsSystem . CanDropHeld ( user , userHands . ActiveHand ) )
{
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-cannot-drop" ) ) ;
return false ;
}
if ( ! _inventorySystem . HasSlot ( component . Owner , slot ) )
return false ;
if ( _inventorySystem . TryGetSlotEntity ( component . Owner , slot , out _ ) )
{
2022-05-20 12:42:24 +03:00
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-item-slot-occupied" , ( "owner" , component . Owner ) ) ) ;
2022-05-13 14:59:57 +10:00
return false ;
}
if ( ! _inventorySystem . CanEquip ( user , component . Owner , held , slot , out _ ) )
{
2022-05-20 12:42:24 +03:00
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-cannot-equip-message" , ( "owner" , component . Owner ) ) ) ;
2022-05-13 14:59:57 +10:00
return false ;
}
return true ;
}
2022-06-19 20:55:59 -04:00
if ( ! _inventorySystem . TryGetSlot ( component . Owner , slot , out var slotDef ) )
{
Logger . Error ( $"{ToPrettyString(user)} attempted to place an item in a non-existent inventory slot ({slot}) on {ToPrettyString(component.Owner)}" ) ;
return ;
}
2022-10-16 18:26:28 +13:00
var userEv = new BeforeStripEvent ( slotDef . StripTime ) ;
RaiseLocalEvent ( user , userEv ) ;
var ev = new BeforeGettingStrippedEvent ( userEv . Time , userEv . Stealth ) ;
RaiseLocalEvent ( component . Owner , ev ) ;
2022-07-30 23:02:27 -07:00
2022-10-16 18:26:28 +13:00
var doAfterArgs = new DoAfterEventArgs ( user , ev . Time , CancellationToken . None , component . Owner )
2022-05-13 14:59:57 +10:00
{
ExtraCheck = Check ,
BreakOnStun = true ,
BreakOnDamage = true ,
BreakOnTargetMove = true ,
BreakOnUserMove = true ,
NeedHand = true ,
} ;
2022-07-30 23:02:27 -07:00
if ( ! ev . Stealth & & Check ( ) & & userHands . ActiveHandEntity ! = null )
2022-06-22 19:58:53 -04:00
{
2022-07-30 23:02:27 -07:00
var message = Loc . GetString ( "strippable-component-alert-owner-insert" ,
( "user" , Identity . Entity ( user , EntityManager ) ) , ( "item" , userHands . ActiveHandEntity ) ) ;
_popupSystem . PopupEntity ( message , component . Owner , Filter . Entities ( component . Owner ) , PopupType . Large ) ;
2022-06-22 19:58:53 -04:00
}
2022-05-13 14:59:57 +10:00
var result = await _doAfterSystem . WaitDoAfter ( doAfterArgs ) ;
if ( result ! = DoAfterStatus . Finished ) return ;
if ( userHands . ActiveHand ? . HeldEntity is { } held
& & _handsSystem . TryDrop ( user , userHands . ActiveHand , handsComp : userHands ) )
{
_inventorySystem . TryEquip ( user , component . Owner , held , slot ) ;
2022-10-16 03:32:00 -03:00
_adminLogger . Add ( LogType . Stripping , LogImpact . Medium , $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(component.Owner):target}'s {slot} slot" ) ;
2022-05-13 14:59:57 +10:00
}
2022-10-16 03:32:00 -03:00
2022-05-13 14:59:57 +10:00
}
/// <summary>
/// Places item in user's active hand in one of the entity's hands.
/// </summary>
private async void PlaceActiveHandItemInHands ( EntityUid user , string handName , StrippableComponent component )
{
var hands = Comp < HandsComponent > ( component . Owner ) ;
var userHands = Comp < HandsComponent > ( user ) ;
bool Check ( )
{
if ( userHands . ActiveHandEntity = = null )
{
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-not-holding-anything" ) ) ;
return false ;
}
if ( ! _handsSystem . CanDropHeld ( user , userHands . ActiveHand ! ) )
{
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-cannot-drop" ) ) ;
return false ;
}
if ( ! hands . Hands . TryGetValue ( handName , out var hand )
| | ! _handsSystem . CanPickupToHand ( component . Owner , userHands . ActiveHandEntity . Value , hand , checkActionBlocker : false , hands ) )
{
2022-05-20 12:42:24 +03:00
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-cannot-put-message" , ( "owner" , component . Owner ) ) ) ;
2022-05-13 14:59:57 +10:00
return false ;
}
return true ;
}
2022-11-04 12:40:01 +13:00
var userEv = new BeforeStripEvent ( component . HandStripDelay ) ;
RaiseLocalEvent ( user , userEv ) ;
var ev = new BeforeGettingStrippedEvent ( userEv . Time , userEv . Stealth ) ;
RaiseLocalEvent ( component . Owner , ev ) ;
var doAfterArgs = new DoAfterEventArgs ( user , ev . Time , CancellationToken . None , component . Owner )
2022-05-13 14:59:57 +10:00
{
ExtraCheck = Check ,
BreakOnStun = true ,
BreakOnDamage = true ,
BreakOnTargetMove = true ,
BreakOnUserMove = true ,
NeedHand = true ,
} ;
2022-06-22 19:58:53 -04:00
if ( Check ( ) & & userHands . Hands . TryGetValue ( handName , out var handSlot ) )
{
if ( handSlot . HeldEntity ! = null )
{
2022-07-10 18:36:53 -07:00
_popupSystem . PopupEntity ( Loc . GetString ( "strippable-component-alert-owner-insert" , ( "user" , Identity . Entity ( user , EntityManager ) ) , ( "item" , handSlot . HeldEntity ) ) , component . Owner ,
2022-07-09 02:09:52 -07:00
Filter . Entities ( component . Owner ) , PopupType . Large ) ;
2022-06-22 19:58:53 -04:00
}
}
2022-05-13 14:59:57 +10:00
var result = await _doAfterSystem . WaitDoAfter ( doAfterArgs ) ;
if ( result ! = DoAfterStatus . Finished ) return ;
if ( userHands . ActiveHandEntity is not { } held )
return ;
_handsSystem . TryDrop ( user , checkActionBlocker : false , handsComp : userHands ) ;
_handsSystem . TryPickup ( component . Owner , held , handName , checkActionBlocker : false , animateUser : true , handsComp : hands ) ;
2022-10-16 03:32:00 -03:00
_adminLogger . Add ( LogType . Stripping , LogImpact . Medium , $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(component.Owner):target}'s hands" ) ;
2022-05-13 14:59:57 +10:00
// hand update will trigger strippable update
}
/// <summary>
/// Takes an item from the inventory and places it in the user's active hand.
/// </summary>
private async void TakeItemFromInventory ( EntityUid user , string slot , StrippableComponent component )
{
bool Check ( )
{
if ( ! _inventorySystem . HasSlot ( component . Owner , slot ) )
return false ;
if ( ! _inventorySystem . TryGetSlotEntity ( component . Owner , slot , out _ ) )
{
2022-05-20 12:42:24 +03:00
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-item-slot-free-message" , ( "owner" , component . Owner ) ) ) ;
2022-05-13 14:59:57 +10:00
return false ;
}
2022-11-04 12:40:01 +13:00
if ( ! _inventorySystem . CanUnequip ( user , component . Owner , slot , out var reason ) )
2022-05-13 14:59:57 +10:00
{
2022-11-04 12:40:01 +13:00
user . PopupMessageCursor ( reason ) ;
2022-05-13 14:59:57 +10:00
return false ;
}
return true ;
}
2022-06-19 20:55:59 -04:00
if ( ! _inventorySystem . TryGetSlot ( component . Owner , slot , out var slotDef ) )
{
2022-06-27 20:14:51 -04:00
Logger . Error ( $"{ToPrettyString(user)} attempted to take an item from a non-existent inventory slot ({slot}) on {ToPrettyString(component.Owner)}" ) ;
2022-06-19 20:55:59 -04:00
return ;
}
2022-10-16 18:26:28 +13:00
var userEv = new BeforeStripEvent ( slotDef . StripTime ) ;
RaiseLocalEvent ( user , userEv ) ;
var ev = new BeforeGettingStrippedEvent ( userEv . Time , userEv . Stealth ) ;
RaiseLocalEvent ( component . Owner , ev ) ;
2022-06-27 20:14:51 -04:00
2022-10-16 18:26:28 +13:00
var doAfterArgs = new DoAfterEventArgs ( user , ev . Time , CancellationToken . None , component . Owner )
2022-05-13 14:59:57 +10:00
{
ExtraCheck = Check ,
BreakOnStun = true ,
BreakOnDamage = true ,
BreakOnTargetMove = true ,
BreakOnUserMove = true ,
} ;
2022-07-30 23:02:27 -07:00
if ( ! ev . Stealth & & Check ( ) )
2022-06-22 19:58:53 -04:00
{
2022-07-30 23:02:27 -07:00
if ( slotDef . StripHidden )
{
2022-07-09 02:09:52 -07:00
_popupSystem . PopupEntity ( Loc . GetString ( "strippable-component-alert-owner-hidden" , ( "slot" , slot ) ) , component . Owner ,
Filter . Entities ( component . Owner ) , PopupType . Large ) ;
2022-07-30 23:02:27 -07:00
}
else if ( _inventorySystem . TryGetSlotEntity ( component . Owner , slot , out var slotItem ) )
2022-06-22 19:58:53 -04:00
{
2022-07-30 23:02:27 -07:00
_popupSystem . PopupEntity ( Loc . GetString ( "strippable-component-alert-owner" , ( "user" , Identity . Entity ( user , EntityManager ) ) , ( "item" , slotItem ) ) , component . Owner ,
Filter . Entities ( component . Owner ) , PopupType . Large ) ;
2022-06-22 19:58:53 -04:00
}
}
2022-05-13 14:59:57 +10:00
var result = await _doAfterSystem . WaitDoAfter ( doAfterArgs ) ;
if ( result ! = DoAfterStatus . Finished ) return ;
if ( _inventorySystem . TryGetSlotEntity ( component . Owner , slot , out var item ) & & _inventorySystem . TryUnequip ( user , component . Owner , slot ) )
{
// Raise a dropped event, so that things like gas tank internals properly deactivate when stripping
2022-06-22 09:53:41 +10:00
RaiseLocalEvent ( item . Value , new DroppedEvent ( user ) , true ) ;
2022-05-13 14:59:57 +10:00
_handsSystem . PickupOrDrop ( user , item . Value ) ;
2022-10-16 03:32:00 -03:00
_adminLogger . Add ( LogType . Stripping , LogImpact . Medium , $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(item.Value):item} from {ToPrettyString(component.Owner):target}" ) ;
2022-05-13 14:59:57 +10:00
}
}
/// <summary>
/// Takes an item from a hand and places it in the user's active hand.
/// </summary>
private async void TakeItemFromHands ( EntityUid user , string handName , StrippableComponent component )
{
var hands = Comp < HandsComponent > ( component . Owner ) ;
var userHands = Comp < HandsComponent > ( user ) ;
bool Check ( )
{
if ( ! hands . Hands . TryGetValue ( handName , out var hand ) | | hand . HeldEntity = = null )
{
2022-05-20 12:42:24 +03:00
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-item-slot-free-message" , ( "owner" , component . Owner ) ) ) ;
2022-05-13 14:59:57 +10:00
return false ;
}
if ( HasComp < HandVirtualItemComponent > ( hand . HeldEntity ) )
return false ;
if ( ! _handsSystem . CanDropHeld ( component . Owner , hand , false ) )
{
2022-05-20 12:42:24 +03:00
user . PopupMessageCursor ( Loc . GetString ( "strippable-component-cannot-drop-message" , ( "owner" , component . Owner ) ) ) ;
2022-05-13 14:59:57 +10:00
return false ;
}
return true ;
}
2022-10-16 18:26:28 +13:00
var userEv = new BeforeStripEvent ( component . HandStripDelay ) ;
RaiseLocalEvent ( user , userEv ) ;
var ev = new BeforeGettingStrippedEvent ( userEv . Time , userEv . Stealth ) ;
RaiseLocalEvent ( component . Owner , ev ) ;
var doAfterArgs = new DoAfterEventArgs ( user , ev . Time , CancellationToken . None , component . Owner )
2022-05-13 14:59:57 +10:00
{
ExtraCheck = Check ,
BreakOnStun = true ,
BreakOnDamage = true ,
BreakOnTargetMove = true ,
BreakOnUserMove = true ,
} ;
2022-06-22 19:58:53 -04:00
if ( Check ( ) & & hands . Hands . TryGetValue ( handName , out var handSlot ) )
{
if ( handSlot . HeldEntity ! = null )
{
2022-07-10 18:36:53 -07:00
_popupSystem . PopupEntity ( Loc . GetString ( "strippable-component-alert-owner" , ( "user" , Identity . Entity ( user , EntityManager ) ) , ( "item" , handSlot . HeldEntity ) ) , component . Owner , Filter . Entities ( component . Owner ) ) ;
2022-06-22 19:58:53 -04:00
}
}
2022-05-13 14:59:57 +10:00
var result = await _doAfterSystem . WaitDoAfter ( doAfterArgs ) ;
if ( result ! = DoAfterStatus . Finished ) return ;
if ( ! hands . Hands . TryGetValue ( handName , out var hand ) | | hand . HeldEntity is not { } held )
return ;
_handsSystem . TryDrop ( component . Owner , hand , checkActionBlocker : false , handsComp : hands ) ;
_handsSystem . PickupOrDrop ( user , held , handsComp : userHands ) ;
// hand update will trigger strippable update
2022-10-16 03:32:00 -03:00
_adminLogger . Add ( LogType . Stripping , LogImpact . Medium , $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(held):item} from {ToPrettyString(component.Owner):target}" ) ;
2022-05-13 14:59:57 +10:00
}
private sealed class OpenStrippingCompleteEvent
{
public readonly EntityUid User ;
public OpenStrippingCompleteEvent ( EntityUid user )
{
User = user ;
}
}
private sealed class OpenStrippingCancelledEvent
{
public readonly EntityUid User ;
public OpenStrippingCancelledEvent ( EntityUid user )
{
User = user ;
}
}
2021-10-05 14:29:03 +11:00
}
}