Skip to content

Commit

Permalink
Adding shock collar and electropack (#30529)
Browse files Browse the repository at this point in the history
* Adding shock collar with the new ShockOnTrigger

* Cleaning and updating the shock collar

* Add StripDelay datafield to ClothingComponent

* Adding SelfUnremovableClothingComponent

* ShockCollar Update

* Correction of the shock collar

* Correction of the shock collar 2

* Renaming the DamageSpecifier DataField to Damage

* Fixing the damage field in ShockCollar

* Cleaning the ShockCollar

* Renaming ShockCollar to ClothingNeckShockCollar

* Adding ClothingNeckShockCollar as a stealTarget to a thief

* Fixing a typo of the sprite path in ClothingNeckShockCollar

* Cleaning the ShockOnTriggerComponent

* Revision of SelfUnremovableClothing

* Adding a ClothingBackpackElectropack

* Sprite fix

* Code review

* Shock Collar sprite update

* add commit hash

---------

Co-authored-by: Nemanja <[email protected]>
  • Loading branch information
chavonadelal and EmoGarbage404 authored Aug 15, 2024
1 parent 02eed07 commit 6567fa3
Show file tree
Hide file tree
Showing 29 changed files with 326 additions and 21 deletions.
37 changes: 37 additions & 0 deletions Content.Server/Explosion/Components/ShockOnTriggerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Content.Server.Explosion.EntitySystems;

namespace Content.Server.Explosion.Components;

/// <summary>
/// A component that electrocutes an entity having this component when a trigger is triggered.
/// </summary>
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(TriggerSystem))]
public sealed partial class ShockOnTriggerComponent : Component
{
/// <summary>
/// The force of an electric shock when the trigger is triggered.
/// </summary>
[DataField]
public int Damage = 5;

/// <summary>
/// Duration of electric shock when the trigger is triggered.
/// </summary>
[DataField]
public TimeSpan Duration = TimeSpan.FromSeconds(2);

/// <summary>
/// The minimum delay between repeating triggers.
/// </summary>
[DataField]
public TimeSpan Cooldown = TimeSpan.FromSeconds(4);

/// <summary>
/// When can the trigger run again?
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextTrigger = TimeSpan.Zero;
}
22 changes: 22 additions & 0 deletions Content.Server/Explosion/EntitySystems/TriggerSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Explosion.Components;
using Content.Server.Flash;
using Content.Server.Electrocution;
using Content.Server.Pinpointer;
using Content.Shared.Flash.Components;
using Content.Server.Radio.EntitySystems;
Expand Down Expand Up @@ -33,6 +34,7 @@
using Robust.Shared.Player;
using Content.Shared.Coordinates;
using Robust.Shared.Utility;
using Robust.Shared.Timing;

namespace Content.Server.Explosion.EntitySystems
{
Expand Down Expand Up @@ -75,6 +77,7 @@ public sealed partial class TriggerSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly ElectrocutionSystem _electrocution = default!;

public override void Initialize()
{
Expand Down Expand Up @@ -104,6 +107,7 @@ public override void Initialize()

SubscribeLocalEvent<AnchorOnTriggerComponent, TriggerEvent>(OnAnchorTrigger);
SubscribeLocalEvent<SoundOnTriggerComponent, TriggerEvent>(OnSoundTrigger);
SubscribeLocalEvent<ShockOnTriggerComponent, TriggerEvent>(HandleShockTrigger);
SubscribeLocalEvent<RattleComponent, TriggerEvent>(HandleRattleTrigger);
}

Expand All @@ -120,6 +124,24 @@ private void OnSoundTrigger(EntityUid uid, SoundOnTriggerComponent component, Tr
}
}

private void HandleShockTrigger(Entity<ShockOnTriggerComponent> shockOnTrigger, ref TriggerEvent args)
{
if (!_container.TryGetContainingContainer(shockOnTrigger, out var container))
return;

var containerEnt = container.Owner;
var curTime = _timing.CurTime;

if (curTime < shockOnTrigger.Comp.NextTrigger)
{
// The trigger's on cooldown.
return;
}

_electrocution.TryDoElectrocution(containerEnt, null, shockOnTrigger.Comp.Damage, shockOnTrigger.Comp.Duration, true);
shockOnTrigger.Comp.NextTrigger = curTime + shockOnTrigger.Comp.Cooldown;
}

private void OnAnchorTrigger(EntityUid uid, AnchorOnTriggerComponent component, TriggerEvent args)
{
var xform = Transform(uid);
Expand Down
8 changes: 4 additions & 4 deletions Content.Server/Strip/StrippableSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ private void StartStripInsertInventory(
return;
}

var (time, stealth) = GetStripTimeModifiers(user, target, slotDef.StripTime);
var (time, stealth) = GetStripTimeModifiers(user, target, held, slotDef.StripTime);

if (!stealth)
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert", ("user", Identity.Entity(user, EntityManager)), ("item", user.Comp.ActiveHandEntity!.Value)), target, target, PopupType.Large);
Expand Down Expand Up @@ -306,7 +306,7 @@ private void StartStripRemoveInventory(
return;
}

var (time, stealth) = GetStripTimeModifiers(user, target, slotDef.StripTime);
var (time, stealth) = GetStripTimeModifiers(user, target, item, slotDef.StripTime);

if (!stealth)
{
Expand Down Expand Up @@ -411,7 +411,7 @@ private void StartStripInsertHand(
if (!CanStripInsertHand(user, target, held, handName))
return;

var (time, stealth) = GetStripTimeModifiers(user, target, targetStrippable.HandStripDelay);
var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay);

if (!stealth)
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert-hand", ("user", Identity.Entity(user, EntityManager)), ("item", user.Comp.ActiveHandEntity!.Value)), target, target, PopupType.Large);
Expand Down Expand Up @@ -510,7 +510,7 @@ private void StartStripRemoveHand(
if (!CanStripRemoveHand(user, target, item, handName))
return;

var (time, stealth) = GetStripTimeModifiers(user, target, targetStrippable.HandStripDelay);
var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay);

if (!stealth)
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", item)), target, target);
Expand Down
7 changes: 7 additions & 0 deletions Content.Shared/Clothing/Components/ClothingComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public sealed partial class ClothingComponent : Component

[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan UnequipDelay = TimeSpan.Zero;

/// <summary>
/// Offset for the strip time for an entity with this component.
/// Only applied when it is being equipped or removed by another player.
/// </summary>
[DataField]
public TimeSpan StripDelay = TimeSpan.Zero;
}

[Serializable, NetSerializable]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Content.Shared.Clothing.EntitySystems;
using Robust.Shared.GameStates;

namespace Content.Shared.Clothing.Components;

/// <summary>
/// The component prohibits the player from taking off clothes on them that have this component.
/// </summary>
/// <remarks>
/// See also ClothingComponent.EquipDelay if you want the clothes that the player cannot take off by himself to be put on by the player with a delay.
///</remarks>
[NetworkedComponent]
[RegisterComponent]
[Access(typeof(SelfUnremovableClothingSystem))]
public sealed partial class SelfUnremovableClothingComponent : Component
{

}
8 changes: 8 additions & 0 deletions Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Strip.Components;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;

Expand All @@ -32,6 +33,8 @@ public override void Initialize()

SubscribeLocalEvent<ClothingComponent, ClothingEquipDoAfterEvent>(OnEquipDoAfter);
SubscribeLocalEvent<ClothingComponent, ClothingUnequipDoAfterEvent>(OnUnequipDoAfter);

SubscribeLocalEvent<ClothingComponent, BeforeItemStrippedEvent>(OnItemStripped);
}

private void OnUseInHand(Entity<ClothingComponent> ent, ref UseInHandEvent args)
Expand Down Expand Up @@ -192,6 +195,11 @@ private void OnUnequipDoAfter(Entity<ClothingComponent> ent, ref ClothingUnequip
_handsSystem.TryPickup(args.User, ent);
}

private void OnItemStripped(Entity<ClothingComponent> ent, ref BeforeItemStrippedEvent args)
{
args.Additive += ent.Comp.StripDelay;
}

private void CheckEquipmentForLayerHide(EntityUid equipment, EntityUid equipee)
{
if (TryComp(equipment, out HideLayerClothingComponent? clothesComp) && TryComp(equipee, out HumanoidAppearanceComponent? appearanceComp))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Content.Shared.Clothing.Components;
using Content.Shared.Examine;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;

namespace Content.Shared.Clothing.EntitySystems;

/// <summary>
/// A system for the operation of a component that prohibits the player from taking off his own clothes that have this component.
/// </summary>
public sealed class SelfUnremovableClothingSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<SelfUnremovableClothingComponent, BeingUnequippedAttemptEvent>(OnUnequip);
SubscribeLocalEvent<SelfUnremovableClothingComponent, ExaminedEvent>(OnUnequipMarkup);
}

private void OnUnequip(Entity<SelfUnremovableClothingComponent> selfUnremovableClothing, ref BeingUnequippedAttemptEvent args)
{
if (TryComp<ClothingComponent>(selfUnremovableClothing, out var clothing) && (clothing.Slots & args.SlotFlags) == SlotFlags.NONE)
return;

if (args.UnEquipTarget == args.Unequipee)
{
args.Cancel();
}
}

private void OnUnequipMarkup(Entity<SelfUnremovableClothingComponent> selfUnremovableClothing, ref ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("comp-self-unremovable-clothing"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ private void StartDoAfter(EntityUid user, EntityUid item, EntityUid wearer, Togg
if (component.StripDelay == null)
return;

var (time, stealth) = _strippable.GetStripTimeModifiers(user, wearer, component.StripDelay.Value);
var (time, stealth) = _strippable.GetStripTimeModifiers(user, wearer, item, component.StripDelay.Value);

var args = new DoAfterArgs(EntityManager, user, time, new ToggleClothingDoAfterEvent(), item, wearer, item)
{
Expand Down
9 changes: 9 additions & 0 deletions Content.Shared/Strip/Components/StrippableComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ public abstract class BaseBeforeStripEvent(TimeSpan initialTime, bool stealth =
public SlotFlags TargetSlots { get; } = SlotFlags.GLOVES;
}

/// <summary>
/// Used to modify strip times. Raised directed at the item being stripped.
/// </summary>
/// <remarks>
/// This is also used by some stripping related interactions, i.e., interactions with items that are currently equipped by another player.
/// </remarks>
[ByRefEvent]
public sealed class BeforeItemStrippedEvent(TimeSpan initialTime, bool stealth = false) : BaseBeforeStripEvent(initialTime, stealth);

/// <summary>
/// Used to modify strip times. Raised directed at the user.
/// </summary>
Expand Down
16 changes: 11 additions & 5 deletions Content.Shared/Strip/SharedStrippableSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ private void OnActivateInWorld(EntityUid uid, StrippableComponent component, Act
args.Handled = true;
}

public (TimeSpan Time, bool Stealth) GetStripTimeModifiers(EntityUid user, EntityUid target, TimeSpan initialTime)
/// <summary>
/// Modify the strip time via events. Raised directed at the item being stripped, the player stripping someone and the player being stripped.
/// </summary>
public (TimeSpan Time, bool Stealth) GetStripTimeModifiers(EntityUid user, EntityUid targetPlayer, EntityUid? targetItem, TimeSpan initialTime)
{
var userEv = new BeforeStripEvent(initialTime);
var itemEv = new BeforeItemStrippedEvent(initialTime, false);
if (targetItem != null)
RaiseLocalEvent(targetItem.Value, ref itemEv);
var userEv = new BeforeStripEvent(itemEv.Time, itemEv.Stealth);
RaiseLocalEvent(user, ref userEv);
var ev = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
RaiseLocalEvent(target, ref ev);
return (ev.Time, ev.Stealth);
var targetEv = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
RaiseLocalEvent(targetPlayer, ref targetEv);
return (targetEv.Time, targetEv.Stealth);
}

private void OnDragDrop(EntityUid uid, StrippableComponent component, ref DragDropDraggedEvent args)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
comp-self-unremovable-clothing = This cannot be removed without outside help.
1 change: 1 addition & 0 deletions Resources/Locale/en-US/research/technologies.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ research-technology-salvage-weapons = Salvage Weapons
research-technology-draconic-munitions = Draconic Munitions
research-technology-uranium-munitions = Uranium Munitions
research-technology-explosive-technology = Explosive Technology
research-technology-special-means = Special Means
research-technology-weaponized-laser-manipulation = Weaponized Laser Manipulation
research-technology-nonlethal-ammunition = Nonlethal Ammunition
research-technology-practice-ammunition = Practice Ammunition
Expand Down
8 changes: 8 additions & 0 deletions Resources/Prototypes/Catalog/Fills/Lockers/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
- id: ClothingOuterHardsuitWarden
- id: HoloprojectorSecurity
- id: BookSpaceLaw
- id: ClothingNeckShockCollar
amount: 2
- id: RemoteSignaller
amount: 2

- type: entity
id: LockerWardenFilled
Expand All @@ -42,6 +46,10 @@
- id: DoorRemoteArmory
- id: HoloprojectorSecurity
- id: BookSpaceLaw
- id: ClothingNeckShockCollar
amount: 2
- id: RemoteSignaller
amount: 2

- type: entity
id: LockerSecurityFilled
Expand Down
23 changes: 23 additions & 0 deletions Resources/Prototypes/Entities/Clothing/Back/backpacks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,29 @@
- type: Unremoveable
deleteOnDrop: false

- type: entity
parent: ClothingBackpack
id: ClothingBackpackElectropack
name: electropack
suffix: SelfUnremovable
description: Shocks on the signal. It is used to keep a particularly dangerous criminal under control.
components:
- type: Sprite
sprite: Clothing/Back/Backpacks/electropack.rsi
state: icon
- type: Clothing
stripDelay: 10
equipDelay: 5 # to avoid accidentally falling into the trap associated with SelfUnremovableClothing
- type: SelfUnremovableClothing
- type: ShockOnTrigger
damage: 5
duration: 3
cooldown: 4
- type: TriggerOnSignal
- type: DeviceLinkSink
ports:
- Trigger

# Debug
- type: entity
parent: ClothingBackpack
Expand Down
36 changes: 36 additions & 0 deletions Resources/Prototypes/Entities/Objects/Devices/shock_collar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
- type: entity
parent: Clothing
id: ClothingNeckShockCollar
name: shock collar
suffix: SelfUnremovable
description: An electric collar that shocks on the signal.
components:
- type: Item
size: Small
- type: Sprite
sprite: Clothing/Neck/Misc/shock_collar.rsi
state: icon
- type: Clothing
sprite: Clothing/Neck/Misc/shock_collar.rsi
stripDelay: 10
equipDelay: 5 # to avoid accidentally falling into the trap associated with SelfUnremovableClothing
quickEquip: true
slots:
- neck
- type: SelfUnremovableClothing
- type: ShockOnTrigger
damage: 5
duration: 3
cooldown: 4
- type: TriggerOnSignal
- type: DeviceLinkSink
ports:
- Trigger
- type: GuideHelp
guides:
- Security
- type: StealTarget
stealGroup: ClothingNeckShockCollar
- type: Tag
tags:
- WhitelistChameleon
3 changes: 2 additions & 1 deletion Resources/Prototypes/Entities/Structures/Machines/lathe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
mask:
- MachineMask
layer:
- MachineLayer
- MachineLayer
- type: Lathe
- type: MaterialStorage
- type: Destructible
Expand Down Expand Up @@ -808,6 +808,7 @@
- WeaponLaserCannon
- WeaponLaserCarbine
- WeaponXrayCannon
- ClothingBackpackElectropack
- type: MaterialStorage
whitelist:
tags:
Expand Down
Loading

0 comments on commit 6567fa3

Please sign in to comment.