diff --git a/Content.Client/GameObjects/Components/ActionBlocking/HandcuffComponent.cs b/Content.Client/GameObjects/Components/ActionBlocking/HandcuffComponent.cs
index adc7488425..9cee1ac181 100644
--- a/Content.Client/GameObjects/Components/ActionBlocking/HandcuffComponent.cs
+++ b/Content.Client/GameObjects/Components/ActionBlocking/HandcuffComponent.cs
@@ -1,4 +1,5 @@
-using Content.Shared.GameObjects.Components.ActionBlocking;
+#nullable enable
+using Content.Shared.GameObjects.Components.ActionBlocking;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
@@ -6,9 +7,10 @@ using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.ActionBlocking
{
[RegisterComponent]
+ [ComponentReference(typeof(SharedHandcuffComponent))]
public class HandcuffComponent : SharedHandcuffComponent
{
- public override void HandleComponentState(ComponentState curState, ComponentState nextState)
+ public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if (curState is not HandcuffedComponentState state)
{
diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
similarity index 95%
rename from Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs
rename to Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
index 2259df9c43..5910ba4a26 100644
--- a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/CuffUnitTest.cs
+++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
@@ -18,7 +18,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
[TestFixture]
[TestOf(typeof(CuffableComponent))]
[TestOf(typeof(HandcuffComponent))]
- public class CuffUnitTest : ContentIntegrationTest
+ public class HandCuffTest : ContentIntegrationTest
{
private const string PROTOTYPES = @"
- type: entity
@@ -77,7 +77,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
Assert.True(secondCuffs.TryGetComponent(out secondHandcuff!), $"Second handcuffs has no {nameof(HandcuffComponent)}");
// Test to ensure cuffed players register the handcuffs
- cuffed.AddNewCuffs(cuffs);
+ cuffed.TryAddNewCuffs(human, cuffs);
Assert.True(cuffed.CuffedHandCount > 0, "Handcuffing a player did not result in their hands being cuffed");
// Test to ensure a player with 4 hands will still only have 2 hands cuffed
@@ -86,7 +86,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
Assert.True(cuffed.CuffedHandCount == 2 && hands.Hands.Count() == 4, "Player doesn't have correct amount of hands cuffed");
// Test to give a player with 4 hands 2 sets of cuffs
- cuffed.AddNewCuffs(secondCuffs);
+ cuffed.TryAddNewCuffs(human, secondCuffs);
Assert.True(cuffed.CuffedHandCount == 4, "Player doesn't have correct amount of hands cuffed");
});
diff --git a/Content.Server/Alert/Click/RemoveCuffs.cs b/Content.Server/Alert/Click/RemoveCuffs.cs
new file mode 100644
index 0000000000..d4882a93c3
--- /dev/null
+++ b/Content.Server/Alert/Click/RemoveCuffs.cs
@@ -0,0 +1,25 @@
+#nullable enable
+using Content.Server.GameObjects.Components.ActionBlocking;
+using Content.Shared.Alert;
+using JetBrains.Annotations;
+using Robust.Shared.Serialization;
+
+namespace Content.Server.Alert.Click
+{
+ ///
+ /// Try to remove handcuffs from yourself
+ ///
+ [UsedImplicitly]
+ public class RemoveCuffs : IAlertClick
+ {
+ public void ExposeData(ObjectSerializer serializer) {}
+
+ public void AlertClicked(ClickAlertEventArgs args)
+ {
+ if (args.Player.TryGetComponent(out CuffableComponent? cuffableComponent))
+ {
+ cuffableComponent.TryUncuff(args.Player);
+ }
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs
index e82dada792..61354f3538 100644
--- a/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs
+++ b/Content.Server/GameObjects/Components/ActionBlocking/CuffableComponent.cs
@@ -1,15 +1,13 @@
-using System;
+#nullable enable
+using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.GUI;
-using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.Alert;
using Content.Shared.GameObjects.Components.ActionBlocking;
-using Content.Shared.GameObjects.Components.Mobs;
-using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces;
@@ -46,24 +44,20 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
[ViewVariables(VVAccess.ReadOnly)]
private Container _container = default!;
- private float _interactRange;
- private IHandsComponent _hands;
+ // TODO: Make a component message
+ public event Action? OnCuffedStateChanged;
- public event Action OnCuffedStateChanged;
+ private bool _uncuffing;
public override void Initialize()
{
base.Initialize();
_container = ContainerManagerComponent.Ensure(Name, Owner);
- _interactRange = SharedInteractionSystem.InteractionRange / 2;
Owner.EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, HandleHandCountChange);
- if (!Owner.TryGetComponent(out _hands))
- {
- Logger.Warning("Player does not have an IHandsComponent!");
- }
+ Owner.EnsureComponentWarn();
}
public override ComponentState GetComponentState()
@@ -99,27 +93,35 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
/// Add a set of cuffs to an existing CuffedComponent.
///
///
- public void AddNewCuffs(IEntity handcuff)
+ public bool TryAddNewCuffs(IEntity user, IEntity handcuff)
{
if (!handcuff.HasComponent())
{
Logger.Warning($"Handcuffs being applied to player are missing a {nameof(HandcuffComponent)}!");
- return;
+ return false;
}
- if (!handcuff.InRangeUnobstructed(Owner, _interactRange))
+ if (!handcuff.InRangeUnobstructed(Owner))
{
Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!");
- return;
+ return true;
+ }
+
+ // Success!
+ if (user.TryGetComponent(out HandsComponent? handsComponent) && handsComponent.IsHolding(handcuff))
+ {
+ // Good lord handscomponent is scuffed, I hope some smug person will fix it someday
+ handsComponent.Drop(handcuff);
}
_container.Insert(handcuff);
- CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
+ CanStillInteract = Owner.TryGetComponent(out HandsComponent? ownerHands) && ownerHands.Hands.Count() > CuffedHandCount;
OnCuffedStateChanged?.Invoke();
UpdateAlert();
UpdateHeldItems();
Dirty();
+ return true;
}
///
@@ -128,7 +130,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
private void UpdateHandCount()
{
var dirty = false;
- var handCount = _hands.Hands.Count();
+ var handCount = Owner.TryGetComponent(out HandsComponent? handsComponent) ? handsComponent.Hands.Count() : 0;
while (CuffedHandCount > handCount && CuffedHandCount > 0)
{
@@ -142,7 +144,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
if (dirty)
{
CanStillInteract = handCount > CuffedHandCount;
- OnCuffedStateChanged.Invoke();
+ OnCuffedStateChanged?.Invoke();
Dirty();
}
}
@@ -160,17 +162,19 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
///
public void UpdateHeldItems()
{
- var itemCount = _hands.GetAllHeldItems().Count();
- var freeHandCount = _hands.Hands.Count() - CuffedHandCount;
+ if (!Owner.TryGetComponent(out HandsComponent? handsComponent)) return;
+
+ var itemCount = handsComponent.GetAllHeldItems().Count();
+ var freeHandCount = handsComponent.Hands.Count() - CuffedHandCount;
if (freeHandCount < itemCount)
{
- foreach (var item in _hands.GetAllHeldItems())
+ foreach (var item in handsComponent.GetAllHeldItems())
{
if (freeHandCount < itemCount)
{
freeHandCount++;
- _hands.Drop(item.Owner, false);
+ handsComponent.Drop(item.Owner, false);
}
else
{
@@ -185,7 +189,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
///
private void UpdateAlert()
{
- if (Owner.TryGetComponent(out ServerAlertsComponent status))
+ if (Owner.TryGetComponent(out ServerAlertsComponent? status))
{
if (CanStillInteract)
{
@@ -204,8 +208,10 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
///
/// The cuffed entity
/// Optional param for the handcuff entity to remove from the cuffed entity. If null, uses the most recently added handcuff entity.
- public async void TryUncuff(IEntity user, IEntity cuffsToRemove = null)
+ public async void TryUncuff(IEntity user, IEntity? cuffsToRemove = null)
{
+ if (_uncuffing) return;
+
var isOwner = user == Owner;
if (cuffsToRemove == null)
@@ -232,13 +238,13 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
return;
}
- if (!isOwner && !user.InRangeUnobstructed(Owner, _interactRange))
+ if (!isOwner && !user.InRangeUnobstructed(Owner))
{
user.PopupMessage(Loc.GetString("You are too far away to remove the cuffs."));
return;
}
- if (!cuffsToRemove.InRangeUnobstructed(Owner, _interactRange))
+ if (!cuffsToRemove.InRangeUnobstructed(Owner))
{
Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!");
return;
@@ -247,7 +253,17 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
user.PopupMessage(Loc.GetString("You start removing the cuffs."));
var audio = EntitySystem.Get();
- audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner);
+ if (isOwner)
+ {
+ if (cuff.StartBreakoutSound != null)
+ audio.PlayFromEntity(cuff.StartBreakoutSound, Owner);
+ }
+ else
+ {
+ if (cuff.StartUncuffSound != null)
+ audio.PlayFromEntity(cuff.StartUncuffSound, Owner);
+ }
+
var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;
var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime)
@@ -259,11 +275,16 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
};
var doAfterSystem = EntitySystem.Get();
+ _uncuffing = true;
+
var result = await doAfterSystem.DoAfter(doAfterEventArgs);
+ _uncuffing = false;
+
if (result != DoAfterStatus.Cancelled)
{
- audio.PlayFromEntity(cuff.EndUncuffSound, Owner);
+ if (cuff.EndUncuffSound != null)
+ audio.PlayFromEntity(cuff.EndUncuffSound, Owner);
_container.ForceRemove(cuffsToRemove);
cuffsToRemove.Transform.AttachToGridOrMap();
@@ -276,14 +297,14 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
cuffsToRemove.Name = cuff.BrokenName;
cuffsToRemove.Description = cuff.BrokenDesc;
- if (cuffsToRemove.TryGetComponent(out var sprite))
+ if (cuffsToRemove.TryGetComponent(out var sprite) && cuff.BrokenState != null)
{
sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state?
}
}
- CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
- OnCuffedStateChanged.Invoke();
+ CanStillInteract = Owner.TryGetComponent(out HandsComponent? handsComponent) && handsComponent.Hands.Count() > CuffedHandCount;
+ OnCuffedStateChanged?.Invoke();
UpdateAlert();
Dirty();
diff --git a/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs b/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs
index 9e05ab7cd1..51f0addcee 100644
--- a/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs
+++ b/Content.Server/GameObjects/Components/ActionBlocking/HandcuffComponent.cs
@@ -1,10 +1,10 @@
-using System;
+#nullable enable
+using System;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Shared.GameObjects.Components.ActionBlocking;
-using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -14,7 +14,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
-using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -22,6 +21,7 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.ActionBlocking
{
[RegisterComponent]
+ [ComponentReference(typeof(SharedHandcuffComponent))]
public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract
{
///
@@ -58,31 +58,31 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
/// The path of the RSI file used for the player cuffed overlay.
///
[ViewVariables]
- public string CuffedRSI { get; set; }
+ public string? CuffedRSI { get; set; }
///
/// The iconstate used with the RSI file for the player cuffed overlay.
///
[ViewVariables]
- public string OverlayIconState { get; set; }
+ public string? OverlayIconState { get; set; }
///
/// The iconstate used for broken handcuffs
///
[ViewVariables]
- public string BrokenState { get; set; }
+ public string? BrokenState { get; set; }
///
/// The iconstate used for broken handcuffs
///
[ViewVariables]
- public string BrokenName { get; set; }
+ public string BrokenName { get; set; } = default!;
///
/// The iconstate used for broken handcuffs
///
[ViewVariables]
- public string BrokenDesc { get; set; }
+ public string BrokenDesc { get; set; } = default!;
[ViewVariables]
public bool Broken
@@ -102,25 +102,20 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
}
}
- public string StartCuffSound { get; set; }
- public string EndCuffSound { get; set; }
- public string StartBreakoutSound { get; set; }
- public string StartUncuffSound { get; set; }
- public string EndUncuffSound { get; set; }
+ public string? StartCuffSound { get; set; }
+ public string? EndCuffSound { get; set; }
+ public string? StartBreakoutSound { get; set; }
+ public string? StartUncuffSound { get; set; }
+ public string? EndUncuffSound { get; set; }
public Color Color { get; set; }
// Non-exposed data fields
private bool _isBroken = false;
- private float _interactRange;
- private AudioSystem _audioSystem;
- public override void Initialize()
- {
- base.Initialize();
-
- _audioSystem = EntitySystem.Get();
- _interactRange = SharedInteractionSystem.InteractionRange / 2;
- }
+ ///
+ /// Used to prevent DoAfter getting spammed.
+ ///
+ private bool _cuffing;
public override void ExposeData(ObjectSerializer serializer)
{
@@ -150,6 +145,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
+ if (_cuffing) return true;
+
if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent(out var cuffed))
{
return false;
@@ -179,7 +176,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
return true;
}
- if (!eventArgs.InRangeUnobstructed(_interactRange, ignoreInsideBlocker: true))
+ if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true))
{
eventArgs.User.PopupMessage(Loc.GetString("You are too far away to use the cuffs!"));
return true;
@@ -187,7 +184,9 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
eventArgs.User.PopupMessage(Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target));
eventArgs.User.PopupMessage(eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User));
- _audioSystem.PlayFromEntity(StartCuffSound, Owner);
+
+ if (StartCuffSound != null)
+ EntitySystem.Get().PlayFromEntity(StartCuffSound, Owner);
TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed);
return true;
@@ -214,22 +213,21 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
NeedHand = true
};
+ _cuffing = true;
+
var result = await EntitySystem.Get().DoAfter(doAfterEventArgs);
+ _cuffing = false;
+
if (result != DoAfterStatus.Cancelled)
{
- _audioSystem.PlayFromEntity(EndCuffSound, Owner);
- user.PopupMessage(Loc.GetString("You successfully cuff {0:theName}.", target));
- target.PopupMessage(Loc.GetString("You have been cuffed by {0:theName}!", user));
+ if (cuffs.TryAddNewCuffs(user, Owner))
+ {
+ if (EndCuffSound != null)
+ EntitySystem.Get().PlayFromEntity(EndCuffSound, Owner);
- if (user.TryGetComponent(out var hands))
- {
- hands.Drop(Owner);
- cuffs.AddNewCuffs(Owner);
- }
- else
- {
- Logger.Warning("Unable to remove handcuffs from player's hands! This should not happen!");
+ user.PopupMessage(Loc.GetString("You successfully cuff {0:theName}.", target));
+ target.PopupMessage(Loc.GetString("You have been cuffed by {0:theName}!", user));
}
}
else
diff --git a/Content.Shared/GameObjects/Components/ActionBlocking/SharedHandcuffComponent.cs b/Content.Shared/GameObjects/Components/ActionBlocking/SharedHandcuffComponent.cs
index 6ab252ba2b..e55211987a 100644
--- a/Content.Shared/GameObjects/Components/ActionBlocking/SharedHandcuffComponent.cs
+++ b/Content.Shared/GameObjects/Components/ActionBlocking/SharedHandcuffComponent.cs
@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.ActionBlocking
{
- public class SharedHandcuffComponent : Component
+ public abstract class SharedHandcuffComponent : Component
{
public override string Name => "Handcuff";
public override uint? NetID => ContentNetIDs.HANDCUFFS;
diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml
index 5be4593ab4..1c2450b7c8 100644
--- a/Resources/Prototypes/Alerts/alerts.yml
+++ b/Resources/Prototypes/Alerts/alerts.yml
@@ -89,9 +89,10 @@
- type: alert
alertType: Handcuffed
+ onClick: !type:RemoveCuffs { }
icon: /Textures/Interface/Alerts/Handcuffed/Handcuffed.png
name: "[color=yellow]Handcuffed[/color]"
- description: "You're [color=yellow]handcuffed[/color] and can't use your hands. If anyone drags you, you won't be able to resist.."
+ description: "You're [color=yellow]handcuffed[/color] and can't use your hands. If anyone drags you, you won't be able to resist."
- type: alert
alertType: Buckled