using Content.Shared.Ensnaring.Components;
using Robust.Client.GameObjects;
-namespace Content.Client.Ensnaring.Visualizers;
+namespace Content.Client.Ensnaring;
public sealed class EnsnareableSystem : SharedEnsnareableSystem
{
{
base.Initialize();
- SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<EnsnareableComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
- private void OnComponentInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
+ protected override void OnEnsnareInit(Entity<EnsnareableComponent> ent, ref ComponentInit args)
{
- if(!TryComp<SpriteComponent>(uid, out var sprite))
+ base.OnEnsnareInit(ent, ref args);
+
+ if(!TryComp<SpriteComponent>(ent.Owner, out var sprite))
return;
// TODO remove this, this should just be in yaml.
StyleClasses = { StyleBase.ButtonOpenRight }
};
- button.OnPressed += (_) => SendMessage(new StrippingEnsnareButtonPressed());
+ button.OnPressed += (_) => SendPredictedMessage(new StrippingEnsnareButtonPressed());
_strippingMenu.SnareContainer.AddChild(button);
}
// So for now: only stripping & examining
if (ev.Function == EngineKeyFunctions.Use)
{
- SendMessage(new StrippingSlotButtonPressed(slot.SlotName, slot is HandButton));
+ SendPredictedMessage(new StrippingSlotButtonPressed(slot.SlotName, slot is HandButton));
return;
}
}
public override void PopupCursor(string? message, PopupType type = PopupType.Small)
- => PopupCursorInternal(message, type, true);
+ {
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ PopupCursorInternal(message, type, true);
+ }
public override void PopupCursor(string? message, ICommonSession recipient, PopupType type = PopupType.Small)
{
+++ /dev/null
-using System.Linq;
-using Content.Server.Body.Systems;
-using Content.Shared.Alert;
-using Content.Shared.Body.Part;
-using Content.Shared.CombatMode.Pacification;
-using Content.Shared.Damage.Components;
-using Content.Shared.Damage.Systems;
-using Content.Shared.DoAfter;
-using Content.Shared.Ensnaring;
-using Content.Shared.Ensnaring.Components;
-using Content.Shared.IdentityManagement;
-using Content.Shared.StepTrigger.Systems;
-using Content.Shared.Throwing;
-
-namespace Content.Server.Ensnaring;
-
-public sealed partial class EnsnareableSystem
-{
- [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
- [Dependency] private readonly AlertsSystem _alerts = default!;
- [Dependency] private readonly BodySystem _body = default!;
- [Dependency] private readonly StaminaSystem _stamina = default!;
-
- public void InitializeEnsnaring()
- {
- SubscribeLocalEvent<EnsnaringComponent, ComponentRemove>(OnComponentRemove);
- SubscribeLocalEvent<EnsnaringComponent, StepTriggerAttemptEvent>(AttemptStepTrigger);
- SubscribeLocalEvent<EnsnaringComponent, StepTriggeredOffEvent>(OnStepTrigger);
- SubscribeLocalEvent<EnsnaringComponent, ThrowDoHitEvent>(OnThrowHit);
- SubscribeLocalEvent<EnsnaringComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
- SubscribeLocalEvent<EnsnareableComponent, RemoveEnsnareAlertEvent>(OnRemoveEnsnareAlert);
- }
-
- private void OnAttemptPacifiedThrow(Entity<EnsnaringComponent> ent, ref AttemptPacifiedThrowEvent args)
- {
- args.Cancel("pacified-cannot-throw-snare");
- }
-
- private void OnRemoveEnsnareAlert(Entity<EnsnareableComponent> ent, ref RemoveEnsnareAlertEvent args)
- {
- if (args.Handled)
- return;
-
- foreach (var ensnare in ent.Comp.Container.ContainedEntities)
- {
- if (!TryComp<EnsnaringComponent>(ensnare, out var ensnaringComponent))
- return;
-
- TryFree(ent, ent, ensnare, ensnaringComponent);
-
- args.Handled = true;
- // Only one snare at a time.
- break;
- }
- }
-
- private void OnComponentRemove(EntityUid uid, EnsnaringComponent component, ComponentRemove args)
- {
- if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnared))
- return;
-
- if (ensnared.IsEnsnared)
- ForceFree(uid, component);
- }
-
- private void AttemptStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggerAttemptEvent args)
- {
- args.Continue = true;
- }
-
- private void OnStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggeredOffEvent args)
- {
- TryEnsnare(args.Tripper, uid, component);
- }
-
- private void OnThrowHit(EntityUid uid, EnsnaringComponent component, ThrowDoHitEvent args)
- {
- if (!component.CanThrowTrigger)
- return;
-
- TryEnsnare(args.Target, uid, component);
- }
-
- /// <summary>
- /// Used where you want to try to ensnare an entity with the <see cref="EnsnareableComponent"/>
- /// </summary>
- /// <param name="target">The entity that will be ensnared</param>
- /// <paramref name="ensnare"> The entity that is used to ensnare</param>
- /// <param name="component">The ensnaring component</param>
- public void TryEnsnare(EntityUid target, EntityUid ensnare, EnsnaringComponent component)
- {
- //Don't do anything if they don't have the ensnareable component.
- if (!TryComp<EnsnareableComponent>(target, out var ensnareable))
- return;
-
- var legs = _body.GetBodyChildrenOfType(target, BodyPartType.Leg).Count();
- var ensnaredLegs = (2 * ensnareable.Container.ContainedEntities.Count);
- var freeLegs = legs - ensnaredLegs;
-
- if (freeLegs <= 0)
- return;
-
- // Apply stamina damage to target if they weren't ensnared before.
- if (ensnareable.IsEnsnared != true)
- {
- if (TryComp<StaminaComponent>(target, out var stamina))
- {
- _stamina.TakeStaminaDamage(target, component.StaminaDamage, with: ensnare);
- }
- }
-
- component.Ensnared = target;
- _container.Insert(ensnare, ensnareable.Container);
- ensnareable.IsEnsnared = true;
- Dirty(target, ensnareable);
-
- UpdateAlert(target, ensnareable);
- var ev = new EnsnareEvent(component.WalkSpeed, component.SprintSpeed);
- RaiseLocalEvent(target, ev);
- }
-
- /// <summary>
- /// Used where you want to try to free an entity with the <see cref="EnsnareableComponent"/>
- /// </summary>
- /// <param name="target">The entity that will be freed</param>
- /// <param name="user">The entity that is freeing the target</param>
- /// <param name="ensnare">The entity used to ensnare</param>
- /// <param name="component">The ensnaring component</param>
- public void TryFree(EntityUid target, EntityUid user, EntityUid ensnare, EnsnaringComponent component)
- {
- // Don't do anything if they don't have the ensnareable component.
- if (!HasComp<EnsnareableComponent>(target))
- return;
-
- var freeTime = user == target ? component.BreakoutTime : component.FreeTime;
- var breakOnMove = !component.CanMoveBreakout;
-
- var doAfterEventArgs = new DoAfterArgs(EntityManager, user, freeTime, new EnsnareableDoAfterEvent(), target, target: target, used: ensnare)
- {
- BreakOnMove = breakOnMove,
- BreakOnDamage = false,
- NeedHand = true,
- BreakOnDropItem = false,
- };
-
- if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
- return;
-
- if (user == target)
- _popup.PopupEntity(Loc.GetString("ensnare-component-try-free", ("ensnare", ensnare)), target, target);
- else
- _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", ensnare), ("user", Identity.Entity(target, EntityManager))), user, user);
- }
-
- /// <summary>
- /// Used to force free someone for things like if the <see cref="EnsnaringComponent"/> is removed
- /// </summary>
- public void ForceFree(EntityUid ensnare, EnsnaringComponent component)
- {
- if (component.Ensnared == null)
- return;
-
- if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnareable))
- return;
-
- var target = component.Ensnared.Value;
-
- _container.Remove(ensnare, ensnareable.Container, force: true);
- ensnareable.IsEnsnared = ensnareable.Container.ContainedEntities.Count > 0;
- Dirty(component.Ensnared.Value, ensnareable);
- component.Ensnared = null;
-
- UpdateAlert(target, ensnareable);
- var ev = new EnsnareRemoveEvent(component.WalkSpeed, component.SprintSpeed);
- RaiseLocalEvent(ensnare, ev);
- }
-
- /// <summary>
- /// Update the Ensnared alert for an entity.
- /// </summary>
- /// <param name="target">The entity that has been affected by a snare</param>
- public void UpdateAlert(EntityUid target, EnsnareableComponent component)
- {
- if (!component.IsEnsnared)
- _alerts.ClearAlert(target, component.EnsnaredAlert);
- else
- _alerts.ShowAlert(target, component.EnsnaredAlert);
- }
-}
-using Content.Server.Popups;
-using Content.Shared.DoAfter;
using Content.Shared.Ensnaring;
-using Content.Shared.Ensnaring.Components;
-using Content.Shared.Hands.EntitySystems;
-using Content.Shared.Popups;
-using Robust.Server.Containers;
-using Robust.Shared.Containers;
namespace Content.Server.Ensnaring;
-public sealed partial class EnsnareableSystem : SharedEnsnareableSystem
-{
- [Dependency] private readonly ContainerSystem _container = default!;
- [Dependency] private readonly SharedHandsSystem _hands = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- InitializeEnsnaring();
-
- SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnEnsnareableInit);
- SubscribeLocalEvent<EnsnareableComponent, EnsnareableDoAfterEvent>(OnDoAfter);
- }
-
- private void OnEnsnareableInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
- {
- component.Container = _container.EnsureContainer<Container>(uid, "ensnare");
- }
-
- private void OnDoAfter(EntityUid uid, EnsnareableComponent component, DoAfterEvent args)
- {
- if (args.Args.Target == null)
- return;
-
- if (args.Handled || !TryComp<EnsnaringComponent>(args.Args.Used, out var ensnaring))
- return;
-
- if (args.Cancelled || !_container.Remove(args.Args.Used.Value, component.Container))
- {
- _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.Args.Used)), uid, uid, PopupType.MediumCaution);
- return;
- }
-
- component.IsEnsnared = component.Container.ContainedEntities.Count > 0;
- Dirty(uid, component);
- ensnaring.Ensnared = null;
-
- _hands.PickupOrDrop(args.Args.User, args.Args.Used.Value);
-
- _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.Args.Used)), uid, uid, PopupType.Medium);
-
- UpdateAlert(args.Args.Target.Value, component);
- var ev = new EnsnareRemoveEvent(ensnaring.WalkSpeed, ensnaring.SprintSpeed);
- RaiseLocalEvent(uid, ev);
-
- args.Handled = true;
- }
-}
+public sealed class EnsnareableSystem : SharedEnsnareableSystem;
using Robust.Shared.Player;
using Robust.Shared.Utility;
-namespace Content.Server.Strip
-{
- public sealed class StrippableSystem : SharedStrippableSystem
- {
- [Dependency] private readonly InventorySystem _inventorySystem = default!;
- [Dependency] private readonly EnsnareableSystem _ensnaringSystem = 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 IAdminLogManager _adminLogger = default!;
-
- // TODO: ECS popups. Not all of these have ECS equivalents yet.
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<Verb>>(AddStripVerb);
- SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<ExamineVerb>>(AddStripExamineVerb);
-
- // BUI
- SubscribeLocalEvent<StrippableComponent, StrippingSlotButtonPressed>(OnStripButtonPressed);
- SubscribeLocalEvent<EnsnareableComponent, StrippingEnsnareButtonPressed>(OnStripEnsnareMessage);
-
- // DoAfters
- SubscribeLocalEvent<HandsComponent, DoAfterAttemptEvent<StrippableDoAfterEvent>>(OnStrippableDoAfterRunning);
- SubscribeLocalEvent<HandsComponent, StrippableDoAfterEvent>(OnStrippableDoAfterFinished);
- }
-
- private void AddStripVerb(EntityUid uid, StrippableComponent component, GetVerbsEvent<Verb> args)
- {
- if (args.Hands == null || !args.CanAccess || !args.CanInteract || args.Target == args.User)
- return;
-
- if (!HasComp<ActorComponent>(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;
-
- if (!HasComp<ActorComponent>(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 (userHands.ActiveHandEntity != null && !hasEnt)
- StartStripInsertInventory((user, userHands), strippable.Owner, userHands.ActiveHandEntity.Value, args.Slot);
- else if (userHands.ActiveHandEntity == null && 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 (!_handsSystem.TryGetHand(target.Owner, handId, out var handSlot))
- return;
-
- // Is the target a handcuff?
- if (TryComp<VirtualItemComponent>(handSlot.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 (user.Comp.ActiveHandEntity != null && handSlot.HeldEntity == null)
- StartStripInsertHand(user, target, user.Comp.ActiveHandEntity.Value, handId, targetStrippable);
- else if (user.Comp.ActiveHandEntity == null && handSlot.HeldEntity != null)
- StartStripRemoveHand(user, target, handSlot.HeldEntity.Value, handId, targetStrippable);
- }
-
- private void OnStripEnsnareMessage(EntityUid uid, EnsnareableComponent component, StrippingEnsnareButtonPressed args)
- {
- if (args.Actor is not { Valid: true } user)
- return;
-
- foreach (var entity in component.Container.ContainedEntities)
- {
- if (!TryComp<EnsnaringComponent>(entity, out var ensnaring))
- continue;
-
- _ensnaringSystem.TryFree(uid, user, entity, ensnaring);
- return;
- }
- }
-
- /// <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 (user.Comp.ActiveHand == null)
- return false;
-
- if (user.Comp.ActiveHandEntity == null)
- return false;
-
- if (user.Comp.ActiveHandEntity != held)
- return false;
-
- if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand))
- {
- _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"), user);
- return false;
- }
-
- if (_inventorySystem.TryGetSlotEntity(target, slot, out _))
- {
- _popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-occupied", ("owner", target)), user);
- return false;
- }
-
- if (!_inventorySystem.CanEquip(user, target, held, slot, out _))
- {
- _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-equip-message", ("owner", target)), user);
- 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", user.Comp.ActiveHandEntity!.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, handsComp: user.Comp))
- return;
-
- _inventorySystem.TryEquip(user, target, held, slot);
- _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", target)), user);
- return false;
- }
-
- if (slotItem != item)
- return false;
-
- if (!_inventorySystem.CanUnequip(user, target, slot, out var reason))
- {
- _popupSystem.PopupCursor(Loc.GetString(reason), user);
- return false;
- }
-
- return true;
- }
+namespace Content.Server.Strip;
- /// <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 (slotDef.StripHidden)
- _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");
-
- 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))
- return;
-
- RaiseLocalEvent(item, new DroppedEvent(user), true); // Gas tank internals etc.
-
- _handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth);
- _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{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 (user.Comp.ActiveHand == null)
- return false;
-
- if (user.Comp.ActiveHandEntity == null)
- return false;
-
- if (user.Comp.ActiveHandEntity != held)
- return false;
-
- if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand))
- {
- _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"), user);
- return false;
- }
-
- if (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp) ||
- !_handsSystem.CanPickupToHand(target, user.Comp.ActiveHandEntity.Value, handSlot, checkActionBlocker: false, target.Comp))
- {
- _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-put-message", ("owner", target)), user);
- 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", user.Comp.ActiveHandEntity!.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, handsComp: user.Comp);
- _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 (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp))
- {
- _popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", Identity.Name(target, EntityManager, user))), user);
- return false;
- }
-
- if (HasComp<VirtualItemComponent>(handSlot.HeldEntity))
- return false;
-
- if (handSlot.HeldEntity == null)
- return false;
-
- if (handSlot.HeldEntity != item)
- return false;
-
- if (!_handsSystem.CanDropHeld(target, handSlot, false))
- {
- _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop-message", ("owner", Identity.Name(target, EntityManager, user))), user);
- 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");
-
- 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, handsComp: target.Comp);
- _handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth, handsComp: user.Comp);
- _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{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);
+public sealed class StrippableSystem : SharedStrippableSystem
+{
- 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);
- }
- }
- }
}
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
namespace Content.Shared.Ensnaring.Components;
/// <summary>
/// Use this on an entity that you would like to be ensnared by anything that has the <see cref="EnsnaringComponent"/>
/// </summary>
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class EnsnareableComponent : Component
{
/// <summary>
/// How much should this slow down the entities walk?
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("walkSpeed")]
+ [DataField]
public float WalkSpeed = 1.0f;
/// <summary>
/// How much should this slow down the entities sprint?
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("sprintSpeed")]
+ [DataField]
public float SprintSpeed = 1.0f;
/// <summary>
/// Is this entity currently ensnared?
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("isEnsnared")]
+ [DataField, AutoNetworkedField]
public bool IsEnsnared;
/// <summary>
/// </summary>
public Container Container = default!;
- [DataField("sprite")]
+ [DataField]
public string? Sprite;
- [DataField("state")]
+ [DataField]
public string? State;
[DataField]
public sealed partial class RemoveEnsnareAlertEvent : BaseAlertEvent;
-[Serializable, NetSerializable]
-public sealed class EnsnareableComponentState : ComponentState
-{
- public readonly bool IsEnsnared;
-
- public EnsnareableComponentState(bool isEnsnared)
- {
- IsEnsnared = isEnsnared;
- }
-}
-
public sealed class EnsnaredChangedEvent : EntityEventArgs
{
public readonly bool IsEnsnared;
-using System.Threading;
+using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared.Ensnaring.Components;
/// <summary>
/// How long it should take to free someone else.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("freeTime")]
+ [DataField]
public float FreeTime = 3.5f;
/// <summary>
/// How long it should take for an entity to free themselves.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("breakoutTime")]
+ [DataField]
public float BreakoutTime = 30.0f;
/// <summary>
/// How much should this slow down the entities walk?
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("walkSpeed")]
+ [DataField]
public float WalkSpeed = 0.9f;
/// <summary>
/// How much should this slow down the entities sprint?
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("sprintSpeed")]
+ [DataField]
public float SprintSpeed = 0.9f;
/// <summary>
/// How much stamina does the ensnare sap
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("staminaDamage")]
+ [DataField]
public float StaminaDamage = 55f;
/// <summary>
/// Should this ensnare someone when thrown?
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("canThrowTrigger")]
+ [DataField]
public bool CanThrowTrigger;
/// <summary>
/// What is ensnared?
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("ensnared")]
+ [DataField]
public EntityUid? Ensnared;
/// <summary>
/// Should breaking out be possible when moving?
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("canMoveBreakout")]
+ [DataField]
public bool CanMoveBreakout;
+ [DataField]
+ public SoundSpecifier? EnsnareSound = new SoundPathSpecifier("/Audio/Effects/snap.ogg");
}
/// <summary>
SprintSpeed = sprintSpeed;
}
}
-
-/// <summary>
-/// Used for the do after event to free the entity that owns the <see cref="EnsnareableComponent"/>
-/// </summary>
-public sealed class FreeEnsnareDoAfterComplete : EntityEventArgs
-{
- public readonly EntityUid EnsnaringEntity;
-
- public FreeEnsnareDoAfterComplete(EntityUid ensnaringEntity)
- {
- EnsnaringEntity = ensnaringEntity;
- }
-}
-
-/// <summary>
-/// Used for the do after event when it fails to free the entity that owns the <see cref="EnsnareableComponent"/>
-/// </summary>
-public sealed class FreeEnsnareDoAfterCancel : EntityEventArgs
-{
- public readonly EntityUid EnsnaringEntity;
-
- public FreeEnsnareDoAfterCancel(EntityUid ensnaringEntity)
- {
- EnsnaringEntity = ensnaringEntity;
- }
-}
+using System.Linq;
+using Content.Shared.Alert;
+using Content.Shared.Body.Part;
+using Content.Shared.Body.Systems;
+using Content.Shared.CombatMode.Pacification;
+using Content.Shared.Damage.Components;
+using Content.Shared.Damage.Systems;
using Content.Shared.DoAfter;
using Content.Shared.Ensnaring.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.IdentityManagement;
using Content.Shared.Movement.Systems;
-using Robust.Shared.GameStates;
+using Content.Shared.Popups;
+using Content.Shared.StepTrigger.Systems;
+using Content.Shared.Strip.Components;
+using Content.Shared.Throwing;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
using Robust.Shared.Serialization;
namespace Content.Shared.Ensnaring;
public abstract class SharedEnsnareableSystem : EntitySystem
{
- [Dependency] private readonly MovementSpeedModifierSystem _speedModifier = default!;
+ [Dependency] private readonly AlertsSystem _alerts = default!;
+ [Dependency] private readonly MovementSpeedModifierSystem _speedModifier = default!;
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedBodySystem _body = default!;
+ [Dependency] protected readonly SharedContainerSystem Container = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] protected readonly SharedPopupSystem Popup = default!;
+ [Dependency] private readonly StaminaSystem _stamina = default!;
public override void Initialize()
{
base.Initialize();
+ SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnEnsnareInit);
SubscribeLocalEvent<EnsnareableComponent, RefreshMovementSpeedModifiersEvent>(MovementSpeedModify);
SubscribeLocalEvent<EnsnareableComponent, EnsnareEvent>(OnEnsnare);
SubscribeLocalEvent<EnsnareableComponent, EnsnareRemoveEvent>(OnEnsnareRemove);
SubscribeLocalEvent<EnsnareableComponent, EnsnaredChangedEvent>(OnEnsnareChange);
- SubscribeLocalEvent<EnsnareableComponent, ComponentGetState>(OnGetState);
- SubscribeLocalEvent<EnsnareableComponent, ComponentHandleState>(OnHandleState);
+ SubscribeLocalEvent<EnsnareableComponent, AfterAutoHandleStateEvent>(OnHandleState);
+ SubscribeLocalEvent<EnsnareableComponent, StrippingEnsnareButtonPressed>(OnStripEnsnareMessage);
+ SubscribeLocalEvent<EnsnareableComponent, RemoveEnsnareAlertEvent>(OnRemoveEnsnareAlert);
+ SubscribeLocalEvent<EnsnareableComponent, EnsnareableDoAfterEvent>(OnDoAfter);
+
+ SubscribeLocalEvent<EnsnaringComponent, ComponentRemove>(OnComponentRemove);
+ SubscribeLocalEvent<EnsnaringComponent, StepTriggerAttemptEvent>(AttemptStepTrigger);
+ SubscribeLocalEvent<EnsnaringComponent, StepTriggeredOffEvent>(OnStepTrigger);
+ SubscribeLocalEvent<EnsnaringComponent, ThrowDoHitEvent>(OnThrowHit);
+ SubscribeLocalEvent<EnsnaringComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
}
- private void OnHandleState(EntityUid uid, EnsnareableComponent component, ref ComponentHandleState args)
+ protected virtual void OnEnsnareInit(Entity<EnsnareableComponent> ent, ref ComponentInit args)
{
- if (args.Current is not EnsnareableComponentState state)
- return;
-
- if (state.IsEnsnared == component.IsEnsnared)
- return;
+ ent.Comp.Container = Container.EnsureContainer<Container>(ent.Owner, "ensnare");
+ }
- component.IsEnsnared = state.IsEnsnared;
+ private void OnHandleState(EntityUid uid, EnsnareableComponent component, ref AfterAutoHandleStateEvent args)
+ {
RaiseLocalEvent(uid, new EnsnaredChangedEvent(component.IsEnsnared));
}
- private void OnGetState(EntityUid uid, EnsnareableComponent component, ref ComponentGetState args)
+ private void OnDoAfter(EntityUid uid, EnsnareableComponent component, DoAfterEvent args)
{
- args.State = new EnsnareableComponentState(component.IsEnsnared);
+ if (args.Args.Target == null)
+ return;
+
+ if (args.Handled || !TryComp<EnsnaringComponent>(args.Args.Used, out var ensnaring))
+ return;
+
+ if (args.Cancelled || !Container.Remove(args.Args.Used.Value, component.Container))
+ {
+ if (args.User == args.Target)
+ Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.Args.Used)), uid, args.User, PopupType.MediumCaution);
+ else if (args.Target != null)
+ Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-fail-other", ("ensnare", args.Args.Used), ("user", args.Target)), uid, args.User, PopupType.MediumCaution);
+
+ return;
+ }
+
+ component.IsEnsnared = component.Container.ContainedEntities.Count > 0;
+ Dirty(uid, component);
+ ensnaring.Ensnared = null;
+
+ _hands.PickupOrDrop(args.Args.User, args.Args.Used.Value);
+
+ if (args.User == args.Target)
+ Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.Args.Used)), uid, args.User, PopupType.Medium);
+ else if (args.Target != null)
+ Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-complete-other", ("ensnare", args.Args.Used), ("user", args.Target)), uid, args.User, PopupType.Medium);
+
+ UpdateAlert(args.Args.Target.Value, component);
+ var ev = new EnsnareRemoveEvent(ensnaring.WalkSpeed, ensnaring.SprintSpeed);
+ RaiseLocalEvent(uid, ev);
+
+ args.Handled = true;
}
private void OnEnsnare(EntityUid uid, EnsnareableComponent component, EnsnareEvent args)
args.ModifySpeed(component.WalkSpeed, component.SprintSpeed);
}
+
+ /// <summary>
+ /// Used where you want to try to free an entity with the <see cref="EnsnareableComponent"/>
+ /// </summary>
+ /// <param name="target">The entity that will be freed</param>
+ /// <param name="user">The entity that is freeing the target</param>
+ /// <param name="ensnare">The entity used to ensnare</param>
+ /// <param name="component">The ensnaring component</param>
+ public void TryFree(EntityUid target, EntityUid user, EntityUid ensnare, EnsnaringComponent component)
+ {
+ // Don't do anything if they don't have the ensnareable component.
+ if (!HasComp<EnsnareableComponent>(target))
+ return;
+
+ var freeTime = user == target ? component.BreakoutTime : component.FreeTime;
+ var breakOnMove = !component.CanMoveBreakout;
+
+ var doAfterEventArgs = new DoAfterArgs(EntityManager, user, freeTime, new EnsnareableDoAfterEvent(), target, target: target, used: ensnare)
+ {
+ BreakOnMove = breakOnMove,
+ BreakOnDamage = false,
+ NeedHand = true,
+ BreakOnDropItem = false,
+ };
+
+ if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
+ return;
+
+ if (user == target)
+ Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free", ("ensnare", ensnare)), target, target);
+ else
+ Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-other", ("ensnare", ensnare), ("user", Identity.Entity(target, EntityManager))), user, user);
+ }
+
+ private void OnStripEnsnareMessage(EntityUid uid, EnsnareableComponent component, StrippingEnsnareButtonPressed args)
+ {
+ foreach (var entity in component.Container.ContainedEntities)
+ {
+ if (!TryComp<EnsnaringComponent>(entity, out var ensnaring))
+ continue;
+
+ TryFree(uid, args.Actor, entity, ensnaring);
+ return;
+ }
+ }
+
+ private void OnAttemptPacifiedThrow(Entity<EnsnaringComponent> ent, ref AttemptPacifiedThrowEvent args)
+ {
+ args.Cancel("pacified-cannot-throw-snare");
+ }
+
+ private void OnRemoveEnsnareAlert(Entity<EnsnareableComponent> ent, ref RemoveEnsnareAlertEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ foreach (var ensnare in ent.Comp.Container.ContainedEntities)
+ {
+ if (!TryComp<EnsnaringComponent>(ensnare, out var ensnaringComponent))
+ continue;
+
+ TryFree(ent, ent, ensnare, ensnaringComponent);
+
+ args.Handled = true;
+ // Only one snare at a time.
+ break;
+ }
+ }
+
+ private void OnComponentRemove(EntityUid uid, EnsnaringComponent component, ComponentRemove args)
+ {
+ if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnared))
+ return;
+
+ if (ensnared.IsEnsnared)
+ ForceFree(uid, component);
+ }
+
+ private void AttemptStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggerAttemptEvent args)
+ {
+ args.Continue = true;
+ }
+
+ private void OnStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggeredOffEvent args)
+ {
+ TryEnsnare(args.Tripper, uid, component);
+ }
+
+ private void OnThrowHit(EntityUid uid, EnsnaringComponent component, ThrowDoHitEvent args)
+ {
+ if (!component.CanThrowTrigger)
+ return;
+
+ if (TryEnsnare(args.Target, uid, component))
+ {
+ _audio.PlayPvs(component.EnsnareSound, uid);
+ }
+ }
+
+ /// <summary>
+ /// Used where you want to try to ensnare an entity with the <see cref="EnsnareableComponent"/>
+ /// </summary>
+ /// <param name="target">The entity that will be ensnared</param>
+ /// <paramref name="ensnare"> The entity that is used to ensnare</param>
+ /// <param name="component">The ensnaring component</param>
+ public bool TryEnsnare(EntityUid target, EntityUid ensnare, EnsnaringComponent component)
+ {
+ //Don't do anything if they don't have the ensnareable component.
+ if (!TryComp<EnsnareableComponent>(target, out var ensnareable))
+ return false;
+
+ // Need to insert before free legs check.
+ Container.Insert(ensnare, ensnareable.Container);
+
+ var legs = _body.GetBodyChildrenOfType(target, BodyPartType.Leg).Count();
+ var ensnaredLegs = (2 * ensnareable.Container.ContainedEntities.Count);
+ var freeLegs = legs - ensnaredLegs;
+
+ if (freeLegs > 0)
+ return false;
+
+ // Apply stamina damage to target if they weren't ensnared before.
+ if (ensnareable.IsEnsnared != true)
+ {
+ if (TryComp<StaminaComponent>(target, out var stamina))
+ {
+ _stamina.TakeStaminaDamage(target, component.StaminaDamage, with: ensnare, component: stamina);
+ }
+ }
+
+ component.Ensnared = target;
+ ensnareable.IsEnsnared = true;
+ Dirty(target, ensnareable);
+
+ UpdateAlert(target, ensnareable);
+ var ev = new EnsnareEvent(component.WalkSpeed, component.SprintSpeed);
+ RaiseLocalEvent(target, ev);
+ return true;
+ }
+
+ /// <summary>
+ /// Used to force free someone for things like if the <see cref="EnsnaringComponent"/> is removed
+ /// </summary>
+ public void ForceFree(EntityUid ensnare, EnsnaringComponent component)
+ {
+ if (component.Ensnared == null)
+ return;
+
+ if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnareable))
+ return;
+
+ var target = component.Ensnared.Value;
+
+ Container.Remove(ensnare, ensnareable.Container, force: true);
+ ensnareable.IsEnsnared = ensnareable.Container.ContainedEntities.Count > 0;
+ Dirty(component.Ensnared.Value, ensnareable);
+ component.Ensnared = null;
+
+ UpdateAlert(target, ensnareable);
+ var ev = new EnsnareRemoveEvent(component.WalkSpeed, component.SprintSpeed);
+ RaiseLocalEvent(ensnare, ev);
+ }
+
+ /// <summary>
+ /// Update the Ensnared alert for an entity.
+ /// </summary>
+ /// <param name="target">The entity that has been affected by a snare</param>
+ public void UpdateAlert(EntityUid target, EnsnareableComponent component)
+ {
+ if (!component.IsEnsnared)
+ _alerts.ClearAlert(target, component.EnsnaredAlert);
+ else
+ _alerts.ShowAlert(target, component.EnsnaredAlert);
+ }
}
// before we drop the item, check that it can be equipped in the first place.
if (!CanEquip(actor, held.Value, ev.Slot, out var reason))
{
- if (_gameTiming.IsFirstTimePredicted)
- _popup.PopupCursor(Loc.GetString(reason));
+ _popup.PopupCursor(Loc.GetString(reason));
return;
}
{
if (!Resolve(target, ref inventory, false))
{
- if(!silent && _gameTiming.IsFirstTimePredicted)
+ if(!silent)
_popup.PopupCursor(Loc.GetString("inventory-component-can-equip-cannot"));
return false;
}
if (!TryGetSlotContainer(target, slot, out var slotContainer, out var slotDefinition, inventory))
{
- if(!silent && _gameTiming.IsFirstTimePredicted)
+ if(!silent)
_popup.PopupCursor(Loc.GetString("inventory-component-can-equip-cannot"));
return false;
}
if (!force && !CanEquip(actor, target, itemUid, slot, out var reason, slotDefinition, inventory, clothing))
{
- if(!silent && _gameTiming.IsFirstTimePredicted)
+ if(!silent)
_popup.PopupCursor(Loc.GetString(reason));
return false;
}
if (!_containerSystem.Insert(itemUid, slotContainer))
{
- if(!silent && _gameTiming.IsFirstTimePredicted)
+ if(!silent)
_popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"));
return false;
}
if (!Resolve(target, ref inventory, false))
{
- if(!silent && _gameTiming.IsFirstTimePredicted)
+ if(!silent)
_popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"));
return false;
}
if (!TryGetSlotContainer(target, slot, out var slotContainer, out var slotDefinition, inventory))
{
- if(!silent && _gameTiming.IsFirstTimePredicted)
+ if(!silent)
_popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"));
return false;
}
if (!force && !CanUnequip(actor, target, slot, out var reason, slotContainer, slotDefinition, inventory))
{
- if(!silent && _gameTiming.IsFirstTimePredicted)
+ if(!silent)
_popup.PopupCursor(Loc.GetString(reason));
return false;
}
+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.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;
{
[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 (userHands.ActiveHandEntity != null && !hasEnt)
+ StartStripInsertInventory((user, userHands), strippable.Owner, userHands.ActiveHandEntity.Value, args.Slot);
+ else if (userHands.ActiveHandEntity == null && 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 (!_handsSystem.TryGetHand(target.Owner, handId, out var handSlot))
+ return;
+
+ // Is the target a handcuff?
+ if (TryComp<VirtualItemComponent>(handSlot.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 (user.Comp.ActiveHandEntity != null && handSlot.HeldEntity == null)
+ StartStripInsertHand(user, target, user.Comp.ActiveHandEntity.Value, handId, targetStrippable);
+ else if (user.Comp.ActiveHandEntity == null && handSlot.HeldEntity != null)
+ StartStripRemoveHand(user, target, handSlot.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 (user.Comp.ActiveHand == null)
+ return false;
+
+ if (user.Comp.ActiveHandEntity == null)
+ return false;
+
+ if (user.Comp.ActiveHandEntity != held)
+ return false;
+
+ if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand))
+ {
+ _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"));
+ return false;
+ }
+
+ if (_inventorySystem.TryGetSlotEntity(target, slot, out _))
+ {
+ _popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-occupied", ("owner", target)));
+ return false;
+ }
+
+ if (!_inventorySystem.CanEquip(user, target, held, slot, out _))
+ {
+ _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-equip-message", ("owner", target)));
+ 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", user.Comp.ActiveHandEntity!.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, handsComp: user.Comp))
+ return;
+
+ _inventorySystem.TryEquip(user, target, held, slot);
+ _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", target)));
+ 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 (slotDef.StripHidden)
+ _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");
+
+ 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))
+ return;
+
+ RaiseLocalEvent(item, new DroppedEvent(user), true); // Gas tank internals etc.
+
+ _handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth);
+ _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{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 (user.Comp.ActiveHand == null)
+ return false;
+
+ if (user.Comp.ActiveHandEntity == null)
+ return false;
+
+ if (user.Comp.ActiveHandEntity != held)
+ return false;
+
+ if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand))
+ {
+ _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"));
+ return false;
+ }
+
+ if (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp) ||
+ !_handsSystem.CanPickupToHand(target, user.Comp.ActiveHandEntity.Value, handSlot, checkActionBlocker: false, target.Comp))
+ {
+ _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-put-message", ("owner", target)));
+ 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", user.Comp.ActiveHandEntity!.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, handsComp: user.Comp);
+ _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 (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp))
+ {
+ _popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", Identity.Name(target, EntityManager, user))));
+ return false;
+ }
+
+ if (HasComp<VirtualItemComponent>(handSlot.HeldEntity))
+ return false;
+
+ if (handSlot.HeldEntity == null)
+ return false;
+
+ if (handSlot.HeldEntity != item)
+ return false;
+
+ if (!_handsSystem.CanDropHeld(target, handSlot, false))
+ {
+ _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop-message", ("owner", Identity.Name(target, EntityManager, user))));
+ 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");
+
+ 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, handsComp: target.Comp);
+ _handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth, handsComp: user.Comp);
+ _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{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)
ensnare-component-try-free-complete = You successfully free yourself from the {$ensnare}!
ensnare-component-try-free-fail = You fail to free yourself from the {$ensnare}!
+ensnare-component-try-free-complete-other = You successfully free {$user} from the {$ensnare}!
+ensnare-component-try-free-fail-other = You fail to free {$user} from the {$ensnare}!
ensnare-component-try-free-other = You start removing the {$ensnare} caught on {$user}!