Adding shock collar and electropack (#30529)

* 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 <98561806+EmoGarbage404@users.noreply.github.com>
This commit is contained in:
to4no_fix
2024-08-15 17:30:39 +03:00
committed by GitHub
parent 02eed07e21
commit 6567fa36e4
29 changed files with 326 additions and 21 deletions

View File

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

View File

@@ -3,6 +3,7 @@ using Content.Server.Body.Systems;
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;
@@ -33,6 +34,7 @@ using Robust.Shared.Random;
using Robust.Shared.Player;
using Content.Shared.Coordinates;
using Robust.Shared.Utility;
using Robust.Shared.Timing;
namespace Content.Server.Explosion.EntitySystems
{
@@ -75,6 +77,7 @@ namespace Content.Server.Explosion.EntitySystems
[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()
{
@@ -104,6 +107,7 @@ namespace Content.Server.Explosion.EntitySystems
SubscribeLocalEvent<AnchorOnTriggerComponent, TriggerEvent>(OnAnchorTrigger);
SubscribeLocalEvent<SoundOnTriggerComponent, TriggerEvent>(OnSoundTrigger);
SubscribeLocalEvent<ShockOnTriggerComponent, TriggerEvent>(HandleShockTrigger);
SubscribeLocalEvent<RattleComponent, TriggerEvent>(HandleRattleTrigger);
}
@@ -120,6 +124,24 @@ namespace Content.Server.Explosion.EntitySystems
}
}
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);

View File

@@ -218,7 +218,7 @@ namespace Content.Server.Strip
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);
@@ -306,7 +306,7 @@ namespace Content.Server.Strip
return;
}
var (time, stealth) = GetStripTimeModifiers(user, target, slotDef.StripTime);
var (time, stealth) = GetStripTimeModifiers(user, target, item, slotDef.StripTime);
if (!stealth)
{
@@ -411,7 +411,7 @@ namespace Content.Server.Strip
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);
@@ -510,7 +510,7 @@ namespace Content.Server.Strip
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);

View File

@@ -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]

View File

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

View File

@@ -6,6 +6,7 @@ using Content.Shared.Interaction.Events;
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;
@@ -32,6 +33,8 @@ public abstract class ClothingSystem : EntitySystem
SubscribeLocalEvent<ClothingComponent, ClothingEquipDoAfterEvent>(OnEquipDoAfter);
SubscribeLocalEvent<ClothingComponent, ClothingUnequipDoAfterEvent>(OnUnequipDoAfter);
SubscribeLocalEvent<ClothingComponent, BeforeItemStrippedEvent>(OnItemStripped);
}
private void OnUseInHand(Entity<ClothingComponent> ent, ref UseInHandEvent args)
@@ -192,6 +195,11 @@ public abstract class ClothingSystem : EntitySystem
_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))

View File

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

View File

@@ -95,7 +95,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
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)
{

View File

@@ -44,6 +44,15 @@ namespace Content.Shared.Strip.Components
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>

View File

@@ -28,13 +28,19 @@ public abstract class SharedStrippableSystem : EntitySystem
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)

View File

@@ -0,0 +1 @@
comp-self-unremovable-clothing = This cannot be removed without outside help.

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@
mask:
- MachineMask
layer:
- MachineLayer
- MachineLayer
- type: Lathe
- type: MaterialStorage
- type: Destructible
@@ -808,6 +808,7 @@
- WeaponLaserCannon
- WeaponLaserCarbine
- WeaponXrayCannon
- ClothingBackpackElectropack
- type: MaterialStorage
whitelist:
tags:

View File

@@ -73,6 +73,7 @@
ForensicScannerStealObjective: 1 #sec
FlippoEngravedLighterStealObjective: 0.5
ClothingHeadHatWardenStealObjective: 1
ClothingNeckShockCollarStealObjective: 1
ClothingOuterHardsuitVoidParamedStealObjective: 1 #med
MedicalTechFabCircuitboardStealObjective: 1
ClothingHeadsetAltMedicalStealObjective: 1

View File

@@ -272,6 +272,13 @@
sprite: Clothing/Neck/Medals/clownmedal.rsi
state: icon
- type: stealTargetGroup
id: ClothingNeckShockCollar
name: shock collar
sprite:
sprite: Clothing/Neck/Misc/shock_collar.rsi
state: icon
#Thief structures
- type: stealTargetGroup

View File

@@ -316,6 +316,17 @@
- type: Objective
difficulty: 1
- type: entity
parent: BaseThiefStealObjective
id: ClothingNeckShockCollarStealObjective
components:
- type: NotJobRequirement
job: Warden
- type: StealCondition
stealGroup: ClothingNeckShockCollar
- type: Objective
difficulty: 1
# Structures
- type: entity

View File

@@ -38,7 +38,7 @@
materials:
Steel: 250
Plastic: 100
- type: latheRecipe
id: WeaponLaserCarbine
result: WeaponLaserCarbine
@@ -89,6 +89,15 @@
Plastic: 250
Gold: 100
- type: latheRecipe
id: ClothingBackpackElectropack
result: ClothingBackpackElectropack
completetime: 4
materials:
Steel: 500
Plastic: 250
Cloth: 500
- type: latheRecipe
id: ForensicPad
result: ForensicPad
@@ -655,7 +664,7 @@
Steel: 1000
Glass: 500
Plastic: 500
- type: latheRecipe
id: MagazineGrenadeEmpty
result: MagazineGrenadeEmpty
@@ -663,7 +672,7 @@
materials:
Steel: 150
Plastic: 50
- type: latheRecipe
id: GrenadeEMP
result: GrenadeEMP
@@ -672,7 +681,7 @@
Steel: 150
Plastic: 100
Glass: 20
- type: latheRecipe
id: GrenadeBlast
result: GrenadeBlast
@@ -681,7 +690,7 @@
Steel: 450
Plastic: 300
Gold: 150
- type: latheRecipe
id: GrenadeFlash
result: GrenadeFlash

View File

@@ -58,8 +58,8 @@
cost: 5000
recipeUnlocks:
- MagazineShotgunBeanbag
- BoxShellTranquilizer
- BoxBeanbag
- BoxShellTranquilizer
- BoxBeanbag
- WeaponDisabler
- type: technology
@@ -115,6 +115,18 @@
- ExplosivePayload
- ChemicalPayload
- type: technology
id: SpecialMeans
name: research-technology-special-means
icon:
sprite: Clothing/Back/Backpacks/electropack.rsi
state: icon
discipline: Arsenal
tier: 1
cost: 5000
recipeUnlocks:
- ClothingBackpackElectropack
# Tier 2
- type: technology
@@ -144,7 +156,7 @@
- type: technology
id: BasicShuttleArmament
name: research-technology-basic-shuttle-armament
icon:
icon:
sprite: Structures/Power/cage_recharger.rsi
state: full
discipline: Arsenal
@@ -189,11 +201,11 @@
cost: 15000
recipeUnlocks:
- WeaponLaserSvalinn
- type: technology
id: AdvancedShuttleWeapon
name: research-technology-advanced-shuttle-weapon
icon:
icon:
sprite: Objects/Weapons/Guns/Ammunition/Magazine/Grenade/grenade_cartridge.rsi
state: icon
discipline: Arsenal

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

View File

@@ -0,0 +1,33 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/2d26ce62c273d025bed77a0e6c4bdc770b789bb0",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon",
"delays": [
[
0.1,
0.1,
0.1
]
]
},
{
"name": "equipped-BACKPACK",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

View File

@@ -0,0 +1,19 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Drawn by EmoGarbage404 (github) for Space Station 14",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "equipped-NECK",
"directions": 4
},
{
"name": "icon"
}
]
}