fish-station/Content.Shared/Strip/SharedStrippableSystem.cs
VigersRay 6c158e6f0b Merge remote-tracking branch 'space-wizards/master'
# Conflicts:
#	.github/CODEOWNERS
#	.github/ISSUE_TEMPLATE/config.yml
#	.github/ISSUE_TEMPLATE/issue_report.md
#	Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
#	Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml
#	Content.Client/UserInterface/Systems/Ghost/Controls/Roles/MakeGhostRoleWindow.xaml
#	Content.Client/UserInterface/Systems/Ghost/Controls/Roles/MakeGhostRoleWindow.xaml.cs
#	Content.Client/Weapons/Ranged/Systems/GunSystem.cs
#	Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs
#	Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs
#	Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
#	Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs
#	Content.Server/Atmos/EntitySystems/FlammableSystem.cs
#	Content.Server/Chat/Systems/ChatSystem.cs
#	Content.Server/Destructible/Thresholds/DamageThreshold.cs
#	Content.Server/Entry/EntryPoint.cs
#	Content.Server/Fluids/EntitySystems/PuddleSystem.cs
#	Content.Server/GuideGenerator/ReagentEntry.cs
#	Content.Server/IoC/ServerContentIoC.cs
#	Content.Server/Medical/HealthAnalyzerSystem.cs
#	Content.Server/Medical/VomitSystem.cs
#	Content.Server/Radio/EntitySystems/HeadsetSystem.cs
#	Content.Server/Revenant/EntitySystems/RevenantSystem.cs
#	Content.Server/Weapons/Ranged/Systems/GunSystem.cs
#	Content.Shared/Armor/SharedArmorSystem.cs
#	Content.Shared/Bed/Sleep/SleepingSystem.cs
#	Content.Shared/Cabinet/ItemCabinetSystem.cs
#	Content.Shared/Chat/SharedChatSystem.Emote.cs
#	Content.Shared/Chat/SharedChatSystem.cs
#	Content.Shared/Clothing/EntitySystems/MaskSystem.cs
#	Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs
#	Content.Shared/Damage/Systems/DamageableSystem.cs
#	Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs
#	Content.Shared/EntityEffects/Effects/Transform/ExplosionEntityEffect.cs
#	Content.Shared/Humanoid/HumanoidCharacterAppearance.cs
#	Content.Shared/IoC/SharedContentIoC.cs
#	Content.Shared/Lock/LockSystem.cs
#	Content.Shared/Ninja/Components/NinjaSuitComponent.cs
#	Content.Shared/Preferences/HumanoidCharacterProfile.cs
#	Content.Shared/Revenant/Components/RevenantComponent.cs
#	Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs
#	Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs
#	Content.Shared/VendingMachines/SharedVendingMachineSystem.cs
#	Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
#	Content.Shared/Weapons/Ranged/HitscanPrototype.cs
#	Content.Shared/Xenoarchaeology/Artifact/XAT/BaseQueryUpdateXATSystem.cs
#	README.md
#	Resources/Audio/Weapons/attributions.yml
#	Resources/Locale/en-US/_strings/ghost/make-ghost-gui.ftl
#	Resources/Locale/en-US/_strings/guidebook/chemistry/conditions.ftl
#	Resources/Locale/en-US/_strings/guidebook/chemistry/effects.ftl
#	Resources/Locale/en-US/_strings/guidebook/chemistry/healthchange.ftl
#	Resources/Locale/en-US/_strings/guidebook/chemistry/statuseffects.ftl
#	Resources/Locale/en-US/_strings/job/job-description.ftl
#	Resources/Locale/en-US/_strings/medical/components/crew-monitoring-component.ftl
#	Resources/Locale/en-US/_strings/npc/hugbot.ftl
#	Resources/Locale/en-US/_strings/npc/npc-debug.ftl
#	Resources/Locale/en-US/_strings/speech/speech-triggers.ftl
#	Resources/Locale/en-US/_strings/ui/stat-values.ftl
#	Resources/Locale/en-US/guidebook/chemistry/conditions.ftl
#	Resources/Locale/en-US/guidebook/chemistry/effects.ftl
#	Resources/Locale/en-US/guidebook/chemistry/healthchange.ftl
#	Resources/Locale/en-US/guidebook/entity-effects/conditions.ftl
#	Resources/Locale/en-US/guidebook/entity-effects/effects.ftl
#	Resources/Locale/en-US/guidebook/entity-effects/healthchange.ftl
#	Resources/Maps/Shuttles/cargo_relic.yml
#	Resources/Maps/_Sunrise/Station/bagel.yml
#	Resources/Maps/_Sunrise/Station/barratry.yml
#	Resources/Maps/_Sunrise/Station/fland.yml
#	Resources/Maps/_Sunrise/Station/marathon.yml
#	Resources/Maps/_Sunrise/Station/oasis.yml
#	Resources/Maps/_Sunrise/Station/packed.yml
#	Resources/Maps/_Sunrise/Station/plasma.yml
#	Resources/Maps/_Sunrise/Station/relic.yml
#	Resources/Maps/_Sunrise/event/clon_mine.yml
#	Resources/Maps/_Sunrise/event/void.yml
#	Resources/Prototypes/Body/Prototypes/vox.yml
#	Resources/Prototypes/Catalog/Fills/Boxes/emergency.yml
#	Resources/Prototypes/Catalog/Fills/Crates/fun.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/security.yml
#	Resources/Prototypes/Entities/Clothing/Belt/belts.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
#	Resources/Prototypes/Entities/Mobs/Species/skeleton.yml
#	Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml
#	Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/pistol.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
#	Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml
#	Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml
#	Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml
#	Resources/Prototypes/Entities/Structures/Machines/lathe.yml
#	Resources/Prototypes/Entities/Structures/Specific/Atmospherics/sensor.yml
#	Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml
#	Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml
#	Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml
#	Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml
#	Resources/Prototypes/Loadouts/loadout_groups.yml
#	Resources/Prototypes/Objectives/objectiveGroups.yml
#	Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml
#	Resources/Prototypes/Reagents/medicine.yml
#	Resources/Prototypes/Reagents/narcotics.yml
#	Resources/Prototypes/Reagents/toxins.yml
#	Resources/Prototypes/Recipes/Lathes/Packs/biogen.yml
#	Resources/Prototypes/Recipes/Lathes/Packs/security.yml
#	Resources/Prototypes/Recipes/Lathes/biogen.yml
#	Resources/Prototypes/Roles/Antags/nukeops.yml
#	Resources/Prototypes/Roles/Antags/revolutionary.yml
#	Resources/Prototypes/Roles/Antags/thief.yml
#	Resources/Prototypes/Roles/Antags/traitor.yml
#	Resources/Prototypes/Roles/Antags/wizard.yml
#	Resources/Prototypes/Roles/Antags/zombie.yml
#	Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml
#	Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml
#	Resources/Prototypes/Roles/Jobs/Civilian/chef.yml
#	Resources/Prototypes/Roles/Jobs/Civilian/mime.yml
#	Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml
#	Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml
#	Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml
#	Resources/Prototypes/Roles/Jobs/Medical/chemist.yml
#	Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml
#	Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml
#	Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml
#	Resources/Prototypes/Roles/Jobs/Science/borg.yml
#	Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml
#	Resources/Prototypes/Roles/Jobs/Science/scientist.yml
#	Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml
#	Resources/Prototypes/Roles/Jobs/Security/security_officer.yml
#	Resources/Prototypes/Roles/Jobs/Security/warden.yml
#	Resources/Prototypes/Stacks/Materials/crystals.yml
#	Resources/Prototypes/Stacks/floor_tile_stacks.yml
#	Resources/Prototypes/tags.yml
#	Resources/ServerInfo/Guidebook/Science/ArtifactReports.xml
#	Resources/ServerInfo/Guidebook/Science/Xenoarchaeology.xml
#	Resources/Textures/Clothing/Hands/Gloves/Color/black.rsi/equipped-HAND.png
#	Resources/Textures/Clothing/Hands/Gloves/Color/black.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/mime.rsi/meta.json
#	Resources/Textures/Effects/speech.rsi/moth3.png
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
#	Resources/Textures/Structures/Machines/microwave.rsi/meta.json
#	Resources/Textures/Structures/Machines/microwave.rsi/mw.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mw0.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mw_running_unlit.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mw_unlit.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwb.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwbloody.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwbloody0.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwbloody1.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwbloodyo.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwo.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/meta.json
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mw.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mw0.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mw_running_unlit.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mw_unlit.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mwb.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mwo.png
#	Resources/migration.yml
#	RobustToolbox
2025-10-19 17:38:04 +02:00

709 lines
27 KiB
C#

using System.Linq;
using Content.Shared.Administration.Logs;
using Content.Shared.CombatMode;
using Content.Shared.Cuffs;
using Content.Shared.Cuffs.Components;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Popups;
using Content.Shared.Strip.Components;
using Content.Shared.Verbs;
using Robust.Shared.Utility;
namespace Content.Shared.Strip;
public abstract class SharedStrippableSystem : EntitySystem
{
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedCuffableSystem _cuffableSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<Verb>>(AddStripVerb);
SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<ExamineVerb>>(AddStripExamineVerb);
// BUI
SubscribeLocalEvent<StrippableComponent, StrippingSlotButtonPressed>(OnStripButtonPressed);
// DoAfters
SubscribeLocalEvent<HandsComponent, DoAfterAttemptEvent<StrippableDoAfterEvent>>(OnStrippableDoAfterRunning);
SubscribeLocalEvent<HandsComponent, StrippableDoAfterEvent>(OnStrippableDoAfterFinished);
SubscribeLocalEvent<StrippingComponent, CanDropTargetEvent>(OnCanDropOn);
SubscribeLocalEvent<StrippableComponent, CanDropDraggedEvent>(OnCanDrop);
SubscribeLocalEvent<StrippableComponent, DragDropDraggedEvent>(OnDragDrop);
SubscribeLocalEvent<StrippableComponent, ActivateInWorldEvent>(OnActivateInWorld);
}
private void AddStripVerb(EntityUid uid, StrippableComponent component, GetVerbsEvent<Verb> args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract || args.Target == args.User)
return;
Verb verb = new()
{
Text = Loc.GetString("strip-verb-get-data-text"),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
Act = () => TryOpenStrippingUi(args.User, (uid, component), true),
};
args.Verbs.Add(verb);
}
private void AddStripExamineVerb(EntityUid uid, StrippableComponent component, GetVerbsEvent<ExamineVerb> args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract || args.Target == args.User)
return;
ExamineVerb verb = new()
{
Text = Loc.GetString("strip-verb-get-data-text"),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
Act = () => TryOpenStrippingUi(args.User, (uid, component), true),
Category = VerbCategory.Examine,
};
args.Verbs.Add(verb);
}
private void OnStripButtonPressed(Entity<StrippableComponent> strippable, ref StrippingSlotButtonPressed args)
{
if (args.Actor is not { Valid: true } user ||
!TryComp<HandsComponent>(user, out var userHands))
return;
if (args.IsHand)
{
StripHand((user, userHands), (strippable.Owner, null), args.Slot, strippable);
return;
}
if (!TryComp<InventoryComponent>(strippable, out var inventory))
return;
var hasEnt = _inventorySystem.TryGetSlotEntity(strippable, args.Slot, out var held, inventory);
if (_handsSystem.GetActiveItem((user, userHands)) is { } activeItem && !hasEnt)
StartStripInsertInventory((user, userHands), strippable.Owner, activeItem, args.Slot);
else if (hasEnt)
StartStripRemoveInventory(user, strippable.Owner, held!.Value, args.Slot);
}
private void StripHand(
Entity<HandsComponent?> user,
Entity<HandsComponent?> target,
string handId,
StrippableComponent? targetStrippable)
{
if (!Resolve(user, ref user.Comp) ||
!Resolve(target, ref target.Comp) ||
!Resolve(target, ref targetStrippable))
return;
if (!target.Comp.CanBeStripped)
return;
var heldEntity = _handsSystem.GetHeldItem(target.Owner, handId);
// Is the target a handcuff?
if (TryComp<VirtualItemComponent>(heldEntity, out var virtualItem) &&
TryComp<CuffableComponent>(target.Owner, out var cuffable) &&
_cuffableSystem.GetAllCuffs(cuffable).Contains(virtualItem.BlockingEntity))
{
_cuffableSystem.TryUncuff(target.Owner, user, virtualItem.BlockingEntity, cuffable);
return;
}
if (_handsSystem.GetActiveItem(user.AsNullable()) is { } activeItem && heldEntity == null)
StartStripInsertHand(user, target, activeItem, handId, targetStrippable);
else if (heldEntity != null)
StartStripRemoveHand(user, target, heldEntity.Value, handId, targetStrippable);
}
/// <summary>
/// Checks whether the item is in a user's active hand and whether it can be inserted into the inventory slot.
/// </summary>
private bool CanStripInsertInventory(
Entity<HandsComponent?> user,
EntityUid target,
EntityUid held,
string slot)
{
if (!Resolve(user, ref user.Comp))
return false;
if (!_handsSystem.TryGetActiveItem(user, out var activeItem) || activeItem != held)
return false;
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHandId!))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"));
return false;
}
var targetIdentity = Identity.Entity(target, EntityManager);
if (_inventorySystem.TryGetSlotEntity(target, slot, out _))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-occupied", ("owner", targetIdentity)));
return false;
}
if (!_inventorySystem.CanEquip(user, target, held, slot, out _))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-equip-message", ("owner", targetIdentity)));
return false;
}
return true;
}
/// <summary>
/// Begins a DoAfter to insert the item in the user's active hand into the inventory slot.
/// </summary>
private void StartStripInsertInventory(
Entity<HandsComponent?> user,
EntityUid target,
EntityUid held,
string slot)
{
if (!Resolve(user, ref user.Comp))
return;
if (!CanStripInsertInventory(user, target, held, slot))
return;
if (!_inventorySystem.TryGetSlot(target, slot, out var slotDef))
{
Log.Error($"{ToPrettyString(user)} attempted to place an item in a non-existent inventory slot ({slot}) on {ToPrettyString(target)}");
return;
}
var (time, stealth) = GetStripTimeModifiers(user, target, held, slotDef.StripTime);
if (!stealth)
{
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert",
("user", Identity.Entity(user, EntityManager)),
("item", _handsSystem.GetActiveItem((user, user.Comp))!.Value)),
target,
target,
PopupType.Large);
}
var prefix = stealth ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s {slot} slot");
var doAfterArgs = new DoAfterArgs(EntityManager, user, time, new StrippableDoAfterEvent(true, true, slot), user, target, held)
{
Hidden = stealth,
AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnMove = true,
NeedHand = true,
DuplicateCondition = DuplicateConditions.SameTool
};
_doAfterSystem.TryStartDoAfter(doAfterArgs);
}
/// <summary>
/// Inserts the item in the user's active hand into the inventory slot.
/// </summary>
private void StripInsertInventory(
Entity<HandsComponent?> user,
EntityUid target,
EntityUid held,
string slot)
{
if (!Resolve(user, ref user.Comp))
return;
if (!CanStripInsertInventory(user, target, held, slot))
return;
if (!_handsSystem.TryDrop(user))
return;
_inventorySystem.TryEquip(user, target, held, slot, triggerHandContact: true);
_adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s {slot} slot");
}
/// <summary>
/// Checks whether the item can be removed from the target's inventory.
/// </summary>
private bool CanStripRemoveInventory(
EntityUid user,
EntityUid target,
EntityUid item,
string slot)
{
if (!_inventorySystem.TryGetSlotEntity(target, slot, out var slotItem))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", Identity.Entity(target, EntityManager))));
return false;
}
if (slotItem != item)
return false;
if (!_inventorySystem.CanUnequip(user, target, slot, out var reason))
{
_popupSystem.PopupCursor(Loc.GetString(reason));
return false;
}
return true;
}
/// <summary>
/// Begins a DoAfter to remove the item from the target's inventory and insert it in the user's active hand.
/// </summary>
private void StartStripRemoveInventory(
EntityUid user,
EntityUid target,
EntityUid item,
string slot)
{
if (!CanStripRemoveInventory(user, target, item, slot))
return;
if (!_inventorySystem.TryGetSlot(target, slot, out var slotDef))
{
Log.Error($"{ToPrettyString(user)} attempted to take an item from a non-existent inventory slot ({slot}) on {ToPrettyString(target)}");
return;
}
var (time, stealth) = GetStripTimeModifiers(user, target, item, slotDef.StripTime);
if (!stealth)
{
if (IsStripHidden(slotDef, user))
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-hidden", ("slot", slot)), target, target, PopupType.Large);
else
{
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner",
("user", Identity.Entity(user, EntityManager)),
("item", item)),
target,
target,
PopupType.Large);
}
}
var prefix = stealth ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}strip the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s {slot} slot");
_interactionSystem.DoContactInteraction(user, item);
var doAfterArgs = new DoAfterArgs(EntityManager, user, time, new StrippableDoAfterEvent(false, true, slot), user, target, item)
{
Hidden = stealth,
AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnMove = true,
NeedHand = true,
BreakOnHandChange = false, // Allow simultaneously removing multiple items.
DuplicateCondition = DuplicateConditions.SameTool
};
_doAfterSystem.TryStartDoAfter(doAfterArgs);
}
/// <summary>
/// Removes the item from the target's inventory and inserts it in the user's active hand.
/// </summary>
private void StripRemoveInventory(
EntityUid user,
EntityUid target,
EntityUid item,
string slot,
bool stealth)
{
if (!CanStripRemoveInventory(user, target, item, slot))
return;
if (!_inventorySystem.TryUnequip(user, target, slot, triggerHandContact: true))
return;
RaiseLocalEvent(item, new DroppedEvent(user), true); // Gas tank internals etc.
_handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth);
_adminLogger.Add(LogType.Stripping, LogImpact.High, $"{ToPrettyString(user):actor} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s {slot} slot");
}
/// <summary>
/// Checks whether the item in the user's active hand can be inserted into one of the target's hands.
/// </summary>
private bool CanStripInsertHand(
Entity<HandsComponent?> user,
Entity<HandsComponent?> target,
EntityUid held,
string handName)
{
if (!Resolve(user, ref user.Comp) ||
!Resolve(target, ref target.Comp))
return false;
if (!target.Comp.CanBeStripped)
return false;
if (!_handsSystem.TryGetActiveItem(user, out var activeItem) || activeItem != held)
return false;
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHandId!))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"));
return false;
}
if (!_handsSystem.CanPickupToHand(target, activeItem.Value, handName, checkActionBlocker: false, handsComp: target.Comp))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-put-message", ("owner", Identity.Entity(target, EntityManager))));
return false;
}
return true;
}
/// <summary>
/// Begins a DoAfter to insert the item in the user's active hand into one of the target's hands.
/// </summary>
private void StartStripInsertHand(
Entity<HandsComponent?> user,
Entity<HandsComponent?> target,
EntityUid held,
string handName,
StrippableComponent? targetStrippable = null)
{
if (!Resolve(user, ref user.Comp) ||
!Resolve(target, ref target.Comp) ||
!Resolve(target, ref targetStrippable))
return;
if (!CanStripInsertHand(user, target, held, handName))
return;
var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay);
if (!stealth)
{
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert-hand",
("user", Identity.Entity(user, EntityManager)),
("item", _handsSystem.GetActiveItem(user)!.Value)),
target,
target,
PopupType.Large);
}
var prefix = stealth ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");
var doAfterArgs = new DoAfterArgs(EntityManager, user, time, new StrippableDoAfterEvent(true, false, handName), user, target, held)
{
Hidden = stealth,
AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnMove = true,
NeedHand = true,
DuplicateCondition = DuplicateConditions.SameTool
};
_doAfterSystem.TryStartDoAfter(doAfterArgs);
}
/// <summary>
/// Places the item in the user's active hand into one of the target's hands.
/// </summary>
private void StripInsertHand(
Entity<HandsComponent?> user,
Entity<HandsComponent?> target,
EntityUid held,
string handName,
bool stealth)
{
if (!Resolve(user, ref user.Comp) ||
!Resolve(target, ref target.Comp))
return;
if (!CanStripInsertHand(user, target, held, handName))
return;
_handsSystem.TryDrop(user, checkActionBlocker: false);
_handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: stealth, animate: !stealth, handsComp: target.Comp);
_adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");
// Hand update will trigger strippable update.
}
/// <summary>
/// Checks whether the item is in the target's hand and whether it can be dropped.
/// </summary>
private bool CanStripRemoveHand(
EntityUid user,
Entity<HandsComponent?> target,
EntityUid item,
string handName)
{
if (!Resolve(target, ref target.Comp))
return false;
if (!target.Comp.CanBeStripped)
return false;
if (!_handsSystem.TryGetHand(target, handName, out _))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", Identity.Entity(target, EntityManager))));
return false;
}
if (!_handsSystem.TryGetHeldItem(target, handName, out var heldEntity))
return false;
if (HasComp<VirtualItemComponent>(heldEntity))
return false;
if (heldEntity != item)
return false;
if (!_handsSystem.CanDropHeld(target, handName, false))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop-message", ("owner", Identity.Entity(target, EntityManager))));
return false;
}
return true;
}
/// <summary>
/// Begins a DoAfter to remove the item from the target's hand and insert it in the user's active hand.
/// </summary>
private void StartStripRemoveHand(
Entity<HandsComponent?> user,
Entity<HandsComponent?> target,
EntityUid item,
string handName,
StrippableComponent? targetStrippable = null)
{
if (!Resolve(user, ref user.Comp) ||
!Resolve(target, ref target.Comp) ||
!Resolve(target, ref targetStrippable))
return;
if (!CanStripRemoveHand(user, target, item, handName))
return;
var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay);
if (!stealth)
{
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner",
("user", Identity.Entity(user, EntityManager)),
("item", item)),
target,
target);
}
var prefix = stealth ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}strip the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands");
_interactionSystem.DoContactInteraction(user, item);
var doAfterArgs = new DoAfterArgs(EntityManager, user, time, new StrippableDoAfterEvent(false, false, handName), user, target, item)
{
Hidden = stealth,
AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnMove = true,
NeedHand = true,
BreakOnHandChange = false, // Allow simultaneously removing multiple items.
DuplicateCondition = DuplicateConditions.SameTool
};
_doAfterSystem.TryStartDoAfter(doAfterArgs);
}
/// <summary>
/// Takes the item from the target's hand and inserts it in the user's active hand.
/// </summary>
private void StripRemoveHand(
Entity<HandsComponent?> user,
Entity<HandsComponent?> target,
EntityUid item,
string handName,
bool stealth)
{
if (!Resolve(user, ref user.Comp) ||
!Resolve(target, ref target.Comp))
return;
if (!CanStripRemoveHand(user, target, item, handName))
return;
_handsSystem.TryDrop(target, item, checkActionBlocker: false);
_handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth, handsComp: user.Comp);
_adminLogger.Add(LogType.Stripping, LogImpact.High, $"{ToPrettyString(user):actor} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands");
// Hand update will trigger strippable update.
}
private void OnStrippableDoAfterRunning(Entity<HandsComponent> entity, ref DoAfterAttemptEvent<StrippableDoAfterEvent> ev)
{
var args = ev.DoAfter.Args;
DebugTools.Assert(entity.Owner == args.User);
DebugTools.Assert(args.Target != null);
DebugTools.Assert(args.Used != null);
DebugTools.Assert(ev.Event.SlotOrHandName != null);
if (ev.Event.InventoryOrHand)
{
if ( ev.Event.InsertOrRemove && !CanStripInsertInventory((entity.Owner, entity.Comp), args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName) ||
!ev.Event.InsertOrRemove && !CanStripRemoveInventory(entity.Owner, args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName))
{
ev.Cancel();
}
}
else
{
if ( ev.Event.InsertOrRemove && !CanStripInsertHand((entity.Owner, entity.Comp), args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName) ||
!ev.Event.InsertOrRemove && !CanStripRemoveHand(entity.Owner, args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName))
{
ev.Cancel();
}
}
}
private void OnStrippableDoAfterFinished(Entity<HandsComponent> entity, ref StrippableDoAfterEvent ev)
{
if (ev.Cancelled)
return;
DebugTools.Assert(entity.Owner == ev.User);
DebugTools.Assert(ev.Target != null);
DebugTools.Assert(ev.Used != null);
DebugTools.Assert(ev.SlotOrHandName != null);
if (ev.InventoryOrHand)
{
if (ev.InsertOrRemove)
StripInsertInventory((entity.Owner, entity.Comp), ev.Target.Value, ev.Used.Value, ev.SlotOrHandName);
else
StripRemoveInventory(entity.Owner, ev.Target.Value, ev.Used.Value, ev.SlotOrHandName, ev.Args.Hidden);
}
else
{
if (ev.InsertOrRemove)
StripInsertHand((entity.Owner, entity.Comp), ev.Target.Value, ev.Used.Value, ev.SlotOrHandName, ev.Args.Hidden);
else
StripRemoveHand((entity.Owner, entity.Comp), ev.Target.Value, ev.Used.Value, ev.SlotOrHandName, ev.Args.Hidden);
}
}
private void OnActivateInWorld(EntityUid uid, StrippableComponent component, ActivateInWorldEvent args)
{
if (args.Handled || !args.Complex || args.Target == args.User)
return;
if (TryOpenStrippingUi(args.User, (uid, component)))
args.Handled = true;
}
/// <summary>
/// Modify the strip time via events. Raised directed at the item being stripped, the player stripping someone and the player being stripped.
/// </summary>
public (TimeSpan Time, bool Stealth) GetStripTimeModifiers(EntityUid user, EntityUid targetPlayer, EntityUid? targetItem, TimeSpan initialTime)
{
var itemEv = new BeforeItemStrippedEvent(initialTime, false);
if (targetItem != null)
RaiseLocalEvent(targetItem.Value, ref itemEv);
var userEv = new BeforeStripEvent(itemEv.Time, itemEv.Stealth);
RaiseLocalEvent(user, ref userEv);
var targetEv = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
RaiseLocalEvent(targetPlayer, ref targetEv);
return (targetEv.Time, targetEv.Stealth);
}
private void OnDragDrop(EntityUid uid, StrippableComponent component, ref DragDropDraggedEvent args)
{
// If the user drags a strippable thing onto themselves.
if (args.Handled || args.Target != args.User)
return;
// Sunrise-Start
if (!TryComp<StrippingComponent>(args.Target, out var strippingComp) || !strippingComp.UseDragDrop)
{
return;
}
// Sunrise-End
if (TryOpenStrippingUi(args.User, (uid, component)))
args.Handled = true;
}
public bool TryOpenStrippingUi(EntityUid user, Entity<StrippableComponent> target, bool openInCombat = false)
{
if (!openInCombat && TryComp<CombatModeComponent>(user, out var mode) && mode.IsInCombatMode)
return false;
if (!HasComp<StrippingComponent>(user))
return false;
_ui.OpenUi(target.Owner, StrippingUiKey.Key, user);
return true;
}
private void OnCanDropOn(EntityUid uid, StrippingComponent component, ref CanDropTargetEvent args)
{
var val = uid == args.User &&
HasComp<StrippableComponent>(args.Dragged) &&
HasComp<HandsComponent>(args.User) &&
HasComp<StrippingComponent>(args.User);
args.Handled |= val;
args.CanDrop |= val;
}
private void OnCanDrop(EntityUid uid, StrippableComponent component, ref CanDropDraggedEvent args)
{
args.CanDrop |= args.Target == args.User &&
HasComp<StrippingComponent>(args.User) &&
HasComp<HandsComponent>(args.User);
if (args.CanDrop)
args.Handled = true;
}
public bool IsStripHidden(SlotDefinition definition, EntityUid? viewer)
{
if (!definition.StripHidden)
return false;
if (viewer == null)
return true;
return !HasComp<BypassInteractionChecksComponent>(viewer);
}
}