| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802 |
- using System.Linq;
- using Content.Shared.ActionBlocker;
- using Content.Shared.Administration.Components;
- using Content.Shared.Administration.Logs;
- using Content.Shared.Alert;
- using Content.Shared.Buckle.Components;
- using Content.Shared.Cuffs.Components;
- using Content.Shared.Database;
- using Content.Shared.DoAfter;
- using Content.Shared.Hands;
- 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.Events;
- using Content.Shared.Inventory.VirtualItem;
- using Content.Shared.Item;
- using Content.Shared.Movement.Events;
- using Content.Shared.Movement.Pulling.Events;
- using Content.Shared.Popups;
- using Content.Shared.Pulling.Events;
- using Content.Shared.Rejuvenate;
- using Content.Shared.Stunnable;
- using Content.Shared.Timing;
- using Content.Shared.Verbs;
- using Content.Shared.Weapons.Melee.Events;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Containers;
- using Robust.Shared.Network;
- using Robust.Shared.Player;
- using Robust.Shared.Serialization;
- using Robust.Shared.Utility;
- using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent;
- namespace Content.Shared.Cuffs
- {
- // TODO remove all the IsServer() checks.
- public abstract partial class SharedCuffableSystem : EntitySystem
- {
- [Dependency] private readonly IComponentFactory _componentFactory = default!;
- [Dependency] private readonly INetManager _net = default!;
- [Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
- [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
- [Dependency] private readonly AlertsSystem _alerts = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedContainerSystem _container = default!;
- [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
- [Dependency] private readonly SharedHandsSystem _hands = default!;
- [Dependency] private readonly SharedVirtualItemSystem _virtualItem = default!;
- [Dependency] private readonly SharedInteractionSystem _interaction = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly UseDelaySystem _delay = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<CuffableComponent, HandCountChangedEvent>(OnHandCountChanged);
- SubscribeLocalEvent<UncuffAttemptEvent>(OnUncuffAttempt);
- SubscribeLocalEvent<CuffableComponent, EntRemovedFromContainerMessage>(OnCuffsRemovedFromContainer);
- SubscribeLocalEvent<CuffableComponent, EntInsertedIntoContainerMessage>(OnCuffsInsertedIntoContainer);
- SubscribeLocalEvent<CuffableComponent, RejuvenateEvent>(OnRejuvenate);
- SubscribeLocalEvent<CuffableComponent, ComponentInit>(OnStartup);
- SubscribeLocalEvent<CuffableComponent, AttemptStopPullingEvent>(HandleStopPull);
- SubscribeLocalEvent<CuffableComponent, RemoveCuffsAlertEvent>(OnRemoveCuffsAlert);
- SubscribeLocalEvent<CuffableComponent, UpdateCanMoveEvent>(HandleMoveAttempt);
- SubscribeLocalEvent<CuffableComponent, IsEquippingAttemptEvent>(OnEquipAttempt);
- SubscribeLocalEvent<CuffableComponent, IsUnequippingAttemptEvent>(OnUnequipAttempt);
- SubscribeLocalEvent<CuffableComponent, BeingPulledAttemptEvent>(OnBeingPulledAttempt);
- SubscribeLocalEvent<CuffableComponent, BuckleAttemptEvent>(OnBuckleAttemptEvent);
- SubscribeLocalEvent<CuffableComponent, UnbuckleAttemptEvent>(OnUnbuckleAttemptEvent);
- SubscribeLocalEvent<CuffableComponent, GetVerbsEvent<Verb>>(AddUncuffVerb);
- SubscribeLocalEvent<CuffableComponent, UnCuffDoAfterEvent>(OnCuffableDoAfter);
- SubscribeLocalEvent<CuffableComponent, PullStartedMessage>(OnPull);
- SubscribeLocalEvent<CuffableComponent, PullStoppedMessage>(OnPull);
- SubscribeLocalEvent<CuffableComponent, DropAttemptEvent>(CheckAct);
- SubscribeLocalEvent<CuffableComponent, PickupAttemptEvent>(CheckAct);
- SubscribeLocalEvent<CuffableComponent, AttackAttemptEvent>(CheckAct);
- SubscribeLocalEvent<CuffableComponent, UseAttemptEvent>(CheckAct);
- SubscribeLocalEvent<CuffableComponent, InteractionAttemptEvent>(CheckInteract);
- SubscribeLocalEvent<HandcuffComponent, AfterInteractEvent>(OnCuffAfterInteract);
- SubscribeLocalEvent<HandcuffComponent, MeleeHitEvent>(OnCuffMeleeHit);
- SubscribeLocalEvent<HandcuffComponent, AddCuffDoAfterEvent>(OnAddCuffDoAfter);
- SubscribeLocalEvent<HandcuffComponent, VirtualItemDeletedEvent>(OnCuffVirtualItemDeleted);
- }
- private void CheckInteract(Entity<CuffableComponent> ent, ref InteractionAttemptEvent args)
- {
- if (!ent.Comp.CanStillInteract)
- args.Cancelled = true;
- }
- private void OnUncuffAttempt(ref UncuffAttemptEvent args)
- {
- if (args.Cancelled)
- return;
- if (!Exists(args.User) || Deleted(args.User))
- {
- // Should this even be possible?
- args.Cancelled = true;
- return;
- }
- // If the user is the target, special logic applies.
- // This is because the CanInteract blocking of the cuffs prevents self-uncuff.
- if (args.User == args.Target)
- {
- if (!TryComp<CuffableComponent>(args.User, out var cuffable))
- {
- DebugTools.Assert($"{args.User} tried to uncuff themselves but they are not cuffable.");
- return;
- }
- // We temporarily allow interactions so the cuffable system does not block itself.
- // It's assumed that this will always be false.
- // Otherwise they would not be trying to uncuff themselves.
- cuffable.CanStillInteract = true;
- Dirty(args.User, cuffable);
- if (!_actionBlocker.CanInteract(args.User, args.User))
- args.Cancelled = true;
- cuffable.CanStillInteract = false;
- Dirty(args.User, cuffable);
- }
- else
- {
- // Check if the user can interact.
- if (!_actionBlocker.CanInteract(args.User, args.Target))
- args.Cancelled = true;
- }
- if (args.Cancelled)
- {
- _popup.PopupClient(Loc.GetString("cuffable-component-cannot-interact-message"), args.Target, args.User);
- }
- }
- private void OnStartup(EntityUid uid, CuffableComponent component, ComponentInit args)
- {
- component.Container = _container.EnsureContainer<Container>(uid, _componentFactory.GetComponentName(component.GetType()));
- }
- private void OnRejuvenate(EntityUid uid, CuffableComponent component, RejuvenateEvent args)
- {
- _container.EmptyContainer(component.Container, true);
- }
- private void OnCuffsRemovedFromContainer(EntityUid uid, CuffableComponent component, EntRemovedFromContainerMessage args)
- {
- // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
- if (args.Container.ID != component.Container?.ID)
- return;
- _virtualItem.DeleteInHandsMatching(uid, args.Entity);
- UpdateCuffState(uid, component);
- }
- private void OnCuffsInsertedIntoContainer(EntityUid uid, CuffableComponent component, ContainerModifiedMessage args)
- {
- if (args.Container == component.Container)
- UpdateCuffState(uid, component);
- }
- public void UpdateCuffState(EntityUid uid, CuffableComponent component)
- {
- var canInteract = TryComp(uid, out HandsComponent? hands) && hands.Hands.Count > component.CuffedHandCount;
- if (canInteract == component.CanStillInteract)
- return;
- component.CanStillInteract = canInteract;
- Dirty(uid, component);
- _actionBlocker.UpdateCanMove(uid);
- if (component.CanStillInteract)
- _alerts.ClearAlert(uid, component.CuffedAlert);
- else
- _alerts.ShowAlert(uid, component.CuffedAlert);
- var ev = new CuffedStateChangeEvent();
- RaiseLocalEvent(uid, ref ev);
- }
- private void OnBeingPulledAttempt(EntityUid uid, CuffableComponent component, BeingPulledAttemptEvent args)
- {
- if (!TryComp<PullableComponent>(uid, out var pullable))
- return;
- if (pullable.Puller != null && !component.CanStillInteract) // If we are being pulled already and cuffed, we can't get pulled again.
- args.Cancel();
- }
- private void OnBuckleAttempt(Entity<CuffableComponent> ent, EntityUid? user, ref bool cancelled, bool buckling, bool popup)
- {
- if (cancelled || user != ent.Owner)
- return;
- if (!TryComp<HandsComponent>(ent, out var hands) || ent.Comp.CuffedHandCount < hands.Count)
- return;
- cancelled = true;
- if (!popup)
- return;
- var message = buckling
- ? Loc.GetString("handcuff-component-cuff-interrupt-buckled-message")
- : Loc.GetString("handcuff-component-cuff-interrupt-unbuckled-message");
- _popup.PopupClient(message, ent, user);
- }
- private void OnBuckleAttemptEvent(Entity<CuffableComponent> ent, ref BuckleAttemptEvent args)
- {
- OnBuckleAttempt(ent, args.User, ref args.Cancelled, true, args.Popup);
- }
- private void OnUnbuckleAttemptEvent(Entity<CuffableComponent> ent, ref UnbuckleAttemptEvent args)
- {
- OnBuckleAttempt(ent, args.User, ref args.Cancelled, false, args.Popup);
- }
- private void OnPull(EntityUid uid, CuffableComponent component, PullMessage args)
- {
- if (!component.CanStillInteract)
- _actionBlocker.UpdateCanMove(uid);
- }
- private void HandleMoveAttempt(EntityUid uid, CuffableComponent component, UpdateCanMoveEvent args)
- {
- if (component.CanStillInteract || !EntityManager.TryGetComponent(uid, out PullableComponent? pullable) || !pullable.BeingPulled)
- return;
- args.Cancel();
- }
- private void HandleStopPull(EntityUid uid, CuffableComponent component, AttemptStopPullingEvent args)
- {
- if (args.User == null || !Exists(args.User.Value))
- return;
- if (args.User.Value == uid && !component.CanStillInteract)
- args.Cancelled = true;
- }
- private void OnRemoveCuffsAlert(Entity<CuffableComponent> ent, ref RemoveCuffsAlertEvent args)
- {
- if (args.Handled)
- return;
- TryUncuff(ent, ent, cuffable: ent.Comp);
- args.Handled = true;
- }
- private void AddUncuffVerb(EntityUid uid, CuffableComponent component, GetVerbsEvent<Verb> args)
- {
- // Can the user access the cuffs, and is there even anything to uncuff?
- if (!args.CanAccess || component.CuffedHandCount == 0 || args.Hands == null)
- return;
- // We only check can interact if the user is not uncuffing themselves. As a result, the verb will show up
- // when the user is incapacitated & trying to uncuff themselves, but TryUncuff() will still fail when
- // attempted.
- if (args.User != args.Target && !args.CanInteract)
- return;
- Verb verb = new()
- {
- Act = () => TryUncuff(uid, args.User, cuffable: component),
- DoContactInteraction = true,
- Text = Loc.GetString("uncuff-verb-get-data-text")
- };
- //TODO VERB ICON add uncuffing symbol? may re-use the alert symbol showing that you are currently cuffed?
- args.Verbs.Add(verb);
- }
- private void OnCuffableDoAfter(EntityUid uid, CuffableComponent component, UnCuffDoAfterEvent args)
- {
- if (args.Args.Target is not { } target || args.Args.Used is not { } used)
- return;
- if (args.Handled)
- return;
- args.Handled = true;
- var user = args.Args.User;
- if (!args.Cancelled)
- {
- Uncuff(target, user, used, component);
- }
- else
- {
- _popup.PopupClient(Loc.GetString("cuffable-component-remove-cuffs-fail-message"), user, user);
- }
- }
- private void OnCuffAfterInteract(EntityUid uid, HandcuffComponent component, AfterInteractEvent args)
- {
- if (args.Target is not { Valid: true } target)
- return;
- if (!args.CanReach)
- {
- _popup.PopupClient(Loc.GetString("handcuff-component-too-far-away-error"), args.User, args.User);
- return;
- }
- var result = TryCuffing(args.User, target, uid, component);
- args.Handled = result;
- }
- private void OnCuffMeleeHit(EntityUid uid, HandcuffComponent component, MeleeHitEvent args)
- {
- if (!args.HitEntities.Any())
- return;
- TryCuffing(args.User, args.HitEntities.First(), uid, component);
- args.Handled = true;
- }
- private void OnAddCuffDoAfter(EntityUid uid, HandcuffComponent component, AddCuffDoAfterEvent args)
- {
- var user = args.Args.User;
- if (!TryComp<CuffableComponent>(args.Args.Target, out var cuffable))
- return;
- var target = args.Args.Target.Value;
- if (args.Handled)
- return;
- args.Handled = true;
- if (!args.Cancelled && TryAddNewCuffs(target, user, uid, cuffable))
- {
- component.Used = true;
- _audio.PlayPredicted(component.EndCuffSound, uid, user);
- var popupText = (user == target)
- ? "handcuff-component-cuff-self-observer-success-message"
- : "handcuff-component-cuff-observer-success-message";
- _popup.PopupEntity(Loc.GetString(popupText,
- ("user", Identity.Name(user, EntityManager)), ("target", Identity.Entity(target, EntityManager))),
- target, Filter.Pvs(target, entityManager: EntityManager)
- .RemoveWhere(e => e.AttachedEntity == target || e.AttachedEntity == user), true);
- if (target == user)
- {
- _popup.PopupClient(Loc.GetString("handcuff-component-cuff-self-success-message"), user, user);
- _adminLog.Add(LogType.Action, LogImpact.Medium,
- $"{ToPrettyString(user):player} has cuffed himself");
- }
- else
- {
- _popup.PopupClient(Loc.GetString("handcuff-component-cuff-other-success-message",
- ("otherName", Identity.Name(target, EntityManager, user))), user, user);
- _popup.PopupClient(Loc.GetString("handcuff-component-cuff-by-other-success-message",
- ("otherName", Identity.Name(user, EntityManager, target))), target, target);
- _adminLog.Add(LogType.Action, LogImpact.Medium,
- $"{ToPrettyString(user):player} has cuffed {ToPrettyString(target):player}");
- }
- }
- else
- {
- if (target == user)
- {
- _popup.PopupClient(Loc.GetString("handcuff-component-cuff-interrupt-self-message"), user, user);
- }
- else
- {
- // TODO Fix popup message wording
- // This message assumes that the user being handcuffed is the one that caused the handcuff to fail.
- _popup.PopupClient(Loc.GetString("handcuff-component-cuff-interrupt-message",
- ("targetName", Identity.Name(target, EntityManager, user))), user, user);
- _popup.PopupClient(Loc.GetString("handcuff-component-cuff-interrupt-other-message",
- ("otherName", Identity.Name(user, EntityManager, target))), target, target);
- }
- }
- }
- private void OnCuffVirtualItemDeleted(EntityUid uid, HandcuffComponent component, VirtualItemDeletedEvent args)
- {
- Uncuff(args.User, null, uid, cuff: component);
- }
- /// <summary>
- /// Check the current amount of hands the owner has, and if there's less hands than active cuffs we remove some cuffs.
- /// </summary>
- private void OnHandCountChanged(Entity<CuffableComponent> ent, ref HandCountChangedEvent message)
- {
- var dirty = false;
- var handCount = CompOrNull<HandsComponent>(ent.Owner)?.Count ?? 0;
- while (ent.Comp.CuffedHandCount > handCount && ent.Comp.CuffedHandCount > 0)
- {
- dirty = true;
- var handcuffContainer = ent.Comp.Container;
- var handcuffEntity = handcuffContainer.ContainedEntities[^1];
- _transform.PlaceNextTo(handcuffEntity, ent.Owner);
- }
- if (dirty)
- {
- UpdateCuffState(ent.Owner, ent.Comp);
- }
- }
- /// <summary>
- /// Adds virtual cuff items to the user's hands.
- /// </summary>
- private void UpdateHeldItems(EntityUid uid, EntityUid handcuff, CuffableComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- // TODO we probably don't just want to use the generic virtual-item entity, and instead
- // want to add our own item, so that use-in-hand triggers an uncuff attempt and the like.
- if (!TryComp<HandsComponent>(uid, out var handsComponent))
- return;
- var freeHands = 0;
- foreach (var hand in _hands.EnumerateHands(uid, handsComponent))
- {
- if (hand.HeldEntity == null)
- {
- freeHands++;
- continue;
- }
- // Is this entity removable? (it might be an existing handcuff blocker)
- if (HasComp<UnremoveableComponent>(hand.HeldEntity))
- continue;
- _hands.DoDrop(uid, hand, true, handsComponent);
- freeHands++;
- if (freeHands == 2)
- break;
- }
- if (_virtualItem.TrySpawnVirtualItemInHand(handcuff, uid, out var virtItem1))
- EnsureComp<UnremoveableComponent>(virtItem1.Value);
- if (_virtualItem.TrySpawnVirtualItemInHand(handcuff, uid, out var virtItem2))
- EnsureComp<UnremoveableComponent>(virtItem2.Value);
- }
- /// <summary>
- /// Add a set of cuffs to an existing CuffedComponent.
- /// </summary>
- public bool TryAddNewCuffs(EntityUid target, EntityUid user, EntityUid handcuff, CuffableComponent? component = null, HandcuffComponent? cuff = null)
- {
- if (!Resolve(target, ref component) || !Resolve(handcuff, ref cuff))
- return false;
- if (!_interaction.InRangeUnobstructed(handcuff, target))
- return false;
- // if the amount of hands the target has is equal to or less than the amount of hands that are cuffed
- // don't apply the new set of cuffs
- // (how would you even end up with more cuffed hands than actual hands? either way accounting for it)
- if (TryComp<HandsComponent>(target, out var hands) && hands.Count <= component.CuffedHandCount)
- return false;
- // Shitmed Change Start
- EnsureComp<HandcuffComponent>(handcuff, out var handcuffsComp);
- handcuffsComp.Used = true;
- Dirty(handcuff, handcuffsComp);
- // Success!
- _hands.TryDrop(user, handcuff);
- var result = _container.Insert(handcuff, component.Container);
- // Shitmed Change End
- UpdateHeldItems(target, handcuff, component);
- return true;
- }
- /// <returns>False if the target entity isn't cuffable.</returns>
- public bool TryCuffing(EntityUid user, EntityUid target, EntityUid handcuff, HandcuffComponent? handcuffComponent = null, CuffableComponent? cuffable = null)
- {
- if (!Resolve(handcuff, ref handcuffComponent) || !Resolve(target, ref cuffable, false))
- return false;
- if (!TryComp<HandsComponent>(target, out var hands))
- {
- _popup.PopupClient(Loc.GetString("handcuff-component-target-has-no-hands-error",
- ("targetName", Identity.Name(target, EntityManager, user))), user, user);
- return true;
- }
- if (cuffable.CuffedHandCount >= hands.Count)
- {
- _popup.PopupClient(Loc.GetString("handcuff-component-target-has-no-free-hands-error",
- ("targetName", Identity.Name(target, EntityManager, user))), user, user);
- return true;
- }
- if (!_hands.CanDrop(user, handcuff))
- {
- _popup.PopupClient(Loc.GetString("handcuff-component-cannot-drop-cuffs", ("target", Identity.Name(target, EntityManager, user))), user, user);
- return false;
- }
- var cuffTime = handcuffComponent.CuffTime;
- if (HasComp<StunnedComponent>(target))
- cuffTime = MathF.Max(0.1f, cuffTime - handcuffComponent.StunBonus);
- if (HasComp<DisarmProneComponent>(target))
- cuffTime = 0.0f; // cuff them instantly.
- var doAfterEventArgs = new DoAfterArgs(EntityManager, user, cuffTime, new AddCuffDoAfterEvent(), handcuff, target, handcuff)
- {
- BreakOnMove = true,
- BreakOnWeightlessMove = false,
- BreakOnDamage = true,
- NeedHand = true,
- DistanceThreshold = 1f // shorter than default but still feels good
- };
- if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
- return true;
- var popupText = (user == target)
- ? "handcuff-component-start-cuffing-self-observer"
- : "handcuff-component-start-cuffing-observer";
- _popup.PopupEntity(Loc.GetString(popupText,
- ("user", Identity.Name(user, EntityManager)), ("target", Identity.Entity(target, EntityManager))),
- target, Filter.Pvs(target, entityManager: EntityManager)
- .RemoveWhere(e => e.AttachedEntity == target || e.AttachedEntity == user), true);
- if (target == user)
- {
- _popup.PopupClient(Loc.GetString("handcuff-component-target-self"), user, user);
- }
- else
- {
- _popup.PopupClient(Loc.GetString("handcuff-component-start-cuffing-target-message",
- ("targetName", Identity.Name(target, EntityManager, user))), user, user);
- _popup.PopupEntity(Loc.GetString("handcuff-component-start-cuffing-by-other-message",
- ("otherName", Identity.Name(user, EntityManager, target))), target, target);
- }
- _audio.PlayPredicted(handcuffComponent.StartCuffSound, handcuff, user);
- return true;
- }
- /// <summary>
- /// Checks if the target is handcuffed.
- /// </summary>
- /// /// <param name="target">The entity to be checked</param>
- /// <param name="requireFullyCuffed">when true, return false if the target is only partially cuffed (for things with more than 2 hands)</param>
- /// <returns></returns>
- public bool IsCuffed(Entity<CuffableComponent> target, bool requireFullyCuffed = true)
- {
- if (!TryComp<HandsComponent>(target, out var hands))
- return false;
- if (target.Comp.CuffedHandCount <= 0)
- return false;
- if (requireFullyCuffed && hands.Count > target.Comp.CuffedHandCount)
- return false;
- return true;
- }
- /// <summary>
- /// Attempt to uncuff a cuffed entity. Can be called by the cuffed entity, or another entity trying to help uncuff them.
- /// If the uncuffing succeeds, the cuffs will drop on the floor.
- /// </summary>
- /// <param name="target"></param>
- /// <param name="user">The cuffed entity</param>
- /// <param name="cuffsToRemove">Optional param for the handcuff entity to remove from the cuffed entity. If null, uses the most recently added handcuff entity.</param>
- /// <param name="cuffable"></param>
- /// <param name="cuff"></param>
- public void TryUncuff(EntityUid target, EntityUid user, EntityUid? cuffsToRemove = null, CuffableComponent? cuffable = null, HandcuffComponent? cuff = null)
- {
- if (!Resolve(target, ref cuffable))
- return;
- var isOwner = user == target;
- if (cuffsToRemove == null)
- {
- if (cuffable.Container.ContainedEntities.Count == 0)
- {
- return;
- }
- cuffsToRemove = cuffable.LastAddedCuffs;
- }
- else
- {
- if (!cuffable.Container.ContainedEntities.Contains(cuffsToRemove.Value))
- {
- Log.Warning("A user is trying to remove handcuffs that aren't in the owner's container. This should never happen!");
- }
- }
- if (!Resolve(cuffsToRemove.Value, ref cuff))
- return;
- var attempt = new UncuffAttemptEvent(user, target);
- RaiseLocalEvent(user, ref attempt, true);
- if (attempt.Cancelled)
- {
- return;
- }
- if (!isOwner && !_interaction.InRangeUnobstructed(user, target))
- {
- _popup.PopupClient(Loc.GetString("cuffable-component-cannot-remove-cuffs-too-far-message"), user, user);
- return;
- }
- var ev = new ModifyUncuffDurationEvent(user, target, isOwner ? cuff.BreakoutTime : cuff.UncuffTime);
- RaiseLocalEvent(user, ref ev);
- var uncuffTime = ev.Duration;
- if (isOwner)
- {
- if (!TryComp(cuffsToRemove.Value, out UseDelayComponent? useDelay))
- return;
- if (!_delay.TryResetDelay((cuffsToRemove.Value, useDelay), true))
- {
- return;
- }
- }
- var doAfterEventArgs = new DoAfterArgs(EntityManager, user, uncuffTime, new UnCuffDoAfterEvent(), target, target, cuffsToRemove)
- {
- BreakOnMove = true,
- BreakOnWeightlessMove = false,
- BreakOnDamage = true,
- NeedHand = true,
- RequireCanInteract = false, // Trust in UncuffAttemptEvent
- DistanceThreshold = 1f // shorter than default but still feels good
- };
- if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
- return;
- _adminLog.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user)} is trying to uncuff {ToPrettyString(target)}");
- var popupText = user == target
- ? "cuffable-component-start-uncuffing-self-observer"
- : "cuffable-component-start-uncuffing-observer";
- _popup.PopupEntity(
- Loc.GetString(popupText,
- ("user", Identity.Name(user, EntityManager)),
- ("target", Identity.Entity(target, EntityManager))),
- target,
- Filter.Pvs(target, entityManager: EntityManager)
- .RemoveWhere(e => e.AttachedEntity == target || e.AttachedEntity == user),
- true);
- if (target == user)
- {
- _popup.PopupClient(Loc.GetString("cuffable-component-start-uncuffing-self"), user, user);
- }
- else
- {
- _popup.PopupClient(Loc.GetString("cuffable-component-start-uncuffing-target-message",
- ("targetName", Identity.Name(target, EntityManager, user))),
- user,
- user);
- _popup.PopupEntity(Loc.GetString("cuffable-component-start-uncuffing-by-other-message",
- ("otherName", Identity.Name(user, EntityManager, target))),
- target,
- target);
- }
- _audio.PlayPredicted(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, target, user);
- }
- public void Uncuff(EntityUid target, EntityUid? user, EntityUid cuffsToRemove, CuffableComponent? cuffable = null, HandcuffComponent? cuff = null)
- {
- if (!Resolve(target, ref cuffable) || !Resolve(cuffsToRemove, ref cuff))
- return;
- if (!cuff.Used || cuff.Removing || TerminatingOrDeleted(cuffsToRemove) || TerminatingOrDeleted(target))
- return;
- if (user != null)
- {
- var attempt = new UncuffAttemptEvent(user.Value, target);
- RaiseLocalEvent(user.Value, ref attempt);
- if (attempt.Cancelled)
- return;
- }
- cuff.Removing = true;
- cuff.Used = false;
- _audio.PlayPredicted(cuff.EndUncuffSound, target, user);
- _container.Remove(cuffsToRemove, cuffable.Container);
- if (_net.IsServer)
- {
- // Handles spawning broken cuffs on server to avoid client misprediction
- if (cuff.BreakOnRemove)
- {
- QueueDel(cuffsToRemove);
- var trash = Spawn(cuff.BrokenPrototype, Transform(cuffsToRemove).Coordinates);
- _hands.PickupOrDrop(user, trash);
- }
- else
- {
- _hands.PickupOrDrop(user, cuffsToRemove);
- }
- }
- if (cuffable.CuffedHandCount == 0)
- {
- if (user != null)
- _popup.PopupClient(Loc.GetString("cuffable-component-remove-cuffs-success-message"), user.Value, user.Value);
- if (target != user && user != null)
- {
- _popup.PopupEntity(Loc.GetString("cuffable-component-remove-cuffs-by-other-success-message",
- ("otherName", Identity.Name(user.Value, EntityManager, user))), target, target);
- _adminLog.Add(LogType.Action, LogImpact.Medium,
- $"{ToPrettyString(user):player} has successfully uncuffed {ToPrettyString(target):player}");
- }
- else
- {
- _adminLog.Add(LogType.Action, LogImpact.Medium,
- $"{ToPrettyString(user):player} has successfully uncuffed themselves");
- }
- }
- else if (user != null)
- {
- if (user != target)
- {
- _popup.PopupClient(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message",
- ("cuffedHandCount", cuffable.CuffedHandCount),
- ("otherName", Identity.Name(user.Value, EntityManager, user.Value))), user.Value, user.Value);
- _popup.PopupEntity(Loc.GetString(
- "cuffable-component-remove-cuffs-by-other-partial-success-message",
- ("otherName", Identity.Name(user.Value, EntityManager, user.Value)),
- ("cuffedHandCount", cuffable.CuffedHandCount)), target, target);
- }
- else
- {
- _popup.PopupClient(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message",
- ("cuffedHandCount", cuffable.CuffedHandCount)), user.Value, user.Value);
- }
- }
- cuff.Removing = false;
- }
- #region ActionBlocker
- private void CheckAct(EntityUid uid, CuffableComponent component, CancellableEntityEventArgs args)
- {
- if (!component.CanStillInteract)
- args.Cancel();
- }
- private void OnEquipAttempt(EntityUid uid, CuffableComponent component, IsEquippingAttemptEvent args)
- {
- // is this a self-equip, or are they being stripped?
- if (args.Equipee == uid)
- CheckAct(uid, component, args);
- }
- private void OnUnequipAttempt(EntityUid uid, CuffableComponent component, IsUnequippingAttemptEvent args)
- {
- // is this a self-equip, or are they being stripped?
- if (args.Unequipee == uid)
- CheckAct(uid, component, args);
- }
- #endregion
- public IReadOnlyList<EntityUid> GetAllCuffs(CuffableComponent component)
- {
- return component.Container.ContainedEntities;
- }
- [Serializable, NetSerializable]
- private sealed partial class UnCuffDoAfterEvent : SimpleDoAfterEvent
- {
- }
- [Serializable, NetSerializable]
- private sealed partial class AddCuffDoAfterEvent : SimpleDoAfterEvent
- {
- }
- }
- }
|