From 6567fa36e468ca0b2091a862609ee82b77f3d277 Mon Sep 17 00:00:00 2001
From: to4no_fix <156101927+chavonadelal@users.noreply.github.com>
Date: Thu, 15 Aug 2024 17:30:39 +0300
Subject: [PATCH 1/5] 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>
---
.../Components/ShockOnTriggerComponent.cs | 37 ++++++++++++++++++
.../Explosion/EntitySystems/TriggerSystem.cs | 22 +++++++++++
Content.Server/Strip/StrippableSystem.cs | 8 ++--
.../Clothing/Components/ClothingComponent.cs | 7 ++++
.../SelfUnremovableClothingComponent.cs | 18 +++++++++
.../Clothing/EntitySystems/ClothingSystem.cs | 8 ++++
.../SelfUnremovableClothingSystem.cs | 36 +++++++++++++++++
.../EntitySystems/ToggleableClothingSystem.cs | 2 +-
.../Strip/Components/StrippableComponent.cs | 9 +++++
.../Strip/SharedStrippableSystem.cs | 16 +++++---
.../self-unremovable-clothing-component.ftl | 1 +
.../Locale/en-US/research/technologies.ftl | 1 +
.../Catalog/Fills/Lockers/security.yml | 8 ++++
.../Entities/Clothing/Back/backpacks.yml | 23 +++++++++++
.../Entities/Objects/Devices/shock_collar.yml | 36 +++++++++++++++++
.../Entities/Structures/Machines/lathe.yml | 3 +-
.../Prototypes/Objectives/objectiveGroups.yml | 1 +
.../Objectives/stealTargetGroups.yml | 7 ++++
Resources/Prototypes/Objectives/thief.yml | 11 ++++++
.../Prototypes/Recipes/Lathes/security.yml | 19 ++++++---
Resources/Prototypes/Research/arsenal.yml | 22 ++++++++---
.../electropack.rsi/equipped-BACKPACK.png | Bin 0 -> 594 bytes
.../Back/Backpacks/electropack.rsi/icon.png | Bin 0 -> 457 bytes
.../Backpacks/electropack.rsi/inhand-left.png | Bin 0 -> 432 bytes
.../electropack.rsi/inhand-right.png | Bin 0 -> 441 bytes
.../Back/Backpacks/electropack.rsi/meta.json | 33 ++++++++++++++++
.../Misc/shock_collar.rsi/equipped-NECK.png | Bin 0 -> 465 bytes
.../Neck/Misc/shock_collar.rsi/icon.png | Bin 0 -> 800 bytes
.../Neck/Misc/shock_collar.rsi/meta.json | 19 +++++++++
29 files changed, 326 insertions(+), 21 deletions(-)
create mode 100644 Content.Server/Explosion/Components/ShockOnTriggerComponent.cs
create mode 100644 Content.Shared/Clothing/Components/SelfUnremovableClothingComponent.cs
create mode 100644 Content.Shared/Clothing/EntitySystems/SelfUnremovableClothingSystem.cs
create mode 100644 Resources/Locale/en-US/clothing/components/self-unremovable-clothing-component.ftl
create mode 100644 Resources/Prototypes/Entities/Objects/Devices/shock_collar.yml
create mode 100644 Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/equipped-BACKPACK.png
create mode 100644 Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/icon.png
create mode 100644 Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/inhand-left.png
create mode 100644 Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/inhand-right.png
create mode 100644 Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/meta.json
create mode 100644 Resources/Textures/Clothing/Neck/Misc/shock_collar.rsi/equipped-NECK.png
create mode 100644 Resources/Textures/Clothing/Neck/Misc/shock_collar.rsi/icon.png
create mode 100644 Resources/Textures/Clothing/Neck/Misc/shock_collar.rsi/meta.json
diff --git a/Content.Server/Explosion/Components/ShockOnTriggerComponent.cs b/Content.Server/Explosion/Components/ShockOnTriggerComponent.cs
new file mode 100644
index 0000000000..a553cc047a
--- /dev/null
+++ b/Content.Server/Explosion/Components/ShockOnTriggerComponent.cs
@@ -0,0 +1,37 @@
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+using Content.Server.Explosion.EntitySystems;
+
+namespace Content.Server.Explosion.Components;
+
+///
+/// A component that electrocutes an entity having this component when a trigger is triggered.
+///
+[RegisterComponent, AutoGenerateComponentPause]
+[Access(typeof(TriggerSystem))]
+public sealed partial class ShockOnTriggerComponent : Component
+{
+ ///
+ /// The force of an electric shock when the trigger is triggered.
+ ///
+ [DataField]
+ public int Damage = 5;
+
+ ///
+ /// Duration of electric shock when the trigger is triggered.
+ ///
+ [DataField]
+ public TimeSpan Duration = TimeSpan.FromSeconds(2);
+
+ ///
+ /// The minimum delay between repeating triggers.
+ ///
+ [DataField]
+ public TimeSpan Cooldown = TimeSpan.FromSeconds(4);
+
+ ///
+ /// When can the trigger run again?
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoPausedField]
+ public TimeSpan NextTrigger = TimeSpan.Zero;
+}
diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs
index 92e065bf4c..1208cd1771 100644
--- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs
+++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs
@@ -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;
@@ -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
{
@@ -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()
{
@@ -104,6 +107,7 @@ public override void Initialize()
SubscribeLocalEvent(OnAnchorTrigger);
SubscribeLocalEvent(OnSoundTrigger);
+ SubscribeLocalEvent(HandleShockTrigger);
SubscribeLocalEvent(HandleRattleTrigger);
}
@@ -120,6 +124,24 @@ private void OnSoundTrigger(EntityUid uid, SoundOnTriggerComponent component, Tr
}
}
+ private void HandleShockTrigger(Entity 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);
diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs
index 194df7b3d0..6d728df9d6 100644
--- a/Content.Server/Strip/StrippableSystem.cs
+++ b/Content.Server/Strip/StrippableSystem.cs
@@ -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);
@@ -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)
{
@@ -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);
@@ -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);
diff --git a/Content.Shared/Clothing/Components/ClothingComponent.cs b/Content.Shared/Clothing/Components/ClothingComponent.cs
index 581125d4fe..4f8058dbf5 100644
--- a/Content.Shared/Clothing/Components/ClothingComponent.cs
+++ b/Content.Shared/Clothing/Components/ClothingComponent.cs
@@ -69,6 +69,13 @@ public sealed partial class ClothingComponent : Component
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan UnequipDelay = TimeSpan.Zero;
+
+ ///
+ /// Offset for the strip time for an entity with this component.
+ /// Only applied when it is being equipped or removed by another player.
+ ///
+ [DataField]
+ public TimeSpan StripDelay = TimeSpan.Zero;
}
[Serializable, NetSerializable]
diff --git a/Content.Shared/Clothing/Components/SelfUnremovableClothingComponent.cs b/Content.Shared/Clothing/Components/SelfUnremovableClothingComponent.cs
new file mode 100644
index 0000000000..1d624516ec
--- /dev/null
+++ b/Content.Shared/Clothing/Components/SelfUnremovableClothingComponent.cs
@@ -0,0 +1,18 @@
+using Content.Shared.Clothing.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Clothing.Components;
+
+///
+/// The component prohibits the player from taking off clothes on them that have this component.
+///
+///
+/// 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.
+///
+[NetworkedComponent]
+[RegisterComponent]
+[Access(typeof(SelfUnremovableClothingSystem))]
+public sealed partial class SelfUnremovableClothingComponent : Component
+{
+
+}
diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
index 082b040a32..3b26360f10 100644
--- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
+++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
@@ -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;
@@ -32,6 +33,8 @@ public override void Initialize()
SubscribeLocalEvent(OnEquipDoAfter);
SubscribeLocalEvent(OnUnequipDoAfter);
+
+ SubscribeLocalEvent(OnItemStripped);
}
private void OnUseInHand(Entity ent, ref UseInHandEvent args)
@@ -192,6 +195,11 @@ private void OnUnequipDoAfter(Entity ent, ref ClothingUnequip
_handsSystem.TryPickup(args.User, ent);
}
+ private void OnItemStripped(Entity 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))
diff --git a/Content.Shared/Clothing/EntitySystems/SelfUnremovableClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/SelfUnremovableClothingSystem.cs
new file mode 100644
index 0000000000..ab0c41c5c7
--- /dev/null
+++ b/Content.Shared/Clothing/EntitySystems/SelfUnremovableClothingSystem.cs
@@ -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;
+
+///
+/// A system for the operation of a component that prohibits the player from taking off his own clothes that have this component.
+///
+public sealed class SelfUnremovableClothingSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnUnequip);
+ SubscribeLocalEvent(OnUnequipMarkup);
+ }
+
+ private void OnUnequip(Entity selfUnremovableClothing, ref BeingUnequippedAttemptEvent args)
+ {
+ if (TryComp(selfUnremovableClothing, out var clothing) && (clothing.Slots & args.SlotFlags) == SlotFlags.NONE)
+ return;
+
+ if (args.UnEquipTarget == args.Unequipee)
+ {
+ args.Cancel();
+ }
+ }
+
+ private void OnUnequipMarkup(Entity selfUnremovableClothing, ref ExaminedEvent args)
+ {
+ args.PushMarkup(Loc.GetString("comp-self-unremovable-clothing"));
+ }
+}
diff --git a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
index c828b22481..aa3381c6bf 100644
--- a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
+++ b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
@@ -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)
{
diff --git a/Content.Shared/Strip/Components/StrippableComponent.cs b/Content.Shared/Strip/Components/StrippableComponent.cs
index 4faca4d8f2..5191b3f3f9 100644
--- a/Content.Shared/Strip/Components/StrippableComponent.cs
+++ b/Content.Shared/Strip/Components/StrippableComponent.cs
@@ -44,6 +44,15 @@ public abstract class BaseBeforeStripEvent(TimeSpan initialTime, bool stealth =
public SlotFlags TargetSlots { get; } = SlotFlags.GLOVES;
}
+ ///
+ /// Used to modify strip times. Raised directed at the item being stripped.
+ ///
+ ///
+ /// This is also used by some stripping related interactions, i.e., interactions with items that are currently equipped by another player.
+ ///
+ [ByRefEvent]
+ public sealed class BeforeItemStrippedEvent(TimeSpan initialTime, bool stealth = false) : BaseBeforeStripEvent(initialTime, stealth);
+
///
/// Used to modify strip times. Raised directed at the user.
///
diff --git a/Content.Shared/Strip/SharedStrippableSystem.cs b/Content.Shared/Strip/SharedStrippableSystem.cs
index e42f6e3aa7..935dc33540 100644
--- a/Content.Shared/Strip/SharedStrippableSystem.cs
+++ b/Content.Shared/Strip/SharedStrippableSystem.cs
@@ -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)
+ ///
+ /// Modify the strip time via events. Raised directed at the item being stripped, the player stripping someone and the player being stripped.
+ ///
+ 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)
diff --git a/Resources/Locale/en-US/clothing/components/self-unremovable-clothing-component.ftl b/Resources/Locale/en-US/clothing/components/self-unremovable-clothing-component.ftl
new file mode 100644
index 0000000000..bb7ff0206f
--- /dev/null
+++ b/Resources/Locale/en-US/clothing/components/self-unremovable-clothing-component.ftl
@@ -0,0 +1 @@
+comp-self-unremovable-clothing = This cannot be removed without outside help.
diff --git a/Resources/Locale/en-US/research/technologies.ftl b/Resources/Locale/en-US/research/technologies.ftl
index 4fbb0e1bd3..0b0970ec08 100644
--- a/Resources/Locale/en-US/research/technologies.ftl
+++ b/Resources/Locale/en-US/research/technologies.ftl
@@ -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
diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/security.yml b/Resources/Prototypes/Catalog/Fills/Lockers/security.yml
index 2d012128e6..b72fce1107 100644
--- a/Resources/Prototypes/Catalog/Fills/Lockers/security.yml
+++ b/Resources/Prototypes/Catalog/Fills/Lockers/security.yml
@@ -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
diff --git a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml
index 2d5bf42466..f94b773886 100644
--- a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml
+++ b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml
@@ -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
diff --git a/Resources/Prototypes/Entities/Objects/Devices/shock_collar.yml b/Resources/Prototypes/Entities/Objects/Devices/shock_collar.yml
new file mode 100644
index 0000000000..22f2d097fc
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Devices/shock_collar.yml
@@ -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
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 98d5440e3e..e795a5836e 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -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:
diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml
index e62aa9fdf6..c692c85dff 100644
--- a/Resources/Prototypes/Objectives/objectiveGroups.yml
+++ b/Resources/Prototypes/Objectives/objectiveGroups.yml
@@ -73,6 +73,7 @@
ForensicScannerStealObjective: 1 #sec
FlippoEngravedLighterStealObjective: 0.5
ClothingHeadHatWardenStealObjective: 1
+ ClothingNeckShockCollarStealObjective: 1
ClothingOuterHardsuitVoidParamedStealObjective: 1 #med
MedicalTechFabCircuitboardStealObjective: 1
ClothingHeadsetAltMedicalStealObjective: 1
diff --git a/Resources/Prototypes/Objectives/stealTargetGroups.yml b/Resources/Prototypes/Objectives/stealTargetGroups.yml
index 1a9b4223cb..e818442b4c 100644
--- a/Resources/Prototypes/Objectives/stealTargetGroups.yml
+++ b/Resources/Prototypes/Objectives/stealTargetGroups.yml
@@ -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
diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml
index 672f9b2ba7..092a724da2 100644
--- a/Resources/Prototypes/Objectives/thief.yml
+++ b/Resources/Prototypes/Objectives/thief.yml
@@ -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
diff --git a/Resources/Prototypes/Recipes/Lathes/security.yml b/Resources/Prototypes/Recipes/Lathes/security.yml
index 1e6b70f943..a54d5b6235 100644
--- a/Resources/Prototypes/Recipes/Lathes/security.yml
+++ b/Resources/Prototypes/Recipes/Lathes/security.yml
@@ -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
diff --git a/Resources/Prototypes/Research/arsenal.yml b/Resources/Prototypes/Research/arsenal.yml
index 1cfa1fec80..553258fdb3 100644
--- a/Resources/Prototypes/Research/arsenal.yml
+++ b/Resources/Prototypes/Research/arsenal.yml
@@ -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
diff --git a/Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/equipped-BACKPACK.png b/Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/equipped-BACKPACK.png
new file mode 100644
index 0000000000000000000000000000000000000000..3ea6cdc4b05c396229eb4d75f858c4747ac93525
GIT binary patch
literal 594
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`u%tWsIx;Y9
z?C1WI$O`0#1o(uwCM6|hWMr6`nc3Od$;im$w_w@9fIB{Zlc(}8(vzC^Y
zzrX+V>C;V3)n?8pb#TzHu8MA%oui_nvTN5aFE6i(iV8nJzo@7v6%{FzjtmPI}P7M|h_-a5foPGHl3=T%mYvlE{k_8vg>s
zU$1A(_{kSFm)-B)9*O!>OdQvu9AvXkzTr6XJAB?J_YKeMHvE(|S<++vO7g<~Z(^Sr
z)_>P|!r-*}{4J%UQInW9*>M=@IH$BHZkWPaQ~dJYF7G_%Kkf?qT&nu7&XC-o&Z^+%K(?wqq<139DE3;I@oly48C>m*L0tIg=+E
zGx~8en6KQlvvUK}ht;-BLAIVOZxlkAjwruj&)L)L8_D?2p7mX!Rjkt4`G22#&Qedv
z=L%?;P|siSpV6Rq-j072Qi8xx=1Yz6O!M_+&;qhKfEWZW1y2T1p00i_>zopr0MD@O
AUH||9
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/icon.png b/Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..b4b755e0082d7d553160ab8724fc32aefea508b8
GIT binary patch
literal 457
zcmeAS@N?(olHy`uVBq!ia0vp^2|%pC!3-pC$@QiIDaPU;cPEB*=VV?2Ic5PqA+Ba-
zX12Dr6%`e&t*x%EuF=uaE0vWOF)^8%s;Q_*&74tMT@~%%pucL@tbk?sjx=&p~iu?XPed^+;7Anq3*n*)4W*aj>`(Q4Ii@_3L3qq_1NF4lCGCf
zU}o%`(^IqN;;HE7pvKdpFF1YnNHG+wJJ4z0FzLXF-?AGrs)80ahBrToQJJd2*|W#A
zA;^*Mvha+G{mlAYM!P0+@$Q+oFUGOQaA{1VH2>5fBl~A#0=&?Rk11!ob4>u@o}F@AM8D-6RQxt
zWHSp}9Pf;8{ZBflvbgQ5=XRNk44ofy`glX(f`u%tWsIx;Y9
z?C1WI$O`1E1o(uw#>K_i+1cgf*p+6*>Rw~(KSghd%>YEF%Fw_F7+oioDvZ*SbO`=Gv0`AcR88z10tW*v3=cR
zyP~y4o?9zl`ugexM)qPaew6NJn#Q+4!HFwq!4d^lFRnU)8@!2)zU(SHJUf}E^JQGy
zcCf*pUExFNN7g0G0yoSg7+x~4Y~y8fuwrP;ZDyRn_k-s{`;PiWwN?xEC)`S404lnZ
z7an89;Mg<0>6%?f^vc`TB^m#K-sMY;@J#ddWdH>i2M~k6rQpdR%G1@)Wt~$(69D&y
Bn{xmF
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/inhand-right.png b/Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/inhand-right.png
new file mode 100644
index 0000000000000000000000000000000000000000..26901ce4c9aa65f2f2b4376e79e529f822edfb4a
GIT binary patch
literal 441
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`u%tWsIx;Y9
z?C1WI$O`1E1o(uw#>K_i+1V{(V#>+MnK*HxkEf@psakba^voHh4i5TST3SGbiRXD%
z0x6!7Aiv=M2*4n8|J*sCIA?)JWHAE+-(e7DJf6QI1t|E&)5S3)!u{>E(?ZP#9IlE9
zYft|FpTAT{_1Z~i^Vov0>y2|d^Y%=Td&DHpp;yrOA-sw4`cX}f4k_z~YUQ$SpirYexCX3?7e
zzqd{*Fx6o-uh8$$ZXe<*OH~qyvEd=1Yz6O!M_+0EHU|5QD&_;K?A$)78&qol`;+
E0L)3E*8l(j
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/meta.json b/Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/meta.json
new file mode 100644
index 0000000000..4e7738117e
--- /dev/null
+++ b/Resources/Textures/Clothing/Back/Backpacks/electropack.rsi/meta.json
@@ -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
+ }
+ ]
+}
diff --git a/Resources/Textures/Clothing/Neck/Misc/shock_collar.rsi/equipped-NECK.png b/Resources/Textures/Clothing/Neck/Misc/shock_collar.rsi/equipped-NECK.png
new file mode 100644
index 0000000000000000000000000000000000000000..ffca3249f13d35ed449938c85fc67b5c0e740977
GIT binary patch
literal 465
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%z3q4&NLn`LH
zy=9o!Y#_q+z&=!8U}f>7IE~{c9L(|#xQUx7RlYeSI6=TYA>q`4mg@^R+WKVgEHvDD
zY1Jdig*yM$T{$Fw+Ugkn+B?BCg0+=pp`3Mb4eyNykE#G-Cus!bxue(n@Yoc
z4I3WjWdGlvw}nl!G@HG)#Czi5e;wPubJ#ZUr)xZVC|E1wer&E}gqH-H+b?@{VAz1c
uqKo2e_qlSq7pxNo#spt#glC$sFM}44%>l$9a4C2)i1Kvxb6Mw<&;$VGYr@(9
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Clothing/Neck/Misc/shock_collar.rsi/icon.png b/Resources/Textures/Clothing/Neck/Misc/shock_collar.rsi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..f8e0a9cb8e4fab549dd7fe5de64098076d68774a
GIT binary patch
literal 800
zcmV+*1K<3KP)Px%z)3_wR9J=Wls{u6bN2Al~8x^Rx*@e4^4vUpo=M?7D666cDN4F2`Tzxr=ifn-y+?+cklb&
zy>}1hm}8E=fUehM()yp1ce}?@k=|V27WZZ$^#2!&TL@tWe1tG57PmsacNU-sb-gAf
zNJR#a$*iI4HJQn*;kQeW{&Uj=+@D6S0-w30lZoy5q*BY?dTHq)J4TC_dd(A5EN%fv
z!)PtU@k}iNz>d-4%e#G#Ow-mwU$v)hWL889(Dj-ejV=+w^aA@c7sAA{#(}TGWMX?a
zC4|Y2(Ml{uNPw=_WTmp}1=F;3J~rQwt2eN$F_txEI6MOgd~@{%2k!N&bCZe96;P?{
z#)^pT0e`lB3Z5DwQU@5akzyB48o_7Ad
zMIU*mbC}q`v2#F)c;>Djr&k#c&(wSmA)Q{O+x>{9t@GWreQ?R+I1&XY5jnR99{2`Q
zyS~W%#HwsLSFjc^-XkUxbiy*P$J^?Zc9GL9RCIW0O(JZ+iLn{RsaA14rN$LW=%~1
eDgXcg2mk;800000(o>TF0000
Date: Thu, 15 Aug 2024 14:31:47 +0000
Subject: [PATCH 2/5] Automatic changelog update
---
Resources/Changelog/Changelog.yml | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml
index 006ed4f5d0..403488642e 100644
--- a/Resources/Changelog/Changelog.yml
+++ b/Resources/Changelog/Changelog.yml
@@ -1,11 +1,4 @@
Entries:
-- author: ElectroJr
- changes:
- - message: Ghosts can once again open paper & other UIs
- type: Fix
- id: 6614
- time: '2024-05-24T05:03:03.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/27999
- author: nikthechampiongr
changes:
- message: Firelocks will no longer randomly pulse closing lights.
@@ -3811,3 +3804,20 @@
id: 7113
time: '2024-08-15T12:34:41.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30865
+- author: to4no_fix
+ changes:
+ - message: Added a new electropack that shocks when a trigger is triggered
+ type: Add
+ - message: Added a new shock collar that shocks when a trigger is triggered
+ type: Add
+ - message: Two shock collars and two remote signallers added to the warden's locker
+ type: Add
+ - message: Shock collar added as a new target for the thief
+ type: Add
+ - message: A new Special Means technology has been added to the Arsenal research
+ branch at the 1st research level. Its research opens up the possibility of producing
+ electropacks at security techfab. The cost of technology research is 5000
+ type: Add
+ id: 7114
+ time: '2024-08-15T14:30:39.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/30529
From 2e3365793c314702a5f49db0a1451ed4e1e6033f Mon Sep 17 00:00:00 2001
From: Mervill
Date: Thu, 15 Aug 2024 07:45:13 -0700
Subject: [PATCH 3/5] Greatly improve the usability of the Gas Analyzer.
(#30763)
* greatly improve how the gas analyzer behaves
* don't close the analyzer when the object goes out of range
* cleanup
* always switch to the device tab when a new device is analyzed
* modern api part one
* modern api part 2
* modern api part three
* file scope namespace
---
.../Atmos/UI/GasAnalyzerWindow.xaml.cs | 10 +
.../Atmos/EntitySystems/GasAnalyzerSystem.cs | 411 +++++++++---------
.../EntitySystems/RadiationCollectorSystem.cs | 2 +-
.../Atmos/Components/GasAnalyzerComponent.cs | 3 -
.../en-US/atmos/gas-analyzer-component.ftl | 2 +-
5 files changed, 213 insertions(+), 215 deletions(-)
diff --git a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs
index b54af3a587..bb24da44e1 100644
--- a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs
+++ b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs
@@ -16,6 +16,8 @@ namespace Content.Client.Atmos.UI
[GenerateTypedNameReferences]
public sealed partial class GasAnalyzerWindow : DefaultWindow
{
+ private NetEntity _currentEntity = NetEntity.Invalid;
+
public GasAnalyzerWindow()
{
RobustXamlLoader.Load(this);
@@ -55,6 +57,13 @@ public void Populate(GasAnalyzerUserMessage msg)
// Device Tab
if (msg.NodeGasMixes.Length > 1)
{
+ if (_currentEntity != msg.DeviceUid)
+ {
+ // when we get new device data switch to the device tab
+ CTabContainer.CurrentTab = 0;
+ _currentEntity = msg.DeviceUid;
+ }
+
CTabContainer.SetTabVisible(0, true);
CTabContainer.SetTabTitle(0, Loc.GetString("gas-analyzer-window-tab-title-capitalized", ("title", msg.DeviceName)));
// Set up Grid
@@ -143,6 +152,7 @@ public void Populate(GasAnalyzerUserMessage msg)
CTabContainer.SetTabVisible(0, false);
CTabContainer.CurrentTab = 1;
minSize = new Vector2(CEnvironmentMix.DesiredSize.X + 40, MinSize.Y);
+ _currentEntity = NetEntity.Invalid;
}
MinSize = minSize;
diff --git a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs
index 0f4490cd7e..81f0b96d02 100644
--- a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs
@@ -1,5 +1,4 @@
using System.Linq;
-using Content.Server.Atmos;
using Content.Server.Atmos.Components;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.Nodes;
@@ -10,274 +9,266 @@
using Content.Shared.Interaction.Events;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
-using Robust.Shared.Player;
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
-namespace Content.Server.Atmos.EntitySystems
+namespace Content.Server.Atmos.EntitySystems;
+
+[UsedImplicitly]
+public sealed class GasAnalyzerSystem : EntitySystem
{
- [UsedImplicitly]
- public sealed class GasAnalyzerSystem : EntitySystem
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly AtmosphereSystem _atmo = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
+ [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
+
+ ///
+ /// Minimum moles of a gas to be sent to the client.
+ ///
+ private const float UIMinMoles = 0.01f;
+
+ public override void Initialize()
{
- [Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly AtmosphereSystem _atmo = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
- [Dependency] private readonly TransformSystem _transform = default!;
-
- ///
- /// Minimum moles of a gas to be sent to the client.
- ///
- private const float UIMinMoles = 0.01f;
-
- public override void Initialize()
- {
- base.Initialize();
+ base.Initialize();
- SubscribeLocalEvent(OnAfterInteract);
- SubscribeLocalEvent(OnDisabledMessage);
- SubscribeLocalEvent(OnDropped);
- SubscribeLocalEvent(OnUseInHand);
- }
+ SubscribeLocalEvent(OnAfterInteract);
+ SubscribeLocalEvent(OnDisabledMessage);
+ SubscribeLocalEvent(OnDropped);
+ SubscribeLocalEvent(OnUseInHand);
+ }
- public override void Update(float frameTime)
+ public override void Update(float frameTime)
+ {
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var analyzer))
{
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var analyzer))
- {
- // Don't update every tick
- analyzer.AccumulatedFrametime += frameTime;
+ // Don't update every tick
+ analyzer.AccumulatedFrametime += frameTime;
- if (analyzer.AccumulatedFrametime < analyzer.UpdateInterval)
- continue;
+ if (analyzer.AccumulatedFrametime < analyzer.UpdateInterval)
+ continue;
- analyzer.AccumulatedFrametime -= analyzer.UpdateInterval;
+ analyzer.AccumulatedFrametime -= analyzer.UpdateInterval;
- if (!UpdateAnalyzer(uid))
- RemCompDeferred(uid);
- }
+ if (!UpdateAnalyzer(uid))
+ RemCompDeferred(uid);
}
+ }
- ///
- /// Activates the analyzer when used in the world, scanning either the target entity or the tile clicked
- ///
- private void OnAfterInteract(EntityUid uid, GasAnalyzerComponent component, AfterInteractEvent args)
+ ///
+ /// Activates the analyzer when used in the world, scanning the target entity (if it exists) and the tile the analyzer is in
+ ///
+ private void OnAfterInteract(Entity entity, ref AfterInteractEvent args)
+ {
+ var target = args.Target;
+ if (target != null && !_interactionSystem.InRangeUnobstructed((args.User, null), (target.Value, null)))
{
- if (!args.CanReach)
- {
- _popup.PopupEntity(Loc.GetString("gas-analyzer-component-player-cannot-reach-message"), args.User, args.User);
- return;
- }
- ActivateAnalyzer(uid, component, args.User, args.Target);
- args.Handled = true;
+ target = null; // if the target is out of reach, invalidate it
}
+ // always run the analyzer, regardless of weather or not there is a target
+ // since we can always show the local environment.
+ ActivateAnalyzer(entity, args.User, target);
+ args.Handled = true;
+ }
- ///
- /// Activates the analyzer with no target, so it only scans the tile the user was on when activated
- ///
- private void OnUseInHand(EntityUid uid, GasAnalyzerComponent component, UseInHandEvent args)
+ ///
+ /// Activates the analyzer with no target, so it only scans the tile the user was on when activated
+ ///
+ private void OnUseInHand(Entity entity, ref UseInHandEvent args)
+ {
+ if (!entity.Comp.Enabled)
{
- ActivateAnalyzer(uid, component, args.User);
- args.Handled = true;
+ ActivateAnalyzer(entity, args.User);
}
-
- ///
- /// Handles analyzer activation logic
- ///
- private void ActivateAnalyzer(EntityUid uid, GasAnalyzerComponent component, EntityUid user, EntityUid? target = null)
+ else
{
- if (!TryOpenUserInterface(uid, user, component))
- return;
-
- component.Target = target;
- component.User = user;
- if (target != null)
- component.LastPosition = Transform(target.Value).Coordinates;
- else
- component.LastPosition = null;
- component.Enabled = true;
- Dirty(uid, component);
- UpdateAppearance(uid, component);
- EnsureComp(uid);
- UpdateAnalyzer(uid, component);
+ DisableAnalyzer(entity, args.User);
}
+ args.Handled = true;
+ }
- ///
- /// Close the UI, turn the analyzer off, and don't update when it's dropped
- ///
- private void OnDropped(EntityUid uid, GasAnalyzerComponent component, DroppedEvent args)
- {
- if (args.User is var userId && component.Enabled)
- _popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);
- DisableAnalyzer(uid, component, args.User);
- }
+ ///
+ /// Handles analyzer activation logic
+ ///
+ private void ActivateAnalyzer(Entity entity, EntityUid user, EntityUid? target = null)
+ {
+ if (!_userInterface.TryOpenUi(entity.Owner, GasAnalyzerUiKey.Key, user))
+ return;
+
+ entity.Comp.Target = target;
+ entity.Comp.User = user;
+ entity.Comp.Enabled = true;
+ Dirty(entity);
+ _appearance.SetData(entity.Owner, GasAnalyzerVisuals.Enabled, entity.Comp.Enabled);
+ EnsureComp(entity.Owner);
+ UpdateAnalyzer(entity.Owner, entity.Comp);
+ }
- ///
- /// Closes the UI, sets the icon to off, and removes it from the update list
- ///
- private void DisableAnalyzer(EntityUid uid, GasAnalyzerComponent? component = null, EntityUid? user = null)
- {
- if (!Resolve(uid, ref component))
- return;
+ ///
+ /// Close the UI, turn the analyzer off, and don't update when it's dropped
+ ///
+ private void OnDropped(Entity entity, ref DroppedEvent args)
+ {
+ if (args.User is var userId && entity.Comp.Enabled)
+ _popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);
+ DisableAnalyzer(entity, args.User);
+ }
+
+ ///
+ /// Closes the UI, sets the icon to off, and removes it from the update list
+ ///
+ private void DisableAnalyzer(Entity entity, EntityUid? user = null)
+ {
+ _userInterface.CloseUi(entity.Owner, GasAnalyzerUiKey.Key, user);
- _userInterface.CloseUi(uid, GasAnalyzerUiKey.Key, user);
+ entity.Comp.Enabled = false;
+ Dirty(entity);
+ _appearance.SetData(entity.Owner, GasAnalyzerVisuals.Enabled, entity.Comp.Enabled);
+ RemCompDeferred(entity.Owner);
+ }
- component.Enabled = false;
- Dirty(uid, component);
- UpdateAppearance(uid, component);
- RemCompDeferred(uid);
- }
+ ///
+ /// Disables the analyzer when the user closes the UI
+ ///
+ private void OnDisabledMessage(Entity entity, ref GasAnalyzerDisableMessage message)
+ {
+ DisableAnalyzer(entity);
+ }
- ///
- /// Disables the analyzer when the user closes the UI
- ///
- private void OnDisabledMessage(EntityUid uid, GasAnalyzerComponent component, GasAnalyzerDisableMessage message)
- {
- DisableAnalyzer(uid, component);
- }
+ ///
+ /// Fetches fresh data for the analyzer. Should only be called by Update or when the user requests an update via refresh button
+ ///
+ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return false;
- private bool TryOpenUserInterface(EntityUid uid, EntityUid user, GasAnalyzerComponent? component = null)
+ // check if the user has walked away from what they scanned
+ if (component.Target.HasValue)
{
- if (!Resolve(uid, ref component, false))
- return false;
+ // Listen! Even if you don't want the Gas Analyzer to work on moving targets, you should use
+ // this code to determine if the object is still generally in range so that the check is consistent with the code
+ // in OnAfterInteract() and also consistent with interaction code in general.
+ if (!_interactionSystem.InRangeUnobstructed((component.User, null), (component.Target.Value, null)))
+ {
+ if (component.User is { } userId && component.Enabled)
+ _popup.PopupEntity(Loc.GetString("gas-analyzer-object-out-of-range"), userId, userId);
- return _userInterface.TryOpenUi(uid, GasAnalyzerUiKey.Key, user);
+ component.Target = null;
+ }
}
- ///
- /// Fetches fresh data for the analyzer. Should only be called by Update or when the user requests an update via refresh button
- ///
- private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = null)
+ var gasMixList = new List();
+
+ // Fetch the environmental atmosphere around the scanner. This must be the first entry
+ var tileMixture = _atmo.GetContainingMixture(uid, true);
+ if (tileMixture != null)
{
- if (!Resolve(uid, ref component))
- return false;
+ gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), tileMixture.Volume, tileMixture.Pressure, tileMixture.Temperature,
+ GenerateGasEntryArray(tileMixture)));
+ }
+ else
+ {
+ // No gases were found
+ gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), 0f, 0f, 0f));
+ }
- if (!TryComp(component.User, out TransformComponent? xform))
+ var deviceFlipped = false;
+ if (component.Target != null)
+ {
+ if (Deleted(component.Target))
{
- DisableAnalyzer(uid, component);
+ component.Target = null;
+ DisableAnalyzer((uid, component), component.User);
return false;
}
- // check if the user has walked away from what they scanned
- var userPos = xform.Coordinates;
- if (component.LastPosition.HasValue)
- {
- // Check if position is out of range => don't update and disable
- if (!_transform.InRange(component.LastPosition.Value, userPos, SharedInteractionSystem.InteractionRange))
- {
- if (component.User is { } userId && component.Enabled)
- _popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);
- DisableAnalyzer(uid, component, component.User);
- return false;
- }
- }
-
- var gasMixList = new List();
+ var validTarget = false;
- // Fetch the environmental atmosphere around the scanner. This must be the first entry
- var tileMixture = _atmo.GetContainingMixture(uid, true);
- if (tileMixture != null)
- {
- gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), tileMixture.Volume, tileMixture.Pressure, tileMixture.Temperature,
- GenerateGasEntryArray(tileMixture)));
- }
- else
- {
- // No gases were found
- gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), 0f, 0f, 0f));
- }
+ // gas analyzed was used on an entity, try to request gas data via event for override
+ var ev = new GasAnalyzerScanEvent();
+ RaiseLocalEvent(component.Target.Value, ev);
- var deviceFlipped = false;
- if (component.Target != null)
+ if (ev.GasMixtures != null)
{
- if (Deleted(component.Target))
- {
- component.Target = null;
- DisableAnalyzer(uid, component, component.User);
- return false;
- }
-
- // gas analyzed was used on an entity, try to request gas data via event for override
- var ev = new GasAnalyzerScanEvent();
- RaiseLocalEvent(component.Target.Value, ev);
-
- if (ev.GasMixtures != null)
+ foreach (var mixes in ev.GasMixtures)
{
- foreach (var mixes in ev.GasMixtures)
+ if (mixes.Item2 != null)
{
- if (mixes.Item2 != null)
- gasMixList.Add(new GasMixEntry(mixes.Item1, mixes.Item2.Volume, mixes.Item2.Pressure, mixes.Item2.Temperature, GenerateGasEntryArray(mixes.Item2)));
+ gasMixList.Add(new GasMixEntry(mixes.Item1, mixes.Item2.Volume, mixes.Item2.Pressure, mixes.Item2.Temperature, GenerateGasEntryArray(mixes.Item2)));
+ validTarget = true;
}
-
- deviceFlipped = ev.DeviceFlipped;
}
- else
+
+ deviceFlipped = ev.DeviceFlipped;
+ }
+ else
+ {
+ // No override, fetch manually, to handle flippable devices you must subscribe to GasAnalyzerScanEvent
+ if (TryComp(component.Target, out NodeContainerComponent? node))
{
- // No override, fetch manually, to handle flippable devices you must subscribe to GasAnalyzerScanEvent
- if (TryComp(component.Target, out NodeContainerComponent? node))
+ foreach (var pair in node.Nodes)
{
- foreach (var pair in node.Nodes)
+ if (pair.Value is PipeNode pipeNode)
{
- if (pair.Value is PipeNode pipeNode)
- {
- // check if the volume is zero for some reason so we don't divide by zero
- if (pipeNode.Air.Volume == 0f)
- continue;
- // only display the gas in the analyzed pipe element, not the whole system
- var pipeAir = pipeNode.Air.Clone();
- pipeAir.Multiply(pipeNode.Volume / pipeNode.Air.Volume);
- pipeAir.Volume = pipeNode.Volume;
- gasMixList.Add(new GasMixEntry(pair.Key, pipeAir.Volume, pipeAir.Pressure, pipeAir.Temperature, GenerateGasEntryArray(pipeAir)));
- }
+ // check if the volume is zero for some reason so we don't divide by zero
+ if (pipeNode.Air.Volume == 0f)
+ continue;
+ // only display the gas in the analyzed pipe element, not the whole system
+ var pipeAir = pipeNode.Air.Clone();
+ pipeAir.Multiply(pipeNode.Volume / pipeNode.Air.Volume);
+ pipeAir.Volume = pipeNode.Volume;
+ gasMixList.Add(new GasMixEntry(pair.Key, pipeAir.Volume, pipeAir.Pressure, pipeAir.Temperature, GenerateGasEntryArray(pipeAir)));
+ validTarget = true;
}
}
}
}
- // Don't bother sending a UI message with no content, and stop updating I guess?
- if (gasMixList.Count == 0)
- return false;
-
- _userInterface.ServerSendUiMessage(uid, GasAnalyzerUiKey.Key,
- new GasAnalyzerUserMessage(gasMixList.ToArray(),
- component.Target != null ? Name(component.Target.Value) : string.Empty,
- GetNetEntity(component.Target) ?? NetEntity.Invalid,
- deviceFlipped));
- return true;
+ // If the target doesn't actually have any gas mixes to add,
+ // invalidate it as the target
+ if (!validTarget)
+ {
+ component.Target = null;
+ }
}
- ///
- /// Sets the appearance based on the analyzers Enabled state
- ///
- private void UpdateAppearance(EntityUid uid, GasAnalyzerComponent analyzer)
- {
- _appearance.SetData(uid, GasAnalyzerVisuals.Enabled, analyzer.Enabled);
- }
+ // Don't bother sending a UI message with no content, and stop updating I guess?
+ if (gasMixList.Count == 0)
+ return false;
- ///
- /// Generates a GasEntry array for a given GasMixture
- ///
- private GasEntry[] GenerateGasEntryArray(GasMixture? mixture)
- {
- var gases = new List();
+ _userInterface.ServerSendUiMessage(uid, GasAnalyzerUiKey.Key,
+ new GasAnalyzerUserMessage(gasMixList.ToArray(),
+ component.Target != null ? Name(component.Target.Value) : string.Empty,
+ GetNetEntity(component.Target) ?? NetEntity.Invalid,
+ deviceFlipped));
+ return true;
+ }
- for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
- {
- var gas = _atmo.GetGas(i);
+ ///
+ /// Generates a GasEntry array for a given GasMixture
+ ///
+ private GasEntry[] GenerateGasEntryArray(GasMixture? mixture)
+ {
+ var gases = new List();
+
+ for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
+ {
+ var gas = _atmo.GetGas(i);
- if (mixture?[i] <= UIMinMoles)
- continue;
+ if (mixture?[i] <= UIMinMoles)
+ continue;
- if (mixture != null)
- {
- var gasName = Loc.GetString(gas.Name);
- gases.Add(new GasEntry(gasName, mixture[i], gas.Color));
- }
+ if (mixture != null)
+ {
+ var gasName = Loc.GetString(gas.Name);
+ gases.Add(new GasEntry(gasName, mixture[i], gas.Color));
}
+ }
- var gasesOrdered = gases.OrderByDescending(gas => gas.Amount);
+ var gasesOrdered = gases.OrderByDescending(gas => gas.Amount);
- return gasesOrdered.ToArray();
- }
+ return gasesOrdered.ToArray();
}
}
diff --git a/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs b/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs
index c262988c86..3fab18b1b7 100644
--- a/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs
+++ b/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs
@@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using Content.Server.Atmos;
+using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Components;
using Content.Server.Popups;
using Content.Server.Power.Components;
diff --git a/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs b/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs
index dec9516c01..c143e8cf85 100644
--- a/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs
+++ b/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs
@@ -13,9 +13,6 @@ public sealed partial class GasAnalyzerComponent : Component
[ViewVariables]
public EntityUid User;
- [ViewVariables(VVAccess.ReadWrite)]
- public EntityCoordinates? LastPosition;
-
[DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
public bool Enabled;
diff --git a/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl b/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl
index 652bb19cb5..a2cb5301b2 100644
--- a/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl
+++ b/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl
@@ -1,6 +1,6 @@
## Entity
-gas-analyzer-component-player-cannot-reach-message = You can't reach there.
+gas-analyzer-object-out-of-range = The object went out of range.
gas-analyzer-shutoff = The gas analyzer shuts off.
## UI
From 5da2b320991dcce79d386dea3f613fbc5e64795e Mon Sep 17 00:00:00 2001
From: PJBot
Date: Thu, 15 Aug 2024 14:46:20 +0000
Subject: [PATCH 4/5] Automatic changelog update
---
Resources/Changelog/Changelog.yml | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml
index 403488642e..ee4f9cd5a6 100644
--- a/Resources/Changelog/Changelog.yml
+++ b/Resources/Changelog/Changelog.yml
@@ -1,11 +1,4 @@
Entries:
-- author: nikthechampiongr
- changes:
- - message: Firelocks will no longer randomly pulse closing lights.
- type: Fix
- id: 6615
- time: '2024-05-24T14:44:42.0000000+00:00'
- url: https://github.com/space-wizards/space-station-14/pull/28227
- author: ElectroJr
changes:
- message: Fixed modular grenade visuals getting stuck in an incorrect state.
@@ -3821,3 +3814,19 @@
id: 7114
time: '2024-08-15T14:30:39.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30529
+- author: Mervill
+ changes:
+ - message: The Gas Analyzer won't spuriously shut down for seemly no reason.
+ type: Tweak
+ - message: The Gas Analyzer will always switch to the device tab when a new object
+ is scanned.
+ type: Tweak
+ - message: The Gas Analyzer's interaction range is now equal to the standard interaction
+ range
+ type: Fix
+ - message: Clicking the Gas Analyzer when it's in your hand has proper enable/disable
+ behavior.
+ type: Fix
+ id: 7115
+ time: '2024-08-15T14:45:13.0000000+00:00'
+ url: https://github.com/space-wizards/space-station-14/pull/30763
From 84f9dd0f0b8ef46f07503277827669a10fc63819 Mon Sep 17 00:00:00 2001
From: IgorAnt028 <118114530+IgorAnt028@users.noreply.github.com>
Date: Thu, 15 Aug 2024 18:00:56 +0300
Subject: [PATCH 5/5] Fix false and true in player-panel.ftl (#31043)
Fix false and true in player-panel
Add a new True string
---
.../Administration/UI/PlayerPanel/PlayerPanel.xaml.cs | 2 +-
Resources/Locale/en-US/administration/ui/player-panel.ftl | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
index f20e47b6a1..53cc8faa10 100644
--- a/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
+++ b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
@@ -70,7 +70,7 @@ public void SetWhitelisted(bool? whitelisted)
else
{
Whitelisted.Text = Loc.GetString("player-panel-whitelisted");
- WhitelistToggle.Text = whitelisted.Value.ToString();
+ WhitelistToggle.Text = whitelisted.Value ? Loc.GetString("player-panel-true") : Loc.GetString("player-panel-false");
WhitelistToggle.Visible = true;
_isWhitelisted = whitelisted.Value;
}
diff --git a/Resources/Locale/en-US/administration/ui/player-panel.ftl b/Resources/Locale/en-US/administration/ui/player-panel.ftl
index ed63dd6d10..cfb014948d 100644
--- a/Resources/Locale/en-US/administration/ui/player-panel.ftl
+++ b/Resources/Locale/en-US/administration/ui/player-panel.ftl
@@ -20,3 +20,4 @@ player-panel-logs = Logs
player-panel-delete = Delete
player-panel-rejuvenate = Rejuvenate
player-panel-false = False
+player-panel-true = True