Ensnaring Component and Bola Update (#9968)

This commit is contained in:
keronshb
2022-08-24 10:50:31 -04:00
committed by GitHub
parent 16be5184a4
commit cd78c5451d
29 changed files with 681 additions and 2 deletions

View 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;
}

View File

@@ -0,0 +1,9 @@
using Content.Shared.Ensnaring.Components;
namespace Content.Client.Ensnaring.Components;
[RegisterComponent]
[ComponentReference(typeof(SharedEnsnaringComponent))]
public sealed class EnsnaringComponent : SharedEnsnaringComponent
{
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Utility;
namespace Content.Client.Ensnaring.Visualizers;
[RegisterComponent]
[Access(typeof(EnsnareableVisualizerSystem))]
public sealed class EnsnareableVisualizerComponent : Component
{
}

View File

@@ -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,
}

View File

@@ -12,6 +12,7 @@ namespace Content.Client.Inventory
public Dictionary<(string ID, string Name), string>? Inventory { get; private set; } public Dictionary<(string ID, string Name), string>? Inventory { get; private set; }
public Dictionary<string, string>? Hands { get; private set; } public Dictionary<string, string>? Hands { get; private set; }
public Dictionary<EntityUid, string>? Handcuffs { get; private set; } public Dictionary<EntityUid, string>? Handcuffs { get; private set; }
public Dictionary<EntityUid, string>? Ensnare { get; private set; }
[ViewVariables] [ViewVariables]
private StrippingMenu? _strippingMenu; 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) protected override void UpdateState(BoundUserInterfaceState state)
@@ -90,6 +102,7 @@ namespace Content.Client.Inventory
Inventory = stripState.Inventory; Inventory = stripState.Inventory;
Hands = stripState.Hands; Hands = stripState.Hands;
Handcuffs = stripState.Handcuffs; Handcuffs = stripState.Handcuffs;
Ensnare = stripState.Ensnare;
UpdateMenu(); UpdateMenu();
} }

View 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);
}
}
}
}

View 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!;
}

View 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;
}
}

View 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);
}
}
}

View 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);
}
}

View File

@@ -17,6 +17,7 @@ namespace Content.Server.Entry
"CharacterInfo", "CharacterInfo",
"HandheldGPS", "HandheldGPS",
"CableVisualizer", "CableVisualizer",
"EnsnareableVisualizer",
}; };
} }
} }

View File

@@ -1,9 +1,13 @@
using System.ComponentModel;
using System.Threading; using System.Threading;
using Content.Server.Cuffs.Components; using Content.Server.Cuffs.Components;
using Content.Server.DoAfter; using Content.Server.DoAfter;
using Content.Server.Ensnaring;
using Content.Server.Ensnaring.Components;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Inventory; using Content.Server.Inventory;
using Content.Server.UserInterface; using Content.Server.UserInterface;
using Content.Shared.Ensnaring.Components;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
@@ -14,6 +18,7 @@ using Content.Shared.Popups;
using Content.Shared.Strip.Components; using Content.Shared.Strip.Components;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Player; using Robust.Shared.Player;
namespace Content.Server.Strip namespace Content.Server.Strip
@@ -24,6 +29,7 @@ namespace Content.Server.Strip
[Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly EnsnareableSystem _ensnaring = default!;
// TODO: ECS popups. Not all of these have ECS equivalents yet. // 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, DidUnequipEvent>(OnDidUnequip);
SubscribeLocalEvent<StrippableComponent, ComponentInit>(OnCompInit); SubscribeLocalEvent<StrippableComponent, ComponentInit>(OnCompInit);
SubscribeLocalEvent<StrippableComponent, CuffedStateChangeEvent>(OnCuffStateChange); SubscribeLocalEvent<StrippableComponent, CuffedStateChangeEvent>(OnCuffStateChange);
SubscribeLocalEvent<StrippableComponent, EnsnaredChangedEvent>(OnEnsnareChange);
// BUI // BUI
SubscribeLocalEvent<StrippableComponent, StrippingInventoryButtonPressed>(OnStripInvButtonMessage); SubscribeLocalEvent<StrippableComponent, StrippingInventoryButtonPressed>(OnStripInvButtonMessage);
SubscribeLocalEvent<StrippableComponent, StrippingHandButtonPressed>(OnStripHandMessage); SubscribeLocalEvent<StrippableComponent, StrippingHandButtonPressed>(OnStripHandMessage);
SubscribeLocalEvent<StrippableComponent, StrippingHandcuffButtonPressed>(OnStripHandcuffMessage); SubscribeLocalEvent<StrippableComponent, StrippingHandcuffButtonPressed>(OnStripHandcuffMessage);
SubscribeLocalEvent<StrippableComponent, StrippingEnsnareButtonPressed>(OnStripEnsnareMessage);
SubscribeLocalEvent<StrippableComponent, OpenStrippingCompleteEvent>(OnOpenStripComplete); SubscribeLocalEvent<StrippableComponent, OpenStrippingCompleteEvent>(OnOpenStripComplete);
SubscribeLocalEvent<StrippableComponent, OpenStrippingCancelledEvent>(OnOpenStripCancelled); 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) private void OnStripHandMessage(EntityUid uid, StrippableComponent component, StrippingHandButtonPressed args)
{ {
if (args.Session.AttachedEntity is not {Valid: true} user || if (args.Session.AttachedEntity is not {Valid: true} user ||
@@ -154,6 +182,11 @@ namespace Content.Server.Strip
UpdateState(uid, component); UpdateState(uid, component);
} }
private void OnEnsnareChange(EntityUid uid, StrippableComponent component, EnsnaredChangedEvent args)
{
SendUpdate(uid, component);
}
private void OnDidUnequip(EntityUid uid, StrippableComponent component, DidUnequipEvent args) private void OnDidUnequip(EntityUid uid, StrippableComponent component, DidUnequipEvent args)
{ {
SendUpdate(uid, component); SendUpdate(uid, component);
@@ -174,6 +207,7 @@ namespace Content.Server.Strip
} }
var cuffs = new Dictionary<EntityUid, string>(); var cuffs = new Dictionary<EntityUid, string>();
var ensnare = new Dictionary<EntityUid, string>();
var inventory = new Dictionary<(string ID, string Name), string>(); var inventory = new Dictionary<(string ID, string Name), string>();
var hands = new Dictionary<string, 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)) if (_inventorySystem.TryGetSlots(uid, out var slots))
{ {
foreach (var slot in 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) private void AddStripVerb(EntityUid uid, StrippableComponent component, GetVerbsEvent<Verb> args)

View File

@@ -17,6 +17,7 @@ namespace Content.Shared.Alert
Weightless, Weightless,
Stun, Stun,
Handcuffed, Handcuffed,
Ensnared,
Buckled, Buckled,
HumanCrit, HumanCrit,
HumanDead, HumanDead,

View File

@@ -24,6 +24,7 @@ namespace Content.Shared.CharacterAppearance
LFoot, LFoot,
Handcuffs, Handcuffs,
StencilMask, StencilMask,
Ensnare,
Fire, Fire,
} }
} }

View File

@@ -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;
}
}

View File

@@ -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
{
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Ensnaring;
[Serializable, NetSerializable]
public enum EnsnareableVisuals : byte
{
IsEnsnared
}

View 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);
}
}

View File

@@ -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] [NetSerializable, Serializable]
public sealed class StrippingBoundUserInterfaceState : BoundUserInterfaceState public sealed class StrippingBoundUserInterfaceState : BoundUserInterfaceState
{ {
public Dictionary<(string ID, string Name), string> Inventory { get; } public Dictionary<(string ID, string Name), string> Inventory { get; }
public Dictionary<string, string> Hands { get; } public Dictionary<string, string> Hands { get; }
public Dictionary<EntityUid, string> Handcuffs { 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; Inventory = inventory;
Hands = hands; Hands = hands;
Handcuffs = handcuffs; Handcuffs = handcuffs;
Ensnare = ensnare;
} }
} }

View File

@@ -17,6 +17,7 @@ namespace Content.Tests.Shared.Alert
id: testAlertOrder id: testAlertOrder
order: order:
- alertType: Handcuffed - alertType: Handcuffed
- alertType: Ensnared
- category: Pressure - category: Pressure
- category: Hunger - category: Hunger
- alertType: Hot - alertType: Hot
@@ -47,6 +48,10 @@ namespace Content.Tests.Shared.Alert
id: Handcuffed id: Handcuffed
icons: [] icons: []
- type: alert
id: Ensnared
icons: []
- type: alert - type: alert
id: Hot id: Hot
icons: [] icons: []
@@ -82,6 +87,7 @@ namespace Content.Tests.Shared.Alert
// ensure they sort according to our expected criteria // ensure they sort according to our expected criteria
var expectedOrder = new List<AlertType>(); var expectedOrder = new List<AlertType>();
expectedOrder.Add(AlertType.Handcuffed); expectedOrder.Add(AlertType.Handcuffed);
expectedOrder.Add(AlertType.Ensnared);
expectedOrder.Add(AlertType.HighPressure); expectedOrder.Add(AlertType.HighPressure);
// stuff with only category + same category ordered by enum value // stuff with only category + same category ordered by enum value
expectedOrder.Add(AlertType.Peckish); expectedOrder.Add(AlertType.Peckish);

View 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}!

View File

@@ -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-title = {$ownerName}'s inventory
strippable-bound-user-interface-stripping-menu-handcuffs-button = Restraints 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 strippable-bound-user-interface-stripping-menu-obfuscate = Occupied

View File

@@ -9,6 +9,7 @@
- category: Internals - category: Internals
- alertType: Fire - alertType: Fire
- alertType: Handcuffed - alertType: Handcuffed
- alertType: Ensnared
- category: Buckled - category: Buckled
- alertType: Pulling - alertType: Pulling
- category: Piloting - category: Piloting
@@ -120,6 +121,13 @@
name: "[color=yellow]Handcuffed[/color]" 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
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 - type: alert
id: Buckled id: Buckled
category: Buckled category: Buckled

View File

@@ -280,9 +280,13 @@
normalState: Generic_mob_burning normalState: Generic_mob_burning
alternateState: Standing alternateState: Standing
fireStackAlternateState: 3 fireStackAlternateState: 3
- type: EnsnareableVisualizer
- type: CombatMode - type: CombatMode
- type: Climbing - type: Climbing
- type: Cuffable - type: Cuffable
- type: Ensnareable
sprite: Objects/Misc/ensnare.rsi
state: icon
- type: CharacterInfo - type: CharacterInfo
- type: AnimationPlayer - type: AnimationPlayer
- type: Buckle - type: Buckle

View File

@@ -34,3 +34,11 @@
damage: damage:
types: types:
Blunt: 5 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View 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"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

View 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
}
]
}