Ensnaring Component and Bola Update (#9968)
This commit is contained in:
13
Content.Client/Ensnaring/Components/EnsnareableComponent.cs
Normal file
13
Content.Client/Ensnaring/Components/EnsnareableComponent.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
|
||||
namespace Content.Client.Ensnaring.Components;
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedEnsnareableComponent))]
|
||||
public sealed class EnsnareableComponent : SharedEnsnareableComponent
|
||||
{
|
||||
[DataField("sprite")]
|
||||
public string? Sprite;
|
||||
|
||||
[DataField("state")]
|
||||
public string? State;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
|
||||
namespace Content.Client.Ensnaring.Components;
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedEnsnaringComponent))]
|
||||
public sealed class EnsnaringComponent : SharedEnsnaringComponent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Ensnaring.Visualizers;
|
||||
[RegisterComponent]
|
||||
[Access(typeof(EnsnareableVisualizerSystem))]
|
||||
public sealed class EnsnareableVisualizerComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Content.Client.Ensnaring.Components;
|
||||
using Content.Shared.Ensnaring;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Ensnaring.Visualizers;
|
||||
|
||||
public sealed class EnsnareableVisualizerSystem : VisualizerSystem<EnsnareableComponent>
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<EnsnareableVisualizerComponent, ComponentInit>(OnComponentInit);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, EnsnareableVisualizerComponent component, ComponentInit args)
|
||||
{
|
||||
if(!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.LayerMapReserveBlank(EnsnaredVisualLayers.Ensnared);
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, EnsnareableComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Component.TryGetData(EnsnareableVisuals.IsEnsnared, out bool isEnsnared))
|
||||
{
|
||||
if (args.Sprite != null && component.Sprite != null)
|
||||
{
|
||||
args.Sprite.LayerSetRSI(EnsnaredVisualLayers.Ensnared, component.Sprite);
|
||||
args.Sprite.LayerSetState(EnsnaredVisualLayers.Ensnared, component.State);
|
||||
args.Sprite.LayerSetVisible(EnsnaredVisualLayers.Ensnared, isEnsnared);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum EnsnaredVisualLayers : byte
|
||||
{
|
||||
Ensnared,
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace Content.Client.Inventory
|
||||
public Dictionary<(string ID, string Name), string>? Inventory { get; private set; }
|
||||
public Dictionary<string, string>? Hands { get; private set; }
|
||||
public Dictionary<EntityUid, string>? Handcuffs { get; private set; }
|
||||
public Dictionary<EntityUid, string>? Ensnare { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
private StrippingMenu? _strippingMenu;
|
||||
@@ -79,6 +80,17 @@ namespace Content.Client.Inventory
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Ensnare != null)
|
||||
{
|
||||
foreach (var (id, name) in Ensnare)
|
||||
{
|
||||
_strippingMenu.AddButton(Loc.GetString("strippable-bound-user-interface-stripping-menu-ensnare-button"), name, (ev) =>
|
||||
{
|
||||
SendMessage(new StrippingEnsnareButtonPressed(id));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
@@ -90,6 +102,7 @@ namespace Content.Client.Inventory
|
||||
Inventory = stripState.Inventory;
|
||||
Hands = stripState.Hands;
|
||||
Handcuffs = stripState.Handcuffs;
|
||||
Ensnare = stripState.Ensnare;
|
||||
|
||||
UpdateMenu();
|
||||
}
|
||||
|
||||
25
Content.Server/Alert/Click/RemoveEnsnare.cs
Normal file
25
Content.Server/Alert/Click/RemoveEnsnare.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Server.Ensnaring;
|
||||
using Content.Server.Ensnaring.Components;
|
||||
using Content.Shared.Alert;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Alert.Click;
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed class RemoveEnsnare : IAlertClick
|
||||
{
|
||||
public void AlertClicked(EntityUid player)
|
||||
{
|
||||
var entManager = IoCManager.Resolve<IEntityManager>();
|
||||
if (entManager.TryGetComponent(player, out EnsnareableComponent? ensnareableComponent))
|
||||
{
|
||||
foreach (var ensnare in ensnareableComponent.Container.ContainedEntities)
|
||||
{
|
||||
if (!entManager.TryGetComponent(ensnare, out EnsnaringComponent? ensnaringComponent))
|
||||
return;
|
||||
|
||||
entManager.EntitySysManager.GetEntitySystem<EnsnareableSystem>().TryFree(player, ensnaringComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Content.Server/Ensnaring/Components/EnsnareableComponent.cs
Normal file
15
Content.Server/Ensnaring/Components/EnsnareableComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Ensnaring.Components;
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedEnsnareableComponent))]
|
||||
public sealed class EnsnareableComponent : SharedEnsnareableComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The container where the <see cref="EnsnaringComponent"/> entity will be stored
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("container")]
|
||||
public Container Container = default!;
|
||||
}
|
||||
43
Content.Server/Ensnaring/Components/EnsnaringComponent.cs
Normal file
43
Content.Server/Ensnaring/Components/EnsnaringComponent.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Threading;
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
|
||||
namespace Content.Server.Ensnaring.Components;
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedEnsnaringComponent))]
|
||||
public sealed class EnsnaringComponent : SharedEnsnaringComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Should movement cancel breaking out?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("canMoveBreakout")]
|
||||
public bool CanMoveBreakout;
|
||||
|
||||
public CancellationTokenSource? CancelToken;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
149
Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs
Normal file
149
Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System.Threading;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Ensnaring.Components;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.StepTrigger.Systems;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Ensnaring;
|
||||
|
||||
public sealed partial class EnsnareableSystem
|
||||
{
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
|
||||
public void InitializeEnsnaring()
|
||||
{
|
||||
SubscribeLocalEvent<EnsnaringComponent, ComponentRemove>(OnComponentRemove);
|
||||
SubscribeLocalEvent<EnsnaringComponent, StepTriggerAttemptEvent>(AttemptStepTrigger);
|
||||
SubscribeLocalEvent<EnsnaringComponent, StepTriggeredEvent>(OnStepTrigger);
|
||||
SubscribeLocalEvent<EnsnaringComponent, ThrowDoHitEvent>(OnThrowHit);
|
||||
}
|
||||
|
||||
private void OnComponentRemove(EntityUid uid, EnsnaringComponent component, ComponentRemove args)
|
||||
{
|
||||
if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnared))
|
||||
return;
|
||||
|
||||
if (ensnared.IsEnsnared)
|
||||
ForceFree(component);
|
||||
}
|
||||
|
||||
private void AttemptStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggerAttemptEvent args)
|
||||
{
|
||||
args.Continue = true;
|
||||
}
|
||||
|
||||
private void OnStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggeredEvent args)
|
||||
{
|
||||
TryEnsnare(args.Tripper, component);
|
||||
}
|
||||
|
||||
private void OnThrowHit(EntityUid uid, EnsnaringComponent component, ThrowDoHitEvent args)
|
||||
{
|
||||
if (!component.CanThrowTrigger)
|
||||
return;
|
||||
|
||||
TryEnsnare(args.Target, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used where you want to try to ensnare an entity with the <see cref="EnsnareableComponent"/>
|
||||
/// </summary>
|
||||
/// <param name="ensnaringEntity">The entity that will be used to ensnare</param>
|
||||
/// <param name="target">The entity that will be ensnared</param>
|
||||
/// <param name="component">The ensnaring component</param>
|
||||
public void TryEnsnare(EntityUid target, EnsnaringComponent component)
|
||||
{
|
||||
//Don't do anything if they don't have the ensnareable component.
|
||||
if (!TryComp<EnsnareableComponent>(target, out var ensnareable))
|
||||
return;
|
||||
|
||||
component.Ensnared = target;
|
||||
ensnareable.Container.Insert(component.Owner);
|
||||
ensnareable.IsEnsnared = true;
|
||||
|
||||
UpdateAlert(ensnareable);
|
||||
var ev = new EnsnareEvent(component.WalkSpeed, component.SprintSpeed);
|
||||
RaiseLocalEvent(target, ev, false);
|
||||
}
|
||||
|
||||
/// <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 free</param>
|
||||
/// <param name="component">The ensnaring component</param>
|
||||
public void TryFree(EntityUid target, EnsnaringComponent component, EntityUid? user = null)
|
||||
{
|
||||
//Don't do anything if they don't have the ensnareable component.
|
||||
if (!TryComp<EnsnareableComponent>(target, out var ensnareable))
|
||||
return;
|
||||
|
||||
if (component.CancelToken != null)
|
||||
return;
|
||||
|
||||
component.CancelToken = new CancellationTokenSource();
|
||||
|
||||
var isOwner = !(user != null && target != user);
|
||||
var freeTime = isOwner ? component.BreakoutTime : component.FreeTime;
|
||||
bool breakOnMove;
|
||||
|
||||
if (isOwner)
|
||||
breakOnMove = !component.CanMoveBreakout;
|
||||
else
|
||||
breakOnMove = true;
|
||||
|
||||
var doAfterEventArgs = new DoAfterEventArgs(target, freeTime, component.CancelToken.Token, target)
|
||||
{
|
||||
BreakOnUserMove = breakOnMove,
|
||||
BreakOnTargetMove = breakOnMove,
|
||||
BreakOnDamage = false,
|
||||
BreakOnStun = true,
|
||||
NeedHand = true,
|
||||
TargetFinishedEvent = new FreeEnsnareDoAfterComplete(component.Owner),
|
||||
TargetCancelledEvent = new FreeEnsnareDoAfterCancel(component.Owner),
|
||||
};
|
||||
|
||||
_doAfter.DoAfter(doAfterEventArgs);
|
||||
|
||||
if (isOwner)
|
||||
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free", ("ensnare", component.Owner)), target, Filter.Entities(target));
|
||||
|
||||
if (!isOwner && user != null)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", component.Owner), ("user", Identity.Entity(target, EntityManager))), user.Value, Filter.Entities(user.Value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to force free someone for things like if the <see cref="EnsnaringComponent"/> is removed
|
||||
/// </summary>
|
||||
public void ForceFree(EnsnaringComponent component)
|
||||
{
|
||||
if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnareable))
|
||||
return;
|
||||
|
||||
ensnareable.Container.ForceRemove(component.Owner);
|
||||
ensnareable.IsEnsnared = false;
|
||||
component.Ensnared = null;
|
||||
|
||||
UpdateAlert(ensnareable);
|
||||
var ev = new EnsnareRemoveEvent();
|
||||
RaiseLocalEvent(component.Owner, ev, false);
|
||||
}
|
||||
|
||||
public void UpdateAlert(EnsnareableComponent component)
|
||||
{
|
||||
if (!component.IsEnsnared)
|
||||
{
|
||||
_alerts.ClearAlert(component.Owner, AlertType.Ensnared);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ShowAlert(component.Owner, AlertType.Ensnared);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Content.Server/Ensnaring/EnsnareableSystem.cs
Normal file
62
Content.Server/Ensnaring/EnsnareableSystem.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Content.Server.Ensnaring.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Ensnaring;
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Ensnaring;
|
||||
|
||||
public sealed partial class EnsnareableSystem : SharedEnsnareableSystem
|
||||
{
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
InitializeEnsnaring();
|
||||
|
||||
SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnEnsnareableInit);
|
||||
SubscribeLocalEvent<EnsnareableComponent, FreeEnsnareDoAfterComplete>(OnFreeComplete);
|
||||
SubscribeLocalEvent<EnsnareableComponent, FreeEnsnareDoAfterCancel>(OnFreeFail);
|
||||
}
|
||||
|
||||
private void OnEnsnareableInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
|
||||
{
|
||||
component.Container = _container.EnsureContainer<Container>(component.Owner, "ensnare");
|
||||
}
|
||||
|
||||
private void OnFreeComplete(EntityUid uid, EnsnareableComponent component, FreeEnsnareDoAfterComplete args)
|
||||
{
|
||||
if (!TryComp<EnsnaringComponent>(args.EnsnaringEntity, out var ensnaring))
|
||||
return;
|
||||
|
||||
component.Container.Remove(args.EnsnaringEntity);
|
||||
component.IsEnsnared = false;
|
||||
ensnaring.Ensnared = null;
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.EnsnaringEntity)),
|
||||
uid, Filter.Entities(uid), PopupType.Large);
|
||||
|
||||
UpdateAlert(component);
|
||||
var ev = new EnsnareRemoveEvent();
|
||||
RaiseLocalEvent(uid, ev, false);
|
||||
|
||||
ensnaring.CancelToken = null;
|
||||
}
|
||||
|
||||
private void OnFreeFail(EntityUid uid, EnsnareableComponent component, FreeEnsnareDoAfterCancel args)
|
||||
{
|
||||
if (!TryComp<EnsnaringComponent>(args.EnsnaringEntity, out var ensnaring))
|
||||
return;
|
||||
|
||||
ensnaring.CancelToken = null;
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.EnsnaringEntity)),
|
||||
uid, Filter.Entities(uid), PopupType.Large);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ namespace Content.Server.Entry
|
||||
"CharacterInfo",
|
||||
"HandheldGPS",
|
||||
"CableVisualizer",
|
||||
"EnsnareableVisualizer",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using Content.Server.Cuffs.Components;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Ensnaring;
|
||||
using Content.Server.Ensnaring.Components;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -14,6 +18,7 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Strip.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Strip
|
||||
@@ -24,6 +29,7 @@ namespace Content.Server.Strip
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly EnsnareableSystem _ensnaring = default!;
|
||||
|
||||
// TODO: ECS popups. Not all of these have ECS equivalents yet.
|
||||
|
||||
@@ -36,11 +42,13 @@ namespace Content.Server.Strip
|
||||
SubscribeLocalEvent<StrippableComponent, DidUnequipEvent>(OnDidUnequip);
|
||||
SubscribeLocalEvent<StrippableComponent, ComponentInit>(OnCompInit);
|
||||
SubscribeLocalEvent<StrippableComponent, CuffedStateChangeEvent>(OnCuffStateChange);
|
||||
SubscribeLocalEvent<StrippableComponent, EnsnaredChangedEvent>(OnEnsnareChange);
|
||||
|
||||
// BUI
|
||||
SubscribeLocalEvent<StrippableComponent, StrippingInventoryButtonPressed>(OnStripInvButtonMessage);
|
||||
SubscribeLocalEvent<StrippableComponent, StrippingHandButtonPressed>(OnStripHandMessage);
|
||||
SubscribeLocalEvent<StrippableComponent, StrippingHandcuffButtonPressed>(OnStripHandcuffMessage);
|
||||
SubscribeLocalEvent<StrippableComponent, StrippingEnsnareButtonPressed>(OnStripEnsnareMessage);
|
||||
|
||||
SubscribeLocalEvent<StrippableComponent, OpenStrippingCompleteEvent>(OnOpenStripComplete);
|
||||
SubscribeLocalEvent<StrippableComponent, OpenStrippingCancelledEvent>(OnOpenStripCancelled);
|
||||
@@ -76,6 +84,26 @@ namespace Content.Server.Strip
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStripEnsnareMessage(EntityUid uid, StrippableComponent component, StrippingEnsnareButtonPressed args)
|
||||
{
|
||||
if (args.Session.AttachedEntity is not {Valid: true} user)
|
||||
return;
|
||||
|
||||
var ensnareQuery = GetEntityQuery<EnsnareableComponent>();
|
||||
|
||||
foreach (var entity in ensnareQuery.GetComponent(uid).Container.ContainedEntities)
|
||||
{
|
||||
if (!TryComp<EnsnaringComponent>(entity, out var ensnaring))
|
||||
continue;
|
||||
|
||||
if (entity != args.Ensnare)
|
||||
continue;
|
||||
|
||||
_ensnaring.TryFree(component.Owner, ensnaring, user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStripHandMessage(EntityUid uid, StrippableComponent component, StrippingHandButtonPressed args)
|
||||
{
|
||||
if (args.Session.AttachedEntity is not {Valid: true} user ||
|
||||
@@ -154,6 +182,11 @@ namespace Content.Server.Strip
|
||||
UpdateState(uid, component);
|
||||
}
|
||||
|
||||
private void OnEnsnareChange(EntityUid uid, StrippableComponent component, EnsnaredChangedEvent args)
|
||||
{
|
||||
SendUpdate(uid, component);
|
||||
}
|
||||
|
||||
private void OnDidUnequip(EntityUid uid, StrippableComponent component, DidUnequipEvent args)
|
||||
{
|
||||
SendUpdate(uid, component);
|
||||
@@ -174,6 +207,7 @@ namespace Content.Server.Strip
|
||||
}
|
||||
|
||||
var cuffs = new Dictionary<EntityUid, string>();
|
||||
var ensnare = new Dictionary<EntityUid, string>();
|
||||
var inventory = new Dictionary<(string ID, string Name), string>();
|
||||
var hands = new Dictionary<string, string>();
|
||||
|
||||
@@ -186,6 +220,17 @@ namespace Content.Server.Strip
|
||||
}
|
||||
}
|
||||
|
||||
var ensnareQuery = GetEntityQuery<EnsnareableComponent>();
|
||||
|
||||
if (ensnareQuery.TryGetComponent(uid, out var _))
|
||||
{
|
||||
foreach (var entity in ensnareQuery.GetComponent(uid).Container.ContainedEntities)
|
||||
{
|
||||
var name = Name(entity);
|
||||
ensnare.Add(entity, name);
|
||||
}
|
||||
}
|
||||
|
||||
if (_inventorySystem.TryGetSlots(uid, out var slots))
|
||||
{
|
||||
foreach (var slot in slots)
|
||||
@@ -223,7 +268,7 @@ namespace Content.Server.Strip
|
||||
}
|
||||
}
|
||||
|
||||
bui.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs));
|
||||
bui.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs, ensnare));
|
||||
}
|
||||
|
||||
private void AddStripVerb(EntityUid uid, StrippableComponent component, GetVerbsEvent<Verb> args)
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Content.Shared.Alert
|
||||
Weightless,
|
||||
Stun,
|
||||
Handcuffed,
|
||||
Ensnared,
|
||||
Buckled,
|
||||
HumanCrit,
|
||||
HumanDead,
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Content.Shared.CharacterAppearance
|
||||
LFoot,
|
||||
Handcuffs,
|
||||
StencilMask,
|
||||
Ensnare,
|
||||
Fire,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
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="SharedEnsnaringComponent"/>
|
||||
/// </summary>
|
||||
public abstract class SharedEnsnareableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How much should this slow down the entities walk?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("walkSpeed")]
|
||||
public float WalkSpeed = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How much should this slow down the entities sprint?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("sprintSpeed")]
|
||||
public float SprintSpeed = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Is this entity currently ensnared?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("isEnsnared")]
|
||||
public bool IsEnsnared;
|
||||
}
|
||||
|
||||
public sealed class EnsnaredChangedEvent : EntityEventArgs
|
||||
{
|
||||
public readonly bool IsEnsnared;
|
||||
|
||||
public EnsnaredChangedEvent(bool isEnsnared)
|
||||
{
|
||||
IsEnsnared = isEnsnared;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
namespace Content.Shared.Ensnaring.Components;
|
||||
/// <summary>
|
||||
/// Use this on something you want to use to ensnare an entity with
|
||||
/// </summary>
|
||||
public abstract class SharedEnsnaringComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long it should take to free someone else.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("freeTime")]
|
||||
public float FreeTime = 3.5f;
|
||||
|
||||
/// <summary>
|
||||
/// How long it should take for an entity to free themselves.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("breakoutTime")]
|
||||
public float BreakoutTime = 30.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How much should this slow down the entities walk?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("walkSpeed")]
|
||||
public float WalkSpeed = 0.9f;
|
||||
|
||||
/// <summary>
|
||||
/// How much should this slow down the entities sprint?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("sprintSpeed")]
|
||||
public float SprintSpeed = 0.9f;
|
||||
|
||||
/// <summary>
|
||||
/// Should this ensnare someone when thrown?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("canThrowTrigger")]
|
||||
public bool CanThrowTrigger;
|
||||
|
||||
/// <summary>
|
||||
/// What is ensnared?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("ensnared")]
|
||||
public EntityUid? Ensnared;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used whenever you want to do something when someone becomes ensnared by the <see cref="SharedEnsnaringComponent"/>
|
||||
/// </summary>
|
||||
public sealed class EnsnareEvent : EntityEventArgs
|
||||
{
|
||||
public readonly float WalkSpeed;
|
||||
public readonly float SprintSpeed;
|
||||
|
||||
public EnsnareEvent(float walkSpeed, float sprintSpeed)
|
||||
{
|
||||
WalkSpeed = walkSpeed;
|
||||
SprintSpeed = sprintSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used whenever you want to do something when someone is freed by the <see cref="SharedEnsnaringComponent"/>
|
||||
/// </summary>
|
||||
public sealed class EnsnareRemoveEvent : CancellableEntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
9
Content.Shared/Ensnaring/EnsnareableVisuals.cs
Normal file
9
Content.Shared/Ensnaring/EnsnareableVisuals.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Ensnaring;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum EnsnareableVisuals : byte
|
||||
{
|
||||
IsEnsnared
|
||||
}
|
||||
59
Content.Shared/Ensnaring/SharedEnsnareableSystem.cs
Normal file
59
Content.Shared/Ensnaring/SharedEnsnareableSystem.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
|
||||
namespace Content.Shared.Ensnaring;
|
||||
|
||||
public abstract class SharedEnsnareableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _speedModifier = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedEnsnareableComponent, RefreshMovementSpeedModifiersEvent>(MovementSpeedModify);
|
||||
SubscribeLocalEvent<SharedEnsnareableComponent, EnsnareEvent>(OnEnsnare);
|
||||
SubscribeLocalEvent<SharedEnsnareableComponent, EnsnareRemoveEvent>(OnEnsnareRemove);
|
||||
SubscribeLocalEvent<SharedEnsnareableComponent, EnsnaredChangedEvent>(OnEnsnareChange);
|
||||
}
|
||||
|
||||
private void OnEnsnare(EntityUid uid, SharedEnsnareableComponent component, EnsnareEvent args)
|
||||
{
|
||||
component.WalkSpeed = args.WalkSpeed;
|
||||
component.SprintSpeed = args.SprintSpeed;
|
||||
|
||||
_speedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
|
||||
var ev = new EnsnaredChangedEvent(component.IsEnsnared);
|
||||
RaiseLocalEvent(uid, ev, true);
|
||||
}
|
||||
|
||||
private void OnEnsnareRemove(EntityUid uid, SharedEnsnareableComponent component, EnsnareRemoveEvent args)
|
||||
{
|
||||
_speedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
|
||||
var ev = new EnsnaredChangedEvent(component.IsEnsnared);
|
||||
RaiseLocalEvent(uid, ev, true);
|
||||
}
|
||||
|
||||
private void OnEnsnareChange(EntityUid uid, SharedEnsnareableComponent component, EnsnaredChangedEvent args)
|
||||
{
|
||||
UpdateAppearance(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, SharedEnsnareableComponent? component, AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, ref appearance, false))
|
||||
return;
|
||||
|
||||
appearance.SetData(EnsnareableVisuals.IsEnsnared, component.IsEnsnared);
|
||||
}
|
||||
|
||||
private void MovementSpeedModify(EntityUid uid, SharedEnsnareableComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
if (!component.IsEnsnared)
|
||||
return;
|
||||
|
||||
args.ModifySpeed(component.WalkSpeed, component.SprintSpeed);
|
||||
}
|
||||
}
|
||||
@@ -64,18 +64,32 @@ namespace Content.Shared.Strip.Components
|
||||
}
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public sealed class StrippingEnsnareButtonPressed : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid Ensnare { get; }
|
||||
|
||||
public StrippingEnsnareButtonPressed(EntityUid ensnare)
|
||||
{
|
||||
Ensnare = ensnare;
|
||||
}
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public sealed class StrippingBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public Dictionary<(string ID, string Name), string> Inventory { get; }
|
||||
public Dictionary<string, string> Hands { get; }
|
||||
public Dictionary<EntityUid, string> Handcuffs { get; }
|
||||
public Dictionary<EntityUid, string> Ensnare { get; }
|
||||
|
||||
public StrippingBoundUserInterfaceState(Dictionary<(string ID, string Name), string> inventory, Dictionary<string, string> hands, Dictionary<EntityUid, string> handcuffs)
|
||||
public StrippingBoundUserInterfaceState(Dictionary<(string ID, string Name), string> inventory, Dictionary<string, string> hands, Dictionary<EntityUid, string> handcuffs,
|
||||
Dictionary<EntityUid, string> ensnare)
|
||||
{
|
||||
Inventory = inventory;
|
||||
Hands = hands;
|
||||
Handcuffs = handcuffs;
|
||||
Ensnare = ensnare;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Content.Tests.Shared.Alert
|
||||
id: testAlertOrder
|
||||
order:
|
||||
- alertType: Handcuffed
|
||||
- alertType: Ensnared
|
||||
- category: Pressure
|
||||
- category: Hunger
|
||||
- alertType: Hot
|
||||
@@ -47,6 +48,10 @@ namespace Content.Tests.Shared.Alert
|
||||
id: Handcuffed
|
||||
icons: []
|
||||
|
||||
- type: alert
|
||||
id: Ensnared
|
||||
icons: []
|
||||
|
||||
- type: alert
|
||||
id: Hot
|
||||
icons: []
|
||||
@@ -82,6 +87,7 @@ namespace Content.Tests.Shared.Alert
|
||||
// ensure they sort according to our expected criteria
|
||||
var expectedOrder = new List<AlertType>();
|
||||
expectedOrder.Add(AlertType.Handcuffed);
|
||||
expectedOrder.Add(AlertType.Ensnared);
|
||||
expectedOrder.Add(AlertType.HighPressure);
|
||||
// stuff with only category + same category ordered by enum value
|
||||
expectedOrder.Add(AlertType.Peckish);
|
||||
|
||||
5
Resources/Locale/en-US/ensnare/ensnare-component.ftl
Normal file
5
Resources/Locale/en-US/ensnare/ensnare-component.ftl
Normal file
@@ -0,0 +1,5 @@
|
||||
ensnare-component-try-free = You struggle to remove {$ensnare} that's ensnaring you!
|
||||
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-other = You start removing the {$ensnare} caught on {$user}!
|
||||
@@ -17,4 +17,5 @@ strip-verb-get-data-text = Strip
|
||||
|
||||
strippable-bound-user-interface-stripping-menu-title = {$ownerName}'s inventory
|
||||
strippable-bound-user-interface-stripping-menu-handcuffs-button = Restraints
|
||||
strippable-bound-user-interface-stripping-menu-ensnare-button = Leg Restraints
|
||||
strippable-bound-user-interface-stripping-menu-obfuscate = Occupied
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- category: Internals
|
||||
- alertType: Fire
|
||||
- alertType: Handcuffed
|
||||
- alertType: Ensnared
|
||||
- category: Buckled
|
||||
- alertType: Pulling
|
||||
- category: Piloting
|
||||
@@ -120,6 +121,13 @@
|
||||
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."
|
||||
|
||||
- type: alert
|
||||
id: Ensnared
|
||||
onClick: !type:RemoveEnsnare { }
|
||||
icons: [ /Textures/Interface/Alerts/ensnared.rsi/ensnared.png ]
|
||||
name: "[color=yellow]Ensnared[/color]"
|
||||
description: "You're [color=yellow]ensnared[/color] and is impairing your ability to move."
|
||||
|
||||
- type: alert
|
||||
id: Buckled
|
||||
category: Buckled
|
||||
|
||||
@@ -280,9 +280,13 @@
|
||||
normalState: Generic_mob_burning
|
||||
alternateState: Standing
|
||||
fireStackAlternateState: 3
|
||||
- type: EnsnareableVisualizer
|
||||
- type: CombatMode
|
||||
- type: Climbing
|
||||
- type: Cuffable
|
||||
- type: Ensnareable
|
||||
sprite: Objects/Misc/ensnare.rsi
|
||||
state: icon
|
||||
- type: CharacterInfo
|
||||
- type: AnimationPlayer
|
||||
- type: Buckle
|
||||
|
||||
@@ -34,3 +34,11 @@
|
||||
damage:
|
||||
types:
|
||||
Blunt: 5
|
||||
- type: Ensnaring
|
||||
freeTime: 2.0
|
||||
breakoutTime: 3.5 #all bola should generally be fast to remove
|
||||
walkSpeed: 0.7 #makeshift bola shouldn't slow too much
|
||||
sprintSpeed: 0.7
|
||||
canThrowTrigger: true
|
||||
canMoveBreakout: true
|
||||
|
||||
|
||||
BIN
Resources/Textures/Interface/Alerts/ensnared.rsi/ensnared.png
Normal file
BIN
Resources/Textures/Interface/Alerts/ensnared.rsi/ensnared.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
14
Resources/Textures/Interface/Alerts/ensnared.rsi/meta.json
Normal file
14
Resources/Textures/Interface/Alerts/ensnared.rsi/meta.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Ensnared alert created by Hyenh. Bear Trap sprite taken from Citadel Station at https://github.com/Citadel-Station-13/Citadel-Station-13/commit/3cfea7eb92246d311de8b531347795bc76d6dab6",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "ensnared"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Resources/Textures/Objects/Misc/ensnare.rsi/icon.png
Normal file
BIN
Resources/Textures/Objects/Misc/ensnare.rsi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 437 B |
15
Resources/Textures/Objects/Misc/ensnare.rsi/meta.json
Normal file
15
Resources/Textures/Objects/Misc/ensnare.rsi/meta.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/Citadel-Station-13/Citadel-Station-13/commit/2bf6d888bd89e614ec4d83fefa2130231e7ddc3d",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "icon",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user