Skip to content

Commit

Permalink
Animated handcuffs now chase down and restrain people
Browse files Browse the repository at this point in the history
Sentient handcuffs can now restain people using themselves
  • Loading branch information
TGRCdev committed Sep 13, 2024
1 parent 89e2e8d commit 1e3e2e6
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 12 deletions.
3 changes: 0 additions & 3 deletions Content.Client/Revenant/RevenantAnimatedSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ private void OnStartup(EntityUid uid, RevenantAnimatedComponent comp, ComponentS
private void OnShutdown(EntityUid uid, RevenantAnimatedComponent comp, ComponentShutdown args)
{
if (comp.LightOverlay != null)
{
Del(comp.LightOverlay);
return;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;

namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions;

public sealed partial class InteractionActivateOperator : HTNOperator
{
[Dependency] private readonly IEntityManager _entManager = default!;

[DataField("targetKey")]
public string Key = "Target";

/// <summary>
/// If this alt-interaction started a do_after where does the key get stored.
/// </summary>
[DataField("idleKey")]
public string IdleKey = "IdleTime";

public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken)
{
return new(true, new Dictionary<string, object>()
{
{ IdleKey, 1f }
});
}

public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
{
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
var target = blackboard.GetValue<EntityUid>(Key);
var intSystem = _entManager.System<SharedInteractionSystem>();
var count = 0;

if (_entManager.TryGetComponent<DoAfterComponent>(owner, out var doAfter))
{
count = doAfter.DoAfters.Count;
}

var result = intSystem.InteractionActivate(owner, target);

// Interaction started a doafter so set the idle time to it.
if (result && doAfter != null && count != doAfter.DoAfters.Count)
{
var wait = doAfter.DoAfters.First().Value.Args.Delay;
blackboard.SetValue(IdleKey, (float) wait.TotalSeconds + 0.5f);
}
else
{
blackboard.SetValue(IdleKey, 1f);
}

return result ? HTNOperatorStatus.Finished : HTNOperatorStatus.Failed;
}
}
10 changes: 10 additions & 0 deletions Content.Server/NPC/Queries/Considerations/TargetIsCuffableCon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Content.Server.NPC.Queries.Considerations;

/// <summary>
/// Returns 1f if the target can be cuffed or 0f if not.
/// </summary>

public sealed partial class TargetIsCuffableCon : UtilityConsideration
{

}
13 changes: 13 additions & 0 deletions Content.Server/NPC/Systems/NPCUtilitySystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Storage.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Cuffs;
using Content.Shared.Cuffs.Components;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Fluids.Components;
Expand Down Expand Up @@ -52,6 +54,7 @@ public sealed class NPCUtilitySystem : EntitySystem
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly MobThresholdSystem _thresholdSystem = default!;
[Dependency] private readonly SharedCuffableSystem _cuffableSystem = default!;

private EntityQuery<PuddleComponent> _puddleQuery;
private EntityQuery<TransformComponent> _xformQuery;
Expand Down Expand Up @@ -351,6 +354,16 @@ private float GetScore(NPCBlackboard blackboard, EntityUid targetUid, UtilityCon

return 0f;
}
case TargetIsCuffableCon:
{
if (TryComp<CuffableComponent>(targetUid, out var cuffable))
{
if(_cuffableSystem.IsCuffed((targetUid, cuffable), true))
return 0f;
return 1f;
}
return 0f;
}
default:
throw new NotImplementedException();
}
Expand Down
84 changes: 84 additions & 0 deletions Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Content.Shared.Cuffs;
using Content.Shared.Cuffs.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Inventory;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Containers;
using Content.Server.Revenant.Components;
using Content.Shared.Interaction;
using Content.Shared.Popups;

namespace Content.Server.Revenant.EntitySystems;

public sealed class RevenantAnimatedSystem : EntitySystem
{
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<RevenantAnimatedComponent, MeleeHitEvent>(OnMeleeHit, before: [typeof(SharedCuffableSystem)]);
SubscribeLocalEvent<RevenantAnimatedComponent, UserActivateInWorldEvent>(OnCuffInteract, before: [typeof(SharedCuffableSystem)]);
}

private void OnMeleeHit(EntityUid uid, RevenantAnimatedComponent comp, MeleeHitEvent args)
{
if (args.Handled)
return;

if (args.HitEntities.Count == 0)
return;

var hitEntity = args.HitEntities[0];

// Handcuffs will attempt to jump into the victim's hands/pockets before trying to cuff them
if (HasComp<HandcuffComponent>(uid) && HasComp<CuffableComponent>(hitEntity))
TryJumpIntoSlots(uid, hitEntity);
}

private void OnCuffInteract(EntityUid uid, RevenantAnimatedComponent comp, UserActivateInWorldEvent args)
{
if (args.Handled)
return;

if (HasComp<HandcuffComponent>(uid) && HasComp<CuffableComponent>(args.Target))
TryJumpIntoSlots(uid, args.Target);
}

private void TryJumpIntoSlots(EntityUid uid, EntityUid target)
{
if (_container.ContainsEntity(target, uid))
return;

Log.Debug($"{uid} trying to jump into {target} pocket1");

if (_inventory.TryGetSlotContainer(target, "pocket1", out var pocket1, out _)
&& _container.Insert(uid, pocket1)
)
{
_popup.PopupEntity(Loc.GetString("item-jump-into-pocket", ("name", Comp<MetaDataComponent>(uid).EntityName)), target, target);
return;
}

Log.Debug($"{uid} trying to jump into {target} pocket2");

if (_inventory.TryGetSlotContainer(target, "pocket2", out var pocket2, out _)
&& _container.Insert(uid, pocket2)
)
{
_popup.PopupEntity(Loc.GetString("item-jump-into-pocket", ("name", Comp<MetaDataComponent>(uid).EntityName)), target, target);
return;
}

Log.Debug($"{uid} trying to jump into {target} hands");
if (_hands.TryPickupAnyHand(target, uid))
{
_popup.PopupEntity(Loc.GetString("item-jump-into-hands", ("name", Comp<MetaDataComponent>(uid).EntityName)), target, target);
return;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,6 @@ public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity<Revena
if (HasComp<MindContainerComponent>(target) || HasComp<HTNComponent>(target))
return;

// TODO: Make animated handcuffs cuff people and then go inanimate
// Disabling them for now because it causes a ton of errors.
if (HasComp<HandcuffComponent>(target))
return;

if (revenant != null && !TryUseAbility(revenant.Value.Owner, revenant.Value.Comp, revenant.Value.Comp.AnimateCost, revenant.Value.Comp.AnimateDebuffs))
return;

Expand Down Expand Up @@ -448,13 +443,17 @@ public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity<Revena
_factionSystem.ClearFactions((target, factions));
_factionSystem.AddFaction((target, factions), "SimpleHostile");

EnsureComp<DoAfterComponent>(target);

var htn = EnsureComp<HTNComponent>(target);
if (TryComp<GunComponent>(target, out var gun))
{
if (TryComp<ChamberMagazineAmmoProviderComponent>(target, out var bolt))
_gunSystem.SetBoltClosed(target, bolt, true);
htn.RootTask = new HTNCompoundTask() { Task = "SimpleRangedHostileCompound" };
}
else if (TryComp<HandcuffComponent>(target, out var handcuff))
htn.RootTask = new HTNCompoundTask() { Task = "AnimatedHandcuffsCompound" };
else
htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" };
htn.Blackboard.SetValue(NPCBlackboard.Owner, target);
Expand Down
17 changes: 14 additions & 3 deletions Content.Shared/Cuffs/SharedCuffableSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public override void Initialize()
SubscribeLocalEvent<HandcuffComponent, MeleeHitEvent>(OnCuffMeleeHit);
SubscribeLocalEvent<HandcuffComponent, AddCuffDoAfterEvent>(OnAddCuffDoAfter);
SubscribeLocalEvent<HandcuffComponent, VirtualItemDeletedEvent>(OnCuffVirtualItemDeleted);
SubscribeLocalEvent<HandcuffComponent, UserActivateInWorldEvent>(OnCuffInteract);
}

private void CheckInteract(Entity<CuffableComponent> ent, ref InteractionAttemptEvent args)
Expand Down Expand Up @@ -323,6 +324,15 @@ private void OnCuffMeleeHit(EntityUid uid, HandcuffComponent component, MeleeHit
args.Handled = true;
}

private void OnCuffInteract(EntityUid uid, HandcuffComponent component, UserActivateInWorldEvent args)
{
if (args.Handled)
return;

TryCuffing(args.User, args.Target, uid, component);
args.Handled = true;
}

private void OnAddCuffDoAfter(EntityUid uid, HandcuffComponent component, AddCuffDoAfterEvent args)
{
var user = args.Args.User;
Expand Down Expand Up @@ -462,7 +472,8 @@ public bool TryAddNewCuffs(EntityUid target, EntityUid user, EntityUid handcuff,
return false;

// Success!
_hands.TryDrop(user, handcuff);
if (user != handcuff)
_hands.TryDrop(user, handcuff);

_container.Insert(handcuff, component.Container);
UpdateHeldItems(target, handcuff, component);
Expand All @@ -489,7 +500,7 @@ public bool TryCuffing(EntityUid user, EntityUid target, EntityUid handcuff, Han
return true;
}

if (!_hands.CanDrop(user, handcuff))
if (user != handcuff && !_hands.CanDrop(user, handcuff))
{
_popup.PopupClient(Loc.GetString("handcuff-component-cannot-drop-cuffs", ("target", Identity.Name(target, EntityManager, user))), user, user);
return false;
Expand All @@ -508,7 +519,7 @@ public bool TryCuffing(EntityUid user, EntityUid target, EntityUid handcuff, Han
BreakOnMove = true,
BreakOnWeightlessMove = false,
BreakOnDamage = true,
NeedHand = true,
NeedHand = user != handcuff,
DistanceThreshold = 1f // shorter than default but still feels good
};

Expand Down
5 changes: 4 additions & 1 deletion Resources/Locale/en-US/revenant/revenant.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ revenant-soul-finish-harvest = {CAPITALIZE(THE($target))} slumps onto the ground
revenant-user-interface-title = Ability Shop
revenant-user-interface-essence-amount = [color=plum]{$amount}[/color] Stolen Essence
revenant-user-interface-cost = {$price} Essence
revenant-user-interface-cost = {$price} Essence
item-jump-into-pocket = The {$name} jumps into your pocket!
item-jump-into-hands = The {$name} jumps into your hands!
4 changes: 4 additions & 0 deletions Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- type: Handcuff
cuffedRSI: Objects/Misc/handcuffs.rsi
bodyIconState: body-overlay
- type: DoAfter
- type: Sprite
sprite: Objects/Misc/handcuffs.rsi
state: handcuff
Expand Down Expand Up @@ -53,6 +54,7 @@
path: /Audio/Items/Handcuffs/rope_breakout.ogg
startBreakoutSound:
path: /Audio/Items/Handcuffs/rope_takeoff.ogg
- type: DoAfter
- type: Construction
graph: makeshifthandcuffs
node: cuffscable
Expand Down Expand Up @@ -94,6 +96,7 @@
path: /Audio/Items/Handcuffs/rope_breakout.ogg
startBreakoutSound:
path: /Audio/Items/Handcuffs/rope_takeoff.ogg
- type: DoAfter
- type: Sprite
sprite: Objects/Misc/zipties.rsi
state: cuff
Expand Down Expand Up @@ -149,6 +152,7 @@
components:
- type: Item
size: Normal
- type: DoAfter
- type: Handcuff
cuffedRSI: Clothing/OuterClothing/Misc/straight_jacket.rsi
breakoutTime: 100
Expand Down
49 changes: 49 additions & 0 deletions Resources/Prototypes/NPCs/Animated/handcuffs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
- type: htnCompound
id: AnimatedHandcuffsCompound
branches:
- tasks:
- !type:HTNPrimitiveTask
operator: !type:UtilityOperator
proto: NearbyCuffableTargets

- !type:HTNPrimitiveTask
operator: !type:MoveToOperator
pathfindInPlanning: true
removeKeyOnFinish: false
targetKey: TargetCoordinates
pathfindKey: TargetPathfind
rangeKey: MeleeRange

- !type:PrimitiveTask
preconditions:
- !type:KeyExistsPrecondition
key: Target
operator: !type:InteractionActivateOperator

- !type:HTNPrimitiveTask
preconditions:
- !type:KeyExistsPrecondition
key: IdleTime
operator: !type:WaitOperator
key: IdleTime

- tasks:
- !type:HTNCompoundTask
task: IdleCompound

- type: utilityQuery
id: NearbyCuffableTargets
query:
- !type:NearbyHostilesQuery
considerations:
- !type:TargetIsAliveCon
curve: !type:BoolCurve
- !type:TargetDistanceCon
curve: !type:PresetCurve
preset: TargetDistance
- !type:TargetAccessibleCon
curve: !type:BoolCurve
- !type:TargetInLOSOrCurrentCon
curve: !type:BoolCurve
- !type:TargetIsCuffableCon
curve: !type:BoolCurve

0 comments on commit 1e3e2e6

Please sign in to comment.