Handcuff system (#1831)
* Implemented most serverside logic * All serverside cuff logic complete * SFX, Clientside HUD stuff, Other logic. * fffff * Cuffs 1.0 * missing loc string * Cuffs are stored in the balls now. * Basic integrationtest * Support stripping menu. * rrr * Fixes * properties * gun emoji * fixes * get rid of unused * reeee * Update Content.Shared/GameObjects/ContentNetIDs.cs Co-authored-by: Víctor Aguilera Puerto <6766154+Zumorica@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
|
||||
using Robust.Server.GameObjects;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.Components.ActionBlocking;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Robust.Shared.Log;
|
||||
using System.Linq;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Robust.Shared.Maths;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Serilog;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class CuffableComponent : SharedCuffableComponent
|
||||
{
|
||||
[Dependency]
|
||||
private readonly ISharedNotifyManager _notifyManager;
|
||||
|
||||
/// <summary>
|
||||
/// How many of this entity's hands are currently cuffed.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int CuffedHandCount => _container.ContainedEntities.Count * 2;
|
||||
|
||||
protected IEntity LastAddedCuffs => _container.ContainedEntities[_container.ContainedEntities.Count - 1];
|
||||
|
||||
public IReadOnlyList<IEntity> StoredEntities => _container.ContainedEntities;
|
||||
|
||||
/// <summary>
|
||||
/// Container of various handcuffs currently applied to the entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
private Container _container = default!;
|
||||
|
||||
private bool _dirtyThisFrame = false;
|
||||
private float _interactRange;
|
||||
private IHandsComponent _hands;
|
||||
|
||||
public event Action OnCuffedStateChanged;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_container = ContainerManagerComponent.Ensure<Container>(Name, Owner);
|
||||
_interactRange = SharedInteractionSystem.InteractionRange / 2;
|
||||
|
||||
if (!Owner.TryGetComponent(out _hands))
|
||||
{
|
||||
Logger.Warning("Player does not have an IHandsComponent!");
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
// there are 2 approaches i can think of to handle the handcuff overlay on players
|
||||
// 1 - make the current RSI the handcuff type that's currently active. all handcuffs on the player will appear the same.
|
||||
// 2 - allow for several different player overlays for each different cuff type.
|
||||
// approach #2 would be more difficult/time consuming to do and the payoff doesn't make it worth it.
|
||||
// right now we're doing approach #1.
|
||||
|
||||
if (CuffedHandCount > 0)
|
||||
{
|
||||
if (LastAddedCuffs.TryGetComponent<HandcuffComponent>(out var cuffs))
|
||||
{
|
||||
return new CuffableComponentState(CuffedHandCount,
|
||||
CanStillInteract,
|
||||
cuffs.CuffedRSI,
|
||||
$"{cuffs.OverlayIconState}-{CuffedHandCount}",
|
||||
cuffs.Color);
|
||||
// the iconstate is formatted as blah-2, blah-4, blah-6, etc.
|
||||
// the number corresponds to how many hands are cuffed.
|
||||
}
|
||||
}
|
||||
|
||||
return new CuffableComponentState(CuffedHandCount,
|
||||
CanStillInteract,
|
||||
"/Objects/Misc/handcuffs.rsi",
|
||||
"body-overlay-2",
|
||||
Color.White);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a set of cuffs to an existing CuffedComponent.
|
||||
/// </summary>
|
||||
/// <param name="prototype"></param>
|
||||
public void AddNewCuffs(IEntity handcuff)
|
||||
{
|
||||
if (!handcuff.HasComponent<HandcuffComponent>())
|
||||
{
|
||||
Logger.Warning($"Handcuffs being applied to player are missing a {nameof(HandcuffComponent)}!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
|
||||
handcuff.Transform.MapPosition,
|
||||
Owner.Transform.MapPosition,
|
||||
_interactRange,
|
||||
ignoredEnt: Owner))
|
||||
{
|
||||
Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!");
|
||||
return;
|
||||
}
|
||||
|
||||
_container.Insert(handcuff);
|
||||
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
|
||||
|
||||
OnCuffedStateChanged.Invoke();
|
||||
UpdateStatusEffect();
|
||||
UpdateHeldItems();
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
UpdateHandCount();
|
||||
}
|
||||
|
||||
/// <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 UpdateHandCount()
|
||||
{
|
||||
_dirtyThisFrame = false;
|
||||
var handCount = _hands.Hands.Count();
|
||||
|
||||
while (CuffedHandCount > handCount && CuffedHandCount > 0)
|
||||
{
|
||||
_dirtyThisFrame = true;
|
||||
|
||||
var entity = _container.ContainedEntities[_container.ContainedEntities.Count - 1];
|
||||
_container.Remove(entity);
|
||||
entity.Transform.WorldPosition = Owner.Transform.GridPosition.Position;
|
||||
}
|
||||
|
||||
if (_dirtyThisFrame)
|
||||
{
|
||||
CanStillInteract = handCount > CuffedHandCount;
|
||||
OnCuffedStateChanged.Invoke();
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check how many items the user is holding and if it's more than the number of cuffed hands, drop some items.
|
||||
/// </summary>
|
||||
public void UpdateHeldItems()
|
||||
{
|
||||
var itemCount = _hands.GetAllHeldItems().Count();
|
||||
var freeHandCount = _hands.Hands.Count() - CuffedHandCount;
|
||||
|
||||
if (freeHandCount < itemCount)
|
||||
{
|
||||
foreach (ItemComponent item in _hands.GetAllHeldItems())
|
||||
{
|
||||
if (freeHandCount < itemCount)
|
||||
{
|
||||
freeHandCount++;
|
||||
_hands.Drop(item.Owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the status effect indicator on the HUD.
|
||||
/// </summary>
|
||||
private void UpdateStatusEffect()
|
||||
{
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Cuffed,
|
||||
CanStillInteract ? "/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png" : "/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="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>
|
||||
public async void TryUncuff(IEntity user, IEntity cuffsToRemove = null)
|
||||
{
|
||||
var isOwner = user == Owner;
|
||||
|
||||
if (cuffsToRemove == null)
|
||||
{
|
||||
cuffsToRemove = LastAddedCuffs;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_container.ContainedEntities.Contains(cuffsToRemove))
|
||||
{
|
||||
Logger.Warning("A user is trying to remove handcuffs that aren't in the owner's container. This should never happen!");
|
||||
}
|
||||
}
|
||||
|
||||
if (!cuffsToRemove.TryGetComponent<HandcuffComponent>(out var cuff))
|
||||
{
|
||||
Logger.Warning($"A user is trying to remove handcuffs without a {nameof(HandcuffComponent)}. This should never happen!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isOwner && !ActionBlockerSystem.CanInteract(user))
|
||||
{
|
||||
user.PopupMessage(user, "You can't do that!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isOwner &&
|
||||
!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
|
||||
user.Transform.MapPosition,
|
||||
Owner.Transform.MapPosition,
|
||||
_interactRange,
|
||||
ignoredEnt: Owner))
|
||||
{
|
||||
user.PopupMessage(user, "You are too far away to remove the cuffs.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
|
||||
cuffsToRemove.Transform.MapPosition,
|
||||
Owner.Transform.MapPosition,
|
||||
_interactRange,
|
||||
ignoredEnt: Owner))
|
||||
{
|
||||
Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!");
|
||||
return;
|
||||
}
|
||||
|
||||
user.PopupMessage(user, "You start removing the cuffs.");
|
||||
|
||||
var audio = EntitySystem.Get<AudioSystem>();
|
||||
audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner);
|
||||
|
||||
var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;
|
||||
var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime)
|
||||
{
|
||||
BreakOnUserMove = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnStun = true,
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
|
||||
var result = await doAfterSystem.DoAfter(doAfterEventArgs);
|
||||
|
||||
if (result != DoAfterStatus.Cancelled)
|
||||
{
|
||||
audio.PlayFromEntity(cuff.EndUncuffSound, Owner);
|
||||
|
||||
_container.ForceRemove(cuffsToRemove);
|
||||
cuffsToRemove.Transform.AttachToGridOrMap();
|
||||
cuffsToRemove.Transform.WorldPosition = Owner.Transform.WorldPosition;
|
||||
|
||||
if (cuff.BreakOnRemove)
|
||||
{
|
||||
cuff.Broken = true;
|
||||
|
||||
cuffsToRemove.Name = cuff.BrokenName;
|
||||
cuffsToRemove.Description = cuff.BrokenDesc;
|
||||
|
||||
if (cuffsToRemove.TryGetComponent<SpriteComponent>(out var sprite))
|
||||
{
|
||||
sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state?
|
||||
}
|
||||
}
|
||||
|
||||
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
|
||||
OnCuffedStateChanged.Invoke();
|
||||
UpdateStatusEffect();
|
||||
Dirty();
|
||||
|
||||
if (CuffedHandCount == 0)
|
||||
{
|
||||
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs."));
|
||||
|
||||
if (!isOwner)
|
||||
{
|
||||
_notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} uncuffs your hands.", user));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isOwner)
|
||||
{
|
||||
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of {0:theName}'s hands remain cuffed.", CuffedHandCount, user));
|
||||
_notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} removes your cuffs. {1} of your hands remain cuffed.", user, CuffedHandCount));
|
||||
}
|
||||
else
|
||||
{
|
||||
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of your hands remain cuffed.", CuffedHandCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_notifyManager.PopupMessage(user, user, Loc.GetString("You fail to remove the cuffs."));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows the uncuffing of a cuffed person. Used by other people and by the component owner to break out of cuffs.
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class UncuffVerb : Verb<CuffableComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, CuffableComponent component, VerbData data)
|
||||
{
|
||||
if ((user != component.Owner && !ActionBlockerSystem.CanInteract(user)) || component.CuffedHandCount == 0)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
data.Text = Loc.GetString("Uncuff");
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, CuffableComponent component)
|
||||
{
|
||||
if (component.CuffedHandCount > 0)
|
||||
{
|
||||
component.TryUncuff(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.ActionBlocking;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Robust.Shared.Maths;
|
||||
using System;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract
|
||||
{
|
||||
[Dependency]
|
||||
private readonly ISharedNotifyManager _notifyManager;
|
||||
|
||||
/// <summary>
|
||||
/// The time it takes to apply a <see cref="CuffedComponent"/> to an entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float CuffTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time it takes to remove a <see cref="CuffedComponent"/> from an entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float UncuffTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time it takes for a cuffed entity to remove <see cref="CuffedComponent"/> from itself.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float BreakoutTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If an entity being cuffed is stunned, this amount of time is subtracted from the time it takes to add/remove their cuffs.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float StunBonus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Will the cuffs break when removed?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool BreakOnRemove { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path of the RSI file used for the player cuffed overlay.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string CuffedRSI { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The iconstate used with the RSI file for the player cuffed overlay.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string OverlayIconState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The iconstate used for broken handcuffs
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string BrokenState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The iconstate used for broken handcuffs
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string BrokenName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The iconstate used for broken handcuffs
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string BrokenDesc { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public bool Broken
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isBroken;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_isBroken != value)
|
||||
{
|
||||
_isBroken = value;
|
||||
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 DoAfterSystem _doAfterSystem;
|
||||
private AudioSystem _audioSystem;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_audioSystem = EntitySystem.Get<AudioSystem>();
|
||||
_doAfterSystem = EntitySystem.Get<DoAfterSystem>();
|
||||
_interactRange = SharedInteractionSystem.InteractionRange / 2;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(this, x => x.CuffTime, "cuffTime", 5.0f);
|
||||
serializer.DataField(this, x => x.BreakoutTime, "breakoutTime", 30.0f);
|
||||
serializer.DataField(this, x => x.UncuffTime, "uncuffTime", 5.0f);
|
||||
serializer.DataField(this, x => x.StunBonus, "stunBonus", 2.0f);
|
||||
serializer.DataField(this, x => x.StartCuffSound, "startCuffSound", "/Audio/Items/Handcuffs/cuff_start.ogg");
|
||||
serializer.DataField(this, x => x.EndCuffSound, "endCuffSound", "/Audio/Items/Handcuffs/cuff_end.ogg");
|
||||
serializer.DataField(this, x => x.StartUncuffSound, "startUncuffSound", "/Audio/Items/Handcuffs/cuff_takeoff_start.ogg");
|
||||
serializer.DataField(this, x => x.EndUncuffSound, "endUncuffSound", "/Audio/Items/Handcuffs/cuff_takeoff_end.ogg");
|
||||
serializer.DataField(this, x => x.StartBreakoutSound, "startBreakoutSound", "/Audio/Items/Handcuffs/cuff_breakout_start.ogg");
|
||||
serializer.DataField(this, x => x.CuffedRSI, "cuffedRSI", "Objects/Misc/handcuffs.rsi");
|
||||
serializer.DataField(this, x => x.OverlayIconState, "bodyIconState", "body-overlay");
|
||||
serializer.DataField(this, x => x.Color, "color", Color.White);
|
||||
serializer.DataField(this, x => x.BreakOnRemove, "breakOnRemove", false);
|
||||
serializer.DataField(this, x => x.BrokenState, "brokenIconState", string.Empty);
|
||||
serializer.DataField(this, x => x.BrokenName, "brokenName", string.Empty);
|
||||
serializer.DataField(this, x => x.BrokenDesc, "brokenDesc", string.Empty);
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new HandcuffedComponentState(Broken ? BrokenState : string.Empty);
|
||||
}
|
||||
|
||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent<CuffableComponent>(out var cuffed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventArgs.Target == eventArgs.User)
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't cuff yourself!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Broken)
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("The cuffs are broken!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventArgs.Target.TryGetComponent<HandsComponent>(out var hands))
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no hands!", eventArgs.Target));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cuffed.CuffedHandCount == hands.Count)
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no free hands to handcuff!", eventArgs.Target));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
|
||||
eventArgs.User.Transform.MapPosition,
|
||||
eventArgs.Target.Transform.MapPosition,
|
||||
_interactRange,
|
||||
ignoredEnt: Owner))
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are too far away to use the cuffs!"));
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target));
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User));
|
||||
_audioSystem.PlayFromEntity(StartCuffSound, Owner);
|
||||
|
||||
TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the cuffed state of an entity
|
||||
/// </summary>
|
||||
private async void TryUpdateCuff(IEntity user, IEntity target, CuffableComponent cuffs)
|
||||
{
|
||||
var cuffTime = CuffTime;
|
||||
|
||||
if (target.TryGetComponent<StunnableComponent>(out var stun) && stun.Stunned)
|
||||
{
|
||||
cuffTime = MathF.Max(0.1f, cuffTime - StunBonus);
|
||||
}
|
||||
|
||||
var doAfterEventArgs = new DoAfterEventArgs(user, cuffTime, default, target)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnStun = true,
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
|
||||
|
||||
if (result != DoAfterStatus.Cancelled)
|
||||
{
|
||||
_audioSystem.PlayFromEntity(EndCuffSound, Owner);
|
||||
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully cuff {0}.", target.Name));
|
||||
_notifyManager.PopupMessage(target, target, Loc.GetString("You have been cuffed by {0}!", user.Name));
|
||||
|
||||
if (user.TryGetComponent<HandsComponent>(out var hands))
|
||||
{
|
||||
hands.Drop(Owner);
|
||||
cuffs.AddNewCuffs(Owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning("Unable to remove handcuffs from player's hands! This should not happen!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
user.PopupMessage(user, Loc.GetString("You were interrupted while cuffing {0}!", target.Name));
|
||||
target.PopupMessage(target, Loc.GetString("You interrupt {0} while they are cuffing you!", user.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.GameObjects.Components.ActionBlocking;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Content.Server.Interfaces;
|
||||
@@ -28,7 +30,8 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
|
||||
public const float StripDelay = 2f;
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(StrippingUiKey.Key);
|
||||
[ViewVariables]
|
||||
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(StrippingUiKey.Key);
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -39,6 +42,14 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
UserInterface.OnReceiveMessage += HandleUserInterfaceMessage;
|
||||
}
|
||||
|
||||
Owner.EnsureComponent<InventoryComponent>();
|
||||
Owner.EnsureComponent<HandsComponent>();
|
||||
Owner.EnsureComponent<CuffableComponent>();
|
||||
|
||||
if (Owner.TryGetComponent(out CuffableComponent? cuffed))
|
||||
{
|
||||
cuffed.OnCuffedStateChanged += UpdateSubscribed;
|
||||
}
|
||||
if (Owner.TryGetComponent(out InventoryComponent? inventory))
|
||||
{
|
||||
inventory.OnItemChanged += UpdateSubscribed;
|
||||
@@ -62,8 +73,9 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
|
||||
var inventory = GetInventorySlots();
|
||||
var hands = GetHandSlots();
|
||||
var cuffs = GetHandcuffs();
|
||||
|
||||
UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands));
|
||||
UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs));
|
||||
}
|
||||
|
||||
public bool CanDragDrop(DragDropEventArgs eventArgs)
|
||||
@@ -80,6 +92,23 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return true;
|
||||
}
|
||||
|
||||
private Dictionary<EntityUid, string> GetHandcuffs()
|
||||
{
|
||||
var dictionary = new Dictionary<EntityUid, string>();
|
||||
|
||||
if (!Owner.TryGetComponent(out CuffableComponent? cuffed))
|
||||
{
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
foreach (IEntity entity in cuffed.StoredEntities)
|
||||
{
|
||||
dictionary.Add(entity.Uid, entity.Name);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
private Dictionary<Slots, string> GetInventorySlots()
|
||||
{
|
||||
var dictionary = new Dictionary<Slots, string>();
|
||||
@@ -360,26 +389,46 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
switch (obj.Message)
|
||||
{
|
||||
case StrippingInventoryButtonPressed inventoryMessage:
|
||||
var inventory = Owner.GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventory.TryGetSlotItem(inventoryMessage.Slot, out ItemComponent _))
|
||||
placingItem = false;
|
||||
if (Owner.TryGetComponent<InventoryComponent>(out var inventory))
|
||||
{
|
||||
if (inventory.TryGetSlotItem(inventoryMessage.Slot, out ItemComponent _))
|
||||
placingItem = false;
|
||||
|
||||
if(placingItem)
|
||||
PlaceActiveHandItemInInventory(user, inventoryMessage.Slot);
|
||||
else
|
||||
TakeItemFromInventory(user, inventoryMessage.Slot);
|
||||
if (placingItem)
|
||||
PlaceActiveHandItemInInventory(user, inventoryMessage.Slot);
|
||||
else
|
||||
TakeItemFromInventory(user, inventoryMessage.Slot);
|
||||
}
|
||||
break;
|
||||
|
||||
case StrippingHandButtonPressed handMessage:
|
||||
var hands = Owner.GetComponent<HandsComponent>();
|
||||
|
||||
if (hands.TryGetItem(handMessage.Hand, out _))
|
||||
placingItem = false;
|
||||
if (Owner.TryGetComponent<HandsComponent>(out var hands))
|
||||
{
|
||||
if (hands.TryGetItem(handMessage.Hand, out _))
|
||||
placingItem = false;
|
||||
|
||||
if(placingItem)
|
||||
PlaceActiveHandItemInHands(user, handMessage.Hand);
|
||||
else
|
||||
TakeItemFromHands(user, handMessage.Hand);
|
||||
if (placingItem)
|
||||
PlaceActiveHandItemInHands(user, handMessage.Hand);
|
||||
else
|
||||
TakeItemFromHands(user, handMessage.Hand);
|
||||
}
|
||||
break;
|
||||
|
||||
case StrippingHandcuffButtonPressed handcuffMessage:
|
||||
|
||||
if (Owner.TryGetComponent<CuffableComponent>(out var cuffed))
|
||||
{
|
||||
foreach (var entity in cuffed.StoredEntities)
|
||||
{
|
||||
if (entity.Uid == handcuffMessage.Handcuff)
|
||||
{
|
||||
cuffed.TryUncuff(user, entity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ namespace Content.Server.GameObjects.Components.Items.Clothing
|
||||
});
|
||||
|
||||
serializer.DataField(ref _quickEquipEnabled, "QuickEquip", true);
|
||||
|
||||
serializer.DataFieldCached(ref _heatResistance, "HeatResistance", 323);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
|
||||
18
Content.Server/GameObjects/EntitySystems/CuffSystem.cs
Normal file
18
Content.Server/GameObjects/EntitySystems/CuffSystem.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Content.Server.GameObjects.Components.ActionBlocking;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class CuffSystem : EntitySystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var comp in ComponentManager.EntityQuery<CuffableComponent>())
|
||||
{
|
||||
comp.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user