--- /dev/null
+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;
+}
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;
using Robust.Shared.Player;
using Content.Shared.Coordinates;
using Robust.Shared.Utility;
+using Robust.Shared.Timing;
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()
{
SubscribeLocalEvent<AnchorOnTriggerComponent, TriggerEvent>(OnAnchorTrigger);
SubscribeLocalEvent<SoundOnTriggerComponent, TriggerEvent>(OnSoundTrigger);
+ SubscribeLocalEvent<ShockOnTriggerComponent, TriggerEvent>(HandleShockTrigger);
SubscribeLocalEvent<RattleComponent, TriggerEvent>(HandleRattleTrigger);
}
}
}
+ 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);
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);
return;
}
- var (time, stealth) = GetStripTimeModifiers(user, target, slotDef.StripTime);
+ var (time, stealth) = GetStripTimeModifiers(user, target, item, slotDef.StripTime);
if (!stealth)
{
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);
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);
[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]
--- /dev/null
+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
+{
+
+}
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;
SubscribeLocalEvent<ClothingComponent, ClothingEquipDoAfterEvent>(OnEquipDoAfter);
SubscribeLocalEvent<ClothingComponent, ClothingUnequipDoAfterEvent>(OnUnequipDoAfter);
+
+ SubscribeLocalEvent<ClothingComponent, BeforeItemStrippedEvent>(OnItemStripped);
}
private void OnUseInHand(Entity<ClothingComponent> ent, ref UseInHandEvent args)
_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))
--- /dev/null
+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"));
+ }
+}
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)
{
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>
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)
--- /dev/null
+comp-self-unremovable-clothing = This cannot be removed without outside help.
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
- id: ClothingOuterHardsuitWarden
- id: HoloprojectorSecurity
- id: BookSpaceLaw
+ - id: ClothingNeckShockCollar
+ amount: 2
+ - id: RemoteSignaller
+ amount: 2
- type: entity
id: LockerWardenFilled
- id: DoorRemoteArmory
- id: HoloprojectorSecurity
- id: BookSpaceLaw
+ - id: ClothingNeckShockCollar
+ amount: 2
+ - id: RemoteSignaller
+ amount: 2
- type: entity
id: LockerSecurityFilled
- 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
--- /dev/null
+- 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
mask:
- MachineMask
layer:
- - MachineLayer
+ - MachineLayer
- type: Lathe
- type: MaterialStorage
- type: Destructible
- WeaponLaserCannon
- WeaponLaserCarbine
- WeaponXrayCannon
+ - ClothingBackpackElectropack
- type: MaterialStorage
whitelist:
tags:
ForensicScannerStealObjective: 1 #sec
FlippoEngravedLighterStealObjective: 0.5
ClothingHeadHatWardenStealObjective: 1
+ ClothingNeckShockCollarStealObjective: 1
ClothingOuterHardsuitVoidParamedStealObjective: 1 #med
MedicalTechFabCircuitboardStealObjective: 1
ClothingHeadsetAltMedicalStealObjective: 1
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
- 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
materials:
Steel: 250
Plastic: 100
-
+
- type: latheRecipe
id: WeaponLaserCarbine
result: WeaponLaserCarbine
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
Steel: 1000
Glass: 500
Plastic: 500
-
+
- type: latheRecipe
id: MagazineGrenadeEmpty
result: MagazineGrenadeEmpty
materials:
Steel: 150
Plastic: 50
-
+
- type: latheRecipe
id: GrenadeEMP
result: GrenadeEMP
Steel: 150
Plastic: 100
Glass: 20
-
+
- type: latheRecipe
id: GrenadeBlast
result: GrenadeBlast
Steel: 450
Plastic: 300
Gold: 150
-
+
- type: latheRecipe
id: GrenadeFlash
result: GrenadeFlash
cost: 5000
recipeUnlocks:
- MagazineShotgunBeanbag
- - BoxShellTranquilizer
- - BoxBeanbag
+ - BoxShellTranquilizer
+ - BoxBeanbag
- WeaponDisabler
- type: technology
- 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
- type: technology
id: BasicShuttleArmament
name: research-technology-basic-shuttle-armament
- icon:
+ icon:
sprite: Structures/Power/cage_recharger.rsi
state: full
discipline: Arsenal
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
--- /dev/null
+{
+ "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
+ }
+ ]
+}
--- /dev/null
+{
+ "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"
+ }
+ ]
+ }