]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
item toggling giga rework + full ninja refactor (#28039)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Thu, 11 Jul 2024 05:55:56 +0000 (05:55 +0000)
committerGitHub <noreply@github.com>
Thu, 11 Jul 2024 05:55:56 +0000 (15:55 +1000)
* item toggle refactoring and some new systems

* add ToggleClothing component/system

* unhardcode magboots gravity logic

* make magboots and speedboots use ItemToggle and stuff

* remove now useless clothing components

* update client/server magboots systems

* add note to use ItemToggledEvent in ToggleActionEvent doc

* refactor PowerCellDraw to use ItemToggle for ui open/close control

* add TryUseCharges, refactor charges system

* update magboot trigger code

* make borg use ItemToggle, network SelectedModule instead of now removed Activated

* add AccessToggle for borg

* the giga ninja refactor

* update ninja yml

* update ItemToggle usage for some stuff

* fix activatableui requires power

* random fixing

* yaml fixing

* nuke ItemToggleDisarmMalus

* make defib use ItemToggle

* make things that use power not turn on if missing use charge

* pro

* fix sound prediction

* bruh

* proximity detector use ItemToggle

* oop

* big idiot syndrome

* fix ninja spawn rule and make it generic

* fix ninja spawn rule yml

* move loading profiles into AntagLoadProfileRule

* more ninja refactor

* ninja yml fixes

* the dreaded copy paste ops

* remove useless NinjaRuleComponent and ue AntagSelection for greeting

* fix invisibility

* move IsCompleted to SharedObjectivesSystem

* ability fixes

* oop fix powercell instantly draining itself

* sentient speedboots gaming

* make reflect use ItemToggle

* fix other test

* loadprofilerule moved into its own pr

* remove conflict with dragon refactor

* remove all GenericAntag code from ninja

* )

* probably

* remove old enabled

* great language bravo vince

* GREAT LANGUAGE

* who made this language

* because it stinks

* reparent blood-red magboots to magboots probbbly works

* most of the review stuff

* hasGrav doesnt mean what i thought it did

* make health analyzer use itemtoggle, not fail test

* fix mag/speed boots being wacky

* UNTROLL

* add ItemToggle to the random health analyzers

* a

* remove unused obsolete borg func

* untrolling

* :trollface:

* fix test

* fix

* g

* untroll

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
121 files changed:
Content.Client/Items/Systems/ItemToggleSystem.cs [deleted file]
Content.Client/Ninja/Systems/ItemCreatorSystem.cs [new file with mode: 0644]
Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
Content.Client/Ninja/Systems/NinjaSuitSystem.cs
Content.Client/Ninja/Systems/NinjaSystem.cs
Content.Client/Ninja/Systems/SpiderChargeSystem.cs [new file with mode: 0644]
Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
Content.Server/Charges/Components/AutoRechargeComponent.cs
Content.Server/Charges/Systems/ChargesSystem.cs
Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs [deleted file]
Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs [deleted file]
Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs [deleted file]
Content.Server/Item/ItemToggle/ItemToggleSystem.cs [deleted file]
Content.Server/Medical/Components/HealthAnalyzerComponent.cs
Content.Server/Medical/DefibrillatorSystem.cs
Content.Server/Medical/HealthAnalyzerSystem.cs
Content.Server/Ninja/Events/BatteryChangedEvent.cs
Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
Content.Server/Ninja/Systems/ItemCreatorSystem.cs [new file with mode: 0644]
Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
Content.Server/Ninja/Systems/NinjaSuitSystem.cs
Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
Content.Server/Ninja/Systems/SpiderChargeSystem.cs
Content.Server/Ninja/Systems/StunProviderSystem.cs
Content.Server/Objectives/Systems/CodeConditionSystem.cs
Content.Server/PowerCell/PowerCellSystem.Draw.cs
Content.Server/PowerCell/PowerCellSystem.cs
Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
Content.Server/Silicons/Borgs/BorgSystem.cs
Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs [deleted file]
Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs [new file with mode: 0644]
Content.Server/StationEvents/Events/SpaceSpawnRule.cs [moved from Content.Server/StationEvents/Events/NinjaSpawnRule.cs with 53% similarity]
Content.Server/Stunnable/Systems/StunbatonSystem.cs
Content.Server/Weapons/Misc/TetherGunSystem.cs
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
Content.Shared/Access/Components/AccessToggleComponent.cs [new file with mode: 0644]
Content.Shared/Access/Systems/AccessToggleSystem.cs [new file with mode: 0644]
Content.Shared/Beeper/Components/BeeperComponent.cs
Content.Shared/Beeper/Systems/BeeperSystem.cs
Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
Content.Shared/Charges/Systems/SharedChargesSystem.cs
Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
Content.Shared/Clothing/Components/StealthClothingComponent.cs [deleted file]
Content.Shared/Clothing/Components/ToggleClothingComponent.cs [new file with mode: 0644]
Content.Shared/Clothing/Components/ToggleClothingSpeedComponent.cs [deleted file]
Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs [deleted file]
Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs [new file with mode: 0644]
Content.Shared/Clothing/MagbootsComponent.cs
Content.Shared/Clothing/MagbootsSystem.cs [new file with mode: 0644]
Content.Shared/Clothing/SharedMagbootsSystem.cs [deleted file]
Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs [new file with mode: 0644]
Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs [new file with mode: 0644]
Content.Shared/Item/ItemToggle/Components/ItemToggleActiveSoundComponent.cs
Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs [new file with mode: 0644]
Content.Shared/Item/ItemToggle/ItemToggleSystem.cs [moved from Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs with 54% similarity]
Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs [new file with mode: 0644]
Content.Shared/Medical/DefibrillatorComponent.cs
Content.Shared/Ninja/Components/BatteryDrainerComponent.cs
Content.Shared/Ninja/Components/BombingTargetComponent.cs
Content.Shared/Ninja/Components/DashAbilityComponent.cs
Content.Shared/Ninja/Components/EmagProviderComponent.cs
Content.Shared/Ninja/Components/EnergyKatanaComponent.cs
Content.Shared/Ninja/Components/ItemCreatorComponent.cs [new file with mode: 0644]
Content.Shared/Ninja/Components/NinjaGlovesComponent.cs
Content.Shared/Ninja/Components/NinjaSuitComponent.cs
Content.Shared/Ninja/Components/SpaceNinjaComponent.cs
Content.Shared/Ninja/Components/SpiderChargeComponent.cs
Content.Shared/Ninja/Components/StunProviderComponent.cs
Content.Shared/Ninja/Systems/DashAbilitySystem.cs
Content.Shared/Ninja/Systems/EmagProviderSystem.cs
Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs
Content.Shared/Ninja/Systems/ItemCreatorSystem.cs [new file with mode: 0644]
Content.Shared/Ninja/Systems/SharedBatteryDrainerSystem.cs
Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs
Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs
Content.Shared/Ninja/Systems/SharedSpaceNinjaSystem.cs
Content.Shared/Ninja/Systems/SharedSpiderChargeSystem.cs [new file with mode: 0644]
Content.Shared/Ninja/Systems/SharedStunProviderSystem.cs
Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs
Content.Shared/Pinpointer/SharedProximityBeeper.cs [deleted file]
Content.Shared/PowerCell/PowerCellDrawComponent.cs
Content.Shared/PowerCell/SharedPowerCellSystem.cs
Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs
Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs
Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
Content.Shared/Toggleable/ToggleActionEvent.cs
Content.Shared/Tools/Systems/SharedToolSystem.cs
Content.Shared/UserInterface/ActivatableUISystem.Power.cs
Content.Shared/Weapons/Reflect/ReflectComponent.cs
Content.Shared/Weapons/Reflect/ReflectSystem.cs
Resources/Prototypes/Actions/ninja.yml
Resources/Prototypes/Entities/Clothing/Hands/gloves.yml
Resources/Prototypes/Entities/Clothing/Head/misc.yml
Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
Resources/Prototypes/Entities/Clothing/base_clothing.yml
Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml
Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
Resources/Prototypes/Entities/Mobs/Player/human.yml
Resources/Prototypes/Entities/Mobs/Species/base.yml
Resources/Prototypes/Entities/Objects/Devices/base_handheld.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Devices/pda.yml
Resources/Prototypes/Entities/Objects/Devices/station_map.yml
Resources/Prototypes/Entities/Objects/Shields/shields.yml
Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml
Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml
Resources/Prototypes/Entities/Objects/Tools/welders.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml
Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
Resources/Prototypes/GameRules/events.yml
Resources/Prototypes/GameRules/midround.yml

diff --git a/Content.Client/Items/Systems/ItemToggleSystem.cs b/Content.Client/Items/Systems/ItemToggleSystem.cs
deleted file mode 100644 (file)
index 46d6f1b..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.Item.ItemToggle;
-
-namespace Content.Shared.Item;
-
-/// <inheritdoc/>
-public sealed class ItemToggleSystem : SharedItemToggleSystem
-{
-
-}
diff --git a/Content.Client/Ninja/Systems/ItemCreatorSystem.cs b/Content.Client/Ninja/Systems/ItemCreatorSystem.cs
new file mode 100644 (file)
index 0000000..9ab62cc
--- /dev/null
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class ItemCreatorSystem : SharedItemCreatorSystem;
index 7758c3d7e2bc0fcf218dcc4f93a163cca5662f4f..5b07b1588fd4e6ab2fd1d22c999e59056748065f 100644 (file)
@@ -2,9 +2,4 @@ using Content.Shared.Ninja.Systems;
 
 namespace Content.Client.Ninja.Systems;
 
-/// <summary>
-/// Does nothing special, only exists to provide a client implementation.
-/// </summary>
-public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
-{
-}
+public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem;
index fde1801b37d1481495c806ab67b83be8e5dc0dfa..852ea8af46effb0dd0cf77fb476c98c3296f9323 100644 (file)
@@ -1,24 +1,5 @@
-using Content.Shared.Clothing.EntitySystems;
-using Content.Shared.Ninja.Components;
 using Content.Shared.Ninja.Systems;
 
 namespace Content.Client.Ninja.Systems;
 
-/// <summary>
-/// Disables cloak prediction since client has no knowledge of battery power.
-/// Cloak will still be enabled after server tells it.
-/// </summary>
-public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
-{
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<NinjaSuitComponent, AttemptStealthEvent>(OnAttemptStealth);
-    }
-
-    private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
-    {
-        args.Cancel();
-    }
-}
+public sealed class NinjaSuitSystem : SharedNinjaSuitSystem;
index aa2fa2047f13607a78185585a8e94b7c40f98818..958dc6a5d9a36c6ea7ebce47ebbd995144dc6971 100644 (file)
@@ -2,11 +2,4 @@ using Content.Shared.Ninja.Systems;
 
 namespace Content.Client.Ninja.Systems;
 
-/// <summary>
-/// Currently does nothing special clientside.
-/// All functionality is in shared and server.
-/// Only exists to prevent crashing.
-/// </summary>
-public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
-{
-}
+public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem;
diff --git a/Content.Client/Ninja/Systems/SpiderChargeSystem.cs b/Content.Client/Ninja/Systems/SpiderChargeSystem.cs
new file mode 100644 (file)
index 0000000..b107fd3
--- /dev/null
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem;
index a09126a7f7c90acb93e5f395d089fac293d08272..33e4da3fa342d8883dfd4296509500a7356c6c27 100644 (file)
@@ -171,7 +171,7 @@ public abstract partial class InteractionTest
             // turn on welders
             if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated)
             {
-                Assert.That(ItemToggleSys.TryActivate(item, playerEnt, itemToggle: itemToggle));
+                Assert.That(ItemToggleSys.TryActivate((item, itemToggle), user: playerEnt));
             }
         });
 
index 457d3e31920af29adea869e6e611c0c588ee306b..b3d684e01a059511dc2d3546b3714992e698aad2 100644 (file)
@@ -104,7 +104,7 @@ public abstract partial class InteractionTest
     protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
     protected SharedDoAfterSystem DoAfterSys = default!;
     protected ToolSystem ToolSys = default!;
-    protected SharedItemToggleSystem ItemToggleSys = default!;
+    protected ItemToggleSystem ItemToggleSys = default!;
     protected InteractionTestSystem STestSystem = default!;
     protected SharedTransformSystem Transform = default!;
     protected SharedMapSystem MapSystem = default!;
@@ -165,7 +165,7 @@ public abstract partial class InteractionTest
         HandSys = SEntMan.System<HandsSystem>();
         InteractSys = SEntMan.System<SharedInteractionSystem>();
         ToolSys = SEntMan.System<ToolSystem>();
-        ItemToggleSys = SEntMan.System<SharedItemToggleSystem>();
+        ItemToggleSys = SEntMan.System<ItemToggleSystem>();
         DoAfterSys = SEntMan.System<SharedDoAfterSystem>();
         Transform = SEntMan.System<SharedTransformSystem>();
         MapSystem = SEntMan.System<SharedMapSystem>();
index 9dcf555ea93123e985d15274d456352e43854b13..165b181dcbc42f8cc287731704e1efd721816bef 100644 (file)
@@ -7,6 +7,7 @@ namespace Content.Server.Charges.Components;
 /// Something with limited charges that can be recharged automatically.
 /// Requires LimitedChargesComponent to function.
 /// </summary>
+// TODO: no reason this cant be predicted and server system deleted
 [RegisterComponent, AutoGenerateComponentPause]
 [Access(typeof(ChargesSystem))]
 public sealed partial class AutoRechargeComponent : Component
index 03e192e680e85926f84248e8efa2861f0e2902ad..974928ee4bbc95c902aac4b515dfe41d49d99d52 100644 (file)
@@ -37,15 +37,17 @@ public sealed class ChargesSystem : SharedChargesSystem
         args.PushMarkup(Loc.GetString("limited-charges-recharging", ("seconds", timeRemaining)));
     }
 
-    public override void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+    public override void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
     {
-        if (!Resolve(uid, ref comp, false))
+        if (!Query.Resolve(uid, ref comp, false))
             return;
 
         var startRecharge = comp.Charges == comp.MaxCharges;
-        base.UseCharge(uid, comp);
-        // start the recharge time after first use at full charge
-        if (startRecharge && TryComp<AutoRechargeComponent>(uid, out var recharge))
+        base.AddCharges(uid, change, comp);
+
+        // if a charge was just used from full, start the recharge timer
+        // TODO: probably make this an event instead of having le server system that just does this
+        if (change < 0 && startRecharge && TryComp<AutoRechargeComponent>(uid, out var recharge))
             recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration;
     }
 }
diff --git a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs
deleted file mode 100644 (file)
index fa352eb..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-using Content.Server.Ninja.Systems;
-using Content.Shared.Communications;
-using Content.Shared.Random;
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.GameTicking.Rules.Components;
-
-/// <summary>
-/// Stores some configuration used by the ninja system.
-/// Objectives and roundend summary are handled by <see cref="GenericAntagRuleComponent"/>.
-/// </summary>
-[RegisterComponent, Access(typeof(SpaceNinjaSystem))]
-public sealed partial class NinjaRuleComponent : Component
-{
-    /// <summary>
-    /// List of threats that can be called in. Copied onto <see cref="CommsHackerComponent"/> when gloves are enabled.
-    /// </summary>
-    [DataField(required: true)]
-    public ProtoId<WeightedRandomPrototype> Threats = string.Empty;
-
-    /// <summary>
-    /// Sound played when making the player a ninja via antag control or ghost role
-    /// </summary>
-    [DataField]
-    public SoundSpecifier? GreetingSound = new SoundPathSpecifier("/Audio/Misc/ninja_greeting.ogg");
-}
diff --git a/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs b/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs
deleted file mode 100644 (file)
index 30fa84e..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace Content.Server.Item;
-
-/// <summary>
-/// Handles whether this item applies a disarm malus when active.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ItemToggleDisarmMalusComponent : Component
-{
-    /// <summary>
-    ///     Item has this modifier to the chance to disarm when activated.
-    ///     If null, the value will be inferred from the current malus just before the malus is first deactivated.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadOnly), DataField]
-    public float? ActivatedDisarmMalus = null;
-
-    /// <summary>
-    ///     Item has this modifier to the chance to disarm when deactivated. If none is mentioned, it uses the item's default disarm modifier.
-    ///     If null, the value will be inferred from the current malus just before the malus is first activated.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadOnly), DataField]
-    public float? DeactivatedDisarmMalus = null;
-}
diff --git a/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs b/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs
deleted file mode 100644 (file)
index 227491b..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Item;
-
-/// <summary>
-/// Handles whether this item is sharp when toggled on.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ItemToggleSharpComponent : Component
-{
-}
diff --git a/Content.Server/Item/ItemToggle/ItemToggleSystem.cs b/Content.Server/Item/ItemToggle/ItemToggleSystem.cs
deleted file mode 100644 (file)
index f98415e..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-using Content.Server.CombatMode.Disarm;
-using Content.Server.Kitchen.Components;
-using Content.Shared.Item.ItemToggle;
-using Content.Shared.Item.ItemToggle.Components;
-
-namespace Content.Server.Item;
-
-public sealed class ItemToggleSystem : SharedItemToggleSystem
-{
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ItemToggleSharpComponent, ItemToggledEvent>(ToggleSharp);
-        SubscribeLocalEvent<ItemToggleDisarmMalusComponent, ItemToggledEvent>(ToggleMalus);
-    }
-
-    private void ToggleSharp(Entity<ItemToggleSharpComponent> ent, ref ItemToggledEvent args)
-    {
-        // TODO generalize this into a  "ToggleComponentComponent", though probably with a better name
-        if (args.Activated)
-            EnsureComp<SharpComponent>(ent);
-        else
-            RemCompDeferred<SharpComponent>(ent);
-    }
-
-    private void ToggleMalus(Entity<ItemToggleDisarmMalusComponent> ent, ref ItemToggledEvent args)
-    {
-        if (!TryComp<DisarmMalusComponent>(ent, out var malus))
-            return;
-
-        if (args.Activated)
-        {
-            ent.Comp.DeactivatedDisarmMalus ??= malus.Malus;
-            if (ent.Comp.ActivatedDisarmMalus is {} activatedMalus)
-                malus.Malus = activatedMalus;
-            return;
-        }
-
-        ent.Comp.ActivatedDisarmMalus ??= malus.Malus;
-        if (ent.Comp.DeactivatedDisarmMalus is {} deactivatedMalus)
-            malus.Malus = deactivatedMalus;
-    }
-}
index 6380b71c8a0bad8649c5eaf61784e20d1f5d7e21..de40e98e182216ddce2cf497c7f2f627fc85d249 100644 (file)
@@ -6,6 +6,9 @@ namespace Content.Server.Medical.Components;
 /// <summary>
 /// After scanning, retrieves the target Uid to use with its related UI.
 /// </summary>
+/// <remarks>
+/// Requires <c>ItemToggleComponent</c>.
+/// </remarks>
 [RegisterComponent, AutoGenerateComponentPause]
 [Access(typeof(HealthAnalyzerSystem), typeof(CryoPodSystem))]
 public sealed partial class HealthAnalyzerComponent : Component
index 4373532f0186c7fe213176316409a50a0680f8c3..1896f51eddcb63839822a1fa0de63c2d82b0ac97 100644 (file)
@@ -12,6 +12,7 @@ using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
 using Content.Shared.Interaction.Components;
 using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle;
 using Content.Shared.Medical;
 using Content.Shared.Mind;
 using Content.Shared.Mobs;
@@ -37,6 +38,7 @@ public sealed class DefibrillatorSystem : EntitySystem
     [Dependency] private readonly DoAfterSystem _doAfter = default!;
     [Dependency] private readonly ElectrocutionSystem _electrocution = default!;
     [Dependency] private readonly EuiManager _euiManager = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
     [Dependency] private readonly RottingSystem _rotting = default!;
     [Dependency] private readonly MobStateSystem _mobState = default!;
     [Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
@@ -50,30 +52,10 @@ public sealed class DefibrillatorSystem : EntitySystem
     /// <inheritdoc/>
     public override void Initialize()
     {
-        SubscribeLocalEvent<DefibrillatorComponent, UseInHandEvent>(OnUseInHand);
-        SubscribeLocalEvent<DefibrillatorComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
         SubscribeLocalEvent<DefibrillatorComponent, AfterInteractEvent>(OnAfterInteract);
         SubscribeLocalEvent<DefibrillatorComponent, DefibrillatorZapDoAfterEvent>(OnDoAfter);
     }
 
-    private void OnUseInHand(EntityUid uid, DefibrillatorComponent component, UseInHandEvent args)
-    {
-        if (args.Handled || !TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
-            return;
-
-        if (!TryToggle(uid, component, args.User))
-            return;
-
-        args.Handled = true;
-        _useDelay.TryResetDelay((uid, useDelay));
-    }
-
-    private void OnPowerCellSlotEmpty(EntityUid uid, DefibrillatorComponent component, ref PowerCellSlotEmptyEvent args)
-    {
-        if (!TerminatingOrDeleted(uid))
-            TryDisable(uid, component);
-    }
-
     private void OnAfterInteract(EntityUid uid, DefibrillatorComponent component, AfterInteractEvent args)
     {
         if (args.Handled || args.Target is not { } target)
@@ -96,54 +78,12 @@ public sealed class DefibrillatorSystem : EntitySystem
         Zap(uid, target, args.User, component);
     }
 
-    public bool TryToggle(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
-    {
-        if (!Resolve(uid, ref component))
-            return false;
-
-        return component.Enabled
-            ? TryDisable(uid, component)
-            : TryEnable(uid, component, user);
-    }
-
-    public bool TryEnable(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
-    {
-        if (!Resolve(uid, ref component))
-            return false;
-
-        if (component.Enabled)
-            return false;
-
-        if (!_powerCell.HasActivatableCharge(uid))
-            return false;
-
-        component.Enabled = true;
-        _appearance.SetData(uid, ToggleVisuals.Toggled, true);
-        _audio.PlayPvs(component.PowerOnSound, uid);
-        return true;
-    }
-
-    public bool TryDisable(EntityUid uid, DefibrillatorComponent? component = null)
-    {
-        if (!Resolve(uid, ref component))
-            return false;
-
-        if (!component.Enabled)
-            return false;
-
-        component.Enabled = false;
-        _appearance.SetData(uid, ToggleVisuals.Toggled, false);
-
-        _audio.PlayPvs(component.PowerOffSound, uid);
-        return true;
-    }
-
     public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null)
     {
         if (!Resolve(uid, ref component))
             return false;
 
-        if (!component.Enabled)
+        if (!_toggle.IsActivated(uid))
         {
             if (user != null)
                 _popup.PopupEntity(Loc.GetString("defibrillator-not-on"), uid, user.Value);
@@ -257,7 +197,7 @@ public sealed class DefibrillatorSystem : EntitySystem
 
         // if we don't have enough power left for another shot, turn it off
         if (!_powerCell.HasActivatableCharge(uid))
-            TryDisable(uid, component);
+            _toggle.TryDeactivate(uid);
 
         // TODO clean up this clown show above
         var ev = new TargetDefibrillatedEvent(user, (uid, component));
index c72cd2ddf6f69428eeb6697f87775556375472c7..1d6e564a32d90d9257d7a47b917aad870360a404 100644 (file)
@@ -8,6 +8,8 @@ using Content.Shared.DoAfter;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.MedicalScanner;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Popups;
@@ -26,6 +28,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
     [Dependency] private readonly PowerCellSystem _cell = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
     [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
     [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
     [Dependency] private readonly TransformSystem _transformSystem = default!;
@@ -36,7 +39,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
         SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
         SubscribeLocalEvent<HealthAnalyzerComponent, HealthAnalyzerDoAfterEvent>(OnDoAfter);
         SubscribeLocalEvent<HealthAnalyzerComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
-        SubscribeLocalEvent<HealthAnalyzerComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
+        SubscribeLocalEvent<HealthAnalyzerComponent, ItemToggledEvent>(OnToggled);
         SubscribeLocalEvent<HealthAnalyzerComponent, DroppedEvent>(OnDropped);
     }
 
@@ -111,16 +114,16 @@ public sealed class HealthAnalyzerSystem : EntitySystem
     private void OnInsertedIntoContainer(Entity<HealthAnalyzerComponent> uid, ref EntGotInsertedIntoContainerMessage args)
     {
         if (uid.Comp.ScannedEntity is { } patient)
-            StopAnalyzingEntity(uid, patient);
+            _toggle.TryDeactivate(uid.Owner);
     }
 
     /// <summary>
-    /// Disable continuous updates once battery is dead
+    /// Disable continuous updates once turned off
     /// </summary>
-    private void OnPowerCellSlotEmpty(Entity<HealthAnalyzerComponent> uid, ref PowerCellSlotEmptyEvent args)
+    private void OnToggled(Entity<HealthAnalyzerComponent> ent, ref ItemToggledEvent args)
     {
-        if (uid.Comp.ScannedEntity is { } patient)
-            StopAnalyzingEntity(uid, patient);
+        if (!args.Activated && ent.Comp.ScannedEntity is { } patient)
+            StopAnalyzingEntity(ent, patient);
     }
 
     /// <summary>
@@ -129,7 +132,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
     private void OnDropped(Entity<HealthAnalyzerComponent> uid, ref DroppedEvent args)
     {
         if (uid.Comp.ScannedEntity is { } patient)
-            StopAnalyzingEntity(uid, patient);
+            _toggle.TryDeactivate(uid.Owner);
     }
 
     private void OpenUserInterface(EntityUid user, EntityUid analyzer)
@@ -150,7 +153,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
         //Link the health analyzer to the scanned entity
         healthAnalyzer.Comp.ScannedEntity = target;
 
-        _cell.SetPowerCellDrawEnabled(healthAnalyzer, true);
+        _toggle.TryActivate(healthAnalyzer.Owner);
 
         UpdateScannedUser(healthAnalyzer, target, true);
     }
@@ -165,7 +168,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
         //Unlink the analyzer
         healthAnalyzer.Comp.ScannedEntity = null;
 
-        _cell.SetPowerCellDrawEnabled(target, false);
+        _toggle.TryDeactivate(healthAnalyzer.Owner);
 
         UpdateScannedUser(healthAnalyzer, target, false);
     }
index 45bfedfee7659c53036acaf07e97f9acd8ffa5a0..1848e8818687a24889882941547c08d32fc32bdc 100644 (file)
@@ -1,7 +1,7 @@
 namespace Content.Server.Ninja.Events;
 
 /// <summary>
-/// Raised on the ninja when the suit has its powercell changed.
+/// Raised on the ninja and suit when the suit has its powercell changed.
 /// </summary>
 [ByRefEvent]
 public record struct NinjaBatteryChangedEvent(EntityUid Battery, EntityUid BatteryHolder);
index 59dec556fa0b55ce2eeb0dc3aa95c33dcba81e66..4baf0913cecd12a01e5ec27d49755e2b67ce798a 100644 (file)
@@ -33,16 +33,17 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
     /// Start do after for draining a power source.
     /// Can't predict PNBC existing so only done on server.
     /// </summary>
-    private void OnBeforeInteractHand(EntityUid uid, BatteryDrainerComponent comp, BeforeInteractHandEvent args)
+    private void OnBeforeInteractHand(Entity<BatteryDrainerComponent> ent, ref BeforeInteractHandEvent args)
     {
+        var (uid, comp) = ent;
         var target = args.Target;
-        if (args.Handled || comp.BatteryUid == null || !HasComp<PowerNetworkBatteryComponent>(target))
+        if (args.Handled || comp.BatteryUid is not {} battery || !HasComp<PowerNetworkBatteryComponent>(target))
             return;
 
         // handles even if battery is full so you can actually see the poup
         args.Handled = true;
 
-        if (_battery.IsFull(comp.BatteryUid.Value))
+        if (_battery.IsFull(battery))
         {
             _popup.PopupEntity(Loc.GetString("battery-drainer-full"), uid, uid, PopupType.Medium);
             return;
@@ -59,23 +60,24 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
         _doAfter.TryStartDoAfter(doAfterArgs);
     }
 
-    private void OnBatteryChanged(EntityUid uid, BatteryDrainerComponent comp, ref NinjaBatteryChangedEvent args)
+    private void OnBatteryChanged(Entity<BatteryDrainerComponent> ent, ref NinjaBatteryChangedEvent args)
     {
-        SetBattery(uid, args.Battery, comp);
+        SetBattery((ent, ent.Comp), args.Battery);
     }
 
     /// <inheritdoc/>
-    protected override void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent<DrainDoAfterEvent> args)
+    protected override void OnDoAfterAttempt(Entity<BatteryDrainerComponent> ent, ref DoAfterAttemptEvent<DrainDoAfterEvent> args)
     {
-        base.OnDoAfterAttempt(uid, comp, args);
+        base.OnDoAfterAttempt(ent, ref args);
 
-        if (comp.BatteryUid == null || _battery.IsFull(comp.BatteryUid.Value))
+        if (ent.Comp.BatteryUid is not {} battery || _battery.IsFull(battery))
             args.Cancel();
     }
 
     /// <inheritdoc/>
-    protected override bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
+    protected override bool TryDrainPower(Entity<BatteryDrainerComponent> ent, EntityUid target)
     {
+        var (uid, comp) = ent;
         if (comp.BatteryUid == null || !TryComp<BatteryComponent>(comp.BatteryUid.Value, out var battery))
             return false;
 
@@ -98,6 +100,7 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
 
         var output = input * comp.DrainEfficiency;
         _battery.SetCharge(comp.BatteryUid.Value, battery.CurrentCharge + output, battery);
+        // TODO: create effect message or something
         Spawn("EffectSparks", Transform(target).Coordinates);
         _audio.PlayPvs(comp.SparkSound, target);
         _popup.PopupEntity(Loc.GetString("battery-drainer-success", ("battery", target)), uid, uid);
diff --git a/Content.Server/Ninja/Systems/ItemCreatorSystem.cs b/Content.Server/Ninja/Systems/ItemCreatorSystem.cs
new file mode 100644 (file)
index 0000000..d7a7be9
--- /dev/null
@@ -0,0 +1,57 @@
+using Content.Server.Ninja.Events;
+using Content.Server.Power.EntitySystems;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Ninja.Components;
+using Content.Shared.Ninja.Systems;
+using Content.Shared.Popups;
+
+namespace Content.Server.Ninja.Systems;
+
+public sealed class ItemCreatorSystem : SharedItemCreatorSystem
+{
+    [Dependency] private readonly BatterySystem _battery = default!;
+    [Dependency] private readonly SharedHandsSystem _hands = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ItemCreatorComponent, CreateItemEvent>(OnCreateItem);
+        SubscribeLocalEvent<ItemCreatorComponent, NinjaBatteryChangedEvent>(OnBatteryChanged);
+    }
+
+    private void OnCreateItem(Entity<ItemCreatorComponent> ent, ref CreateItemEvent args)
+    {
+        var (uid, comp) = ent;
+        if (comp.Battery is not {} battery)
+            return;
+
+        args.Handled = true;
+
+        var user = args.Performer;
+        if (!_battery.TryUseCharge(battery, comp.Charge))
+        {
+            _popup.PopupEntity(Loc.GetString(comp.NoPowerPopup), user, user);
+            return;
+        }
+
+        var ev = new CreateItemAttemptEvent(user);
+        RaiseLocalEvent(uid, ref ev);
+        if (ev.Cancelled)
+            return;
+
+        // try to put throwing star in hand, otherwise it goes on the ground
+        var star = Spawn(comp.SpawnedPrototype, Transform(user).Coordinates);
+        _hands.TryPickupAnyHand(user, star);
+    }
+
+    private void OnBatteryChanged(Entity<ItemCreatorComponent> ent, ref NinjaBatteryChangedEvent args)
+    {
+        if (ent.Comp.Battery == args.Battery)
+            return;
+
+        ent.Comp.Battery = args.Battery;
+        Dirty(ent, ent.Comp);
+    }
+}
index ac76ae6b77119a71892bd8e60f688479d9f14e8a..3aaf7c5d58e790bd4d7a4315257d6a86d2f53d43 100644 (file)
@@ -1,13 +1,8 @@
-using Content.Server.Communications;
-using Content.Server.Mind;
 using Content.Server.Ninja.Events;
-using Content.Server.Objectives.Systems;
-using Content.Shared.Communications;
-using Content.Shared.CriminalRecords.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Systems;
 using Content.Shared.Ninja.Components;
 using Content.Shared.Ninja.Systems;
-using Content.Shared.Research.Components;
-using Content.Shared.Toggleable;
 
 namespace Content.Server.Ninja.Systems;
 
@@ -16,89 +11,44 @@ namespace Content.Server.Ninja.Systems;
 /// </summary>
 public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
 {
-    [Dependency] private readonly EmagProviderSystem _emagProvider = default!;
-    [Dependency] private readonly CodeConditionSystem _codeCondition = default!;
-    [Dependency] private readonly CommsHackerSystem _commsHacker = default!;
-    [Dependency] private readonly SharedStunProviderSystem _stunProvider = default!;
+    [Dependency] private readonly SharedMindSystem _mind = default!;
+    [Dependency] private readonly SharedObjectivesSystem _objectives = default!;
     [Dependency] private readonly SpaceNinjaSystem _ninja = default!;
 
-    public override void Initialize()
+    protected override void EnableGloves(Entity<NinjaGlovesComponent> ent, Entity<SpaceNinjaComponent> user)
     {
-        base.Initialize();
+        base.EnableGloves(ent, user);
 
-        SubscribeLocalEvent<NinjaGlovesComponent, ToggleActionEvent>(OnToggleAction);
-    }
-
-    /// <summary>
-    /// Toggle gloves, if the user is a ninja wearing a ninja suit.
-    /// </summary>
-    private void OnToggleAction(EntityUid uid, NinjaGlovesComponent comp, ToggleActionEvent args)
-    {
-        if (args.Handled)
+        // can't use abilities if suit is not equipped, this is checked elsewhere but just making sure to satisfy nullability
+        if (user.Comp.Suit is not {} suit)
             return;
 
-        args.Handled = true;
-
-        var user = args.Performer;
-        // need to wear suit to enable gloves
-        if (!TryComp<SpaceNinjaComponent>(user, out var ninja)
-            || ninja.Suit == null
-            || !HasComp<NinjaSuitComponent>(ninja.Suit.Value))
-        {
-            Popup.PopupEntity(Loc.GetString("ninja-gloves-not-wearing-suit"), user, user);
+        if (!_mind.TryGetMind(user, out var mindId, out var mind))
             return;
-        }
-
-        // show its state to the user
-        var enabling = comp.User == null;
-        Appearance.SetData(uid, ToggleVisuals.Toggled, enabling);
-        var message = Loc.GetString(enabling ? "ninja-gloves-on" : "ninja-gloves-off");
-        Popup.PopupEntity(message, user, user);
 
-        if (enabling)
+        foreach (var ability in ent.Comp.Abilities)
         {
-            EnableGloves(uid, comp, user, ninja);
+            // non-objective abilities are added in shared already
+            if (ability.Objective is not {} objId)
+                continue;
+
+            // prevent doing an objective multiple times by toggling gloves after doing them
+            // if it's not tied to an objective always add them anyway
+            if (!_mind.TryFindObjective((mindId, mind), objId, out var obj))
+            {
+                Log.Error($"Ninja glove ability of {ent} referenced missing objective {ability.Objective} of {_mind.MindOwnerLoggingString(mind)}");
+                continue;
+            }
+
+            if (!_objectives.IsCompleted(obj.Value, (mindId, mind)))
+                EntityManager.AddComponents(user, ability.Components);
         }
-        else
-        {
-            DisableGloves(uid, comp);
-        }
-    }
 
-    private void EnableGloves(EntityUid uid, NinjaGlovesComponent comp, EntityUid user, SpaceNinjaComponent ninja)
-    {
-        // can't use abilities if suit is not equipped, this is checked elsewhere but just making sure to satisfy nullability
-        if (ninja.Suit == null)
-            return;
-
-        comp.User = user;
-        Dirty(uid, comp);
-        _ninja.AssignGloves(user, uid, ninja);
-
-        var drainer = EnsureComp<BatteryDrainerComponent>(user);
-        var stun = EnsureComp<StunProviderComponent>(user);
-        _stunProvider.SetNoPowerPopup(user, "ninja-no-power", stun);
+        // let abilities that use battery power work
         if (_ninja.GetNinjaBattery(user, out var battery, out var _))
         {
-            var ev = new NinjaBatteryChangedEvent(battery.Value, ninja.Suit.Value);
+            var ev = new NinjaBatteryChangedEvent(battery.Value, suit);
             RaiseLocalEvent(user, ref ev);
         }
-
-        var emag = EnsureComp<EmagProviderComponent>(user);
-        _emagProvider.SetWhitelist(user, comp.DoorjackWhitelist, emag);
-
-        EnsureComp<ResearchStealerComponent>(user);
-        // prevent calling in multiple threats by toggling gloves after
-        if (!_codeCondition.IsCompleted(user, ninja.TerrorObjective))
-        {
-            var hacker = EnsureComp<CommsHackerComponent>(user);
-            var rule = _ninja.NinjaRule(user);
-            if (rule != null)
-                _commsHacker.SetThreats(user, rule.Threats, hacker);
-        }
-        if (!_codeCondition.IsCompleted(user, ninja.MassArrestObjective))
-        {
-            EnsureComp<CriminalRecordsHackerComponent>(user);
-        }
     }
 }
index 04095b549c6bf54abd861ac5de44d27a7b744d7d..63054eaad50ecc020c2de809c5b94640deb38931 100644 (file)
@@ -2,7 +2,6 @@ using Content.Server.Emp;
 using Content.Server.Ninja.Events;
 using Content.Server.Power.Components;
 using Content.Server.PowerCell;
-using Content.Shared.Clothing.EntitySystems;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Ninja.Components;
 using Content.Shared.Ninja.Systems;
@@ -29,15 +28,13 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
 
         SubscribeLocalEvent<NinjaSuitComponent, ContainerIsInsertingAttemptEvent>(OnSuitInsertAttempt);
         SubscribeLocalEvent<NinjaSuitComponent, EmpAttemptEvent>(OnEmpAttempt);
-        SubscribeLocalEvent<NinjaSuitComponent, AttemptStealthEvent>(OnAttemptStealth);
-        SubscribeLocalEvent<NinjaSuitComponent, CreateThrowingStarEvent>(OnCreateThrowingStar);
         SubscribeLocalEvent<NinjaSuitComponent, RecallKatanaEvent>(OnRecallKatana);
         SubscribeLocalEvent<NinjaSuitComponent, NinjaEmpEvent>(OnEmp);
     }
 
-    protected override void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
+    protected override void NinjaEquipped(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
     {
-        base.NinjaEquippedSuit(uid, comp, user, ninja);
+        base.NinjaEquipped(ent, user);
 
         _ninja.SetSuitPowerAlert(user);
     }
@@ -57,16 +54,15 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
 
         // can only upgrade power cell, not swap to recharge instantly otherwise ninja could just swap batteries with flashlights in maints for easy power
         if (!TryComp<BatteryComponent>(args.EntityUid, out var inserting) || inserting.MaxCharge <= battery.MaxCharge)
-        {
             args.Cancel();
-        }
 
         // tell ninja abilities that use battery to update it so they don't use charge from the old one
         var user = Transform(uid).ParentUid;
-        if (!HasComp<SpaceNinjaComponent>(user))
+        if (!_ninja.IsNinja(user))
             return;
 
         var ev = new NinjaBatteryChangedEvent(args.EntityUid, uid);
+        RaiseLocalEvent(uid, ref ev);
         RaiseLocalEvent(user, ref ev);
     }
 
@@ -77,64 +73,22 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
         args.Cancel();
     }
 
-    protected override void UserUnequippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user)
+    protected override void UserUnequippedSuit(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
     {
-        base.UserUnequippedSuit(uid, comp, user);
+        base.UserUnequippedSuit(ent, user);
 
         // remove power indicator
         _ninja.SetSuitPowerAlert(user);
     }
 
-    private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
+    private void OnRecallKatana(Entity<NinjaSuitComponent> ent, ref RecallKatanaEvent args)
     {
-        var user = args.User;
-        // need 1 second of charge to turn on stealth
-        var chargeNeeded = SuitWattage(uid, comp);
-        // being attacked while cloaked gives no power message since it overloads the power supply or something
-        if (!_ninja.GetNinjaBattery(user, out _, out var battery) || battery.CurrentCharge < chargeNeeded)
-        {
-            Popup.PopupEntity(Loc.GetString("ninja-no-power"), user, user);
-            args.Cancel();
-            return;
-        }
-
-        if (comp.DisableCooldown > GameTiming.CurTime)
-        {
-            Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
-            args.Cancel();
-            return;
-        }
-
-        StealthClothing.SetEnabled(uid, user, true);
-    }
-
-    private void OnCreateThrowingStar(EntityUid uid, NinjaSuitComponent comp, CreateThrowingStarEvent args)
-    {
-        args.Handled = true;
+        var (uid, comp) = ent;
         var user = args.Performer;
-        if (!_ninja.TryUseCharge(user, comp.ThrowingStarCharge))
-        {
-            Popup.PopupEntity(Loc.GetString("ninja-no-power"), user, user);
+        if (!_ninja.NinjaQuery.TryComp(user, out var ninja) || ninja.Katana == null)
             return;
-        }
-
-        if (comp.DisableCooldown > GameTiming.CurTime)
-        {
-            Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
-            return;
-        }
-
-        // try to put throwing star in hand, otherwise it goes on the ground
-        var star = Spawn(comp.ThrowingStarPrototype, Transform(user).Coordinates);
-        _hands.TryPickupAnyHand(user, star);
-    }
 
-    private void OnRecallKatana(EntityUid uid, NinjaSuitComponent comp, RecallKatanaEvent args)
-    {
         args.Handled = true;
-        var user = args.Performer;
-        if (!TryComp<SpaceNinjaComponent>(user, out var ninja) || ninja.Katana == null)
-            return;
 
         var katana = ninja.Katana.Value;
         var coords = _transform.GetWorldPosition(katana);
@@ -146,11 +100,8 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
             return;
         }
 
-        if (comp.DisableCooldown > GameTiming.CurTime)
-        {
-            Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
+        if (CheckDisabled(ent, user))
             return;
-        }
 
         // TODO: teleporting into belt slot
         var message = _hands.TryPickupAnyHand(user, katana)
@@ -159,9 +110,11 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
         Popup.PopupEntity(Loc.GetString(message), user, user);
     }
 
-    private void OnEmp(EntityUid uid, NinjaSuitComponent comp, NinjaEmpEvent args)
+    private void OnEmp(Entity<NinjaSuitComponent> ent, ref NinjaEmpEvent args)
     {
+        var (uid, comp) = ent;
         args.Handled = true;
+
         var user = args.Performer;
         if (!_ninja.TryUseCharge(user, comp.EmpCharge))
         {
@@ -169,13 +122,9 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
             return;
         }
 
-        if (comp.DisableCooldown > GameTiming.CurTime)
-        {
-            Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
+        if (CheckDisabled(ent, user))
             return;
-        }
 
-        // I don't think this affects the suit battery, but if it ever does in the future add a blacklist for it
         var coords = _transform.GetMapCoordinates(user);
         _emp.EmpPulse(coords, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration);
     }
index 0c1e88653fa4a421998c8793186375e12bfddd34..28ab6332276f828cb231383e46c5a2175983c0fa 100644 (file)
@@ -2,7 +2,6 @@ using Content.Server.Communications;
 using Content.Server.Chat.Managers;
 using Content.Server.CriminalRecords.Systems;
 using Content.Server.GameTicking.Rules.Components;
-using Content.Server.GenericAntag;
 using Content.Server.Objectives.Components;
 using Content.Server.Objectives.Systems;
 using Content.Server.Power.Components;
@@ -11,7 +10,6 @@ using Content.Server.PowerCell;
 using Content.Server.Research.Systems;
 using Content.Server.Roles;
 using Content.Shared.Alert;
-using Content.Shared.Clothing.EntitySystems;
 using Content.Shared.Doors.Components;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Mind;
@@ -26,11 +24,6 @@ using Robust.Shared.Audio.Systems;
 
 namespace Content.Server.Ninja.Systems;
 
-// TODO: when syndiborgs are a thing have a borg converter with 6 second doafter
-// engi -> saboteur
-// medi -> idk reskin it
-// other -> assault
-
 /// <summary>
 /// Main ninja system that handles ninja setup, provides helper methods for the rest of the code to use.
 /// </summary>
@@ -44,13 +37,11 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
     [Dependency] private readonly RoleSystem _role = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedMindSystem _mind = default!;
-    [Dependency] private readonly StealthClothingSystem _stealthClothing = default!;
 
     public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<SpaceNinjaComponent, GenericAntagCreatedEvent>(OnNinjaCreated);
         SubscribeLocalEvent<SpaceNinjaComponent, EmaggedSomethingEvent>(OnDoorjack);
         SubscribeLocalEvent<SpaceNinjaComponent, ResearchStolenEvent>(OnResearchStolen);
         SubscribeLocalEvent<SpaceNinjaComponent, ThreatCalledInEvent>(OnThreatCalledIn);
@@ -62,7 +53,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
         var query = EntityQueryEnumerator<SpaceNinjaComponent>();
         while (query.MoveNext(out var uid, out var ninja))
         {
-            UpdateNinja(uid, ninja, frameTime);
+            SetSuitPowerAlert((uid, ninja));
         }
     }
 
@@ -80,31 +71,13 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
         return newCount - oldCount;
     }
 
-    /// <summary>
-    /// Returns a ninja's gamerule config data.
-    /// If the gamerule was not started then it will be started automatically.
-    /// </summary>
-    public NinjaRuleComponent? NinjaRule(EntityUid uid, GenericAntagComponent? comp = null)
-    {
-        if (!Resolve(uid, ref comp))
-            return null;
-
-        // mind not added yet so no rule
-        if (comp.RuleEntity == null)
-            return null;
-
-        return CompOrNull<NinjaRuleComponent>(comp.RuleEntity);
-    }
-
     // TODO: can probably copy paste borg code here
     /// <summary>
     /// Update the alert for the ninja's suit power indicator.
     /// </summary>
-    public void SetSuitPowerAlert(EntityUid uid, SpaceNinjaComponent? comp = null)
+    public void SetSuitPowerAlert(Entity<SpaceNinjaComponent> ent)
     {
-        if (!Resolve(uid, ref comp, false))
-            return;
-
+        var (uid, comp) = ent;
         if (comp.Deleted || comp.Suit == null)
         {
             _alerts.ClearAlert(uid, comp.SuitPowerAlert);
@@ -145,53 +118,6 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
         return GetNinjaBattery(user, out var uid, out var battery) && _battery.TryUseCharge(uid.Value, charge, battery);
     }
 
-    /// <summary>
-    /// Set up everything for ninja to work and send the greeting message/sound.
-    /// Objectives are added by <see cref="GenericAntagSystem"/>.
-    /// </summary>
-    private void OnNinjaCreated(EntityUid uid, SpaceNinjaComponent comp, ref GenericAntagCreatedEvent args)
-    {
-        var mindId = args.MindId;
-        var mind = args.Mind;
-
-        if (mind.Session == null)
-            return;
-
-        var config = NinjaRule(uid);
-        if (config == null)
-            return;
-
-        var role = new NinjaRoleComponent
-        {
-            PrototypeId = "SpaceNinja"
-        };
-        _role.MindAddRole(mindId, role, mind);
-        _role.MindPlaySound(mindId, config.GreetingSound, mind);
-
-        var session = mind.Session;
-        _audio.PlayGlobal(config.GreetingSound, Filter.Empty().AddPlayer(session), false, AudioParams.Default);
-        _chatMan.DispatchServerMessage(session, Loc.GetString("ninja-role-greeting"));
-    }
-
-    // TODO: PowerCellDraw, modify when cloak enabled
-    /// <summary>
-    /// Handle constant power drains from passive usage and cloak.
-    /// </summary>
-    private void UpdateNinja(EntityUid uid, SpaceNinjaComponent ninja, float frameTime)
-    {
-        if (ninja.Suit == null)
-            return;
-
-        float wattage = Suit.SuitWattage(ninja.Suit.Value);
-
-        SetSuitPowerAlert(uid, ninja);
-        if (!TryUseCharge(uid, wattage * frameTime))
-        {
-            // ran out of power, uncloak ninja
-            _stealthClothing.SetEnabled(ninja.Suit.Value, uid, false);
-        }
-    }
-
     /// <summary>
     /// Increment greentext when emagging a door.
     /// </summary>
index 64c958d6f1aba24d11e738c7bb7ae69cffd1ee87..c262651f27ae780a258f1c8ee6ff58f134102ec0 100644 (file)
@@ -7,6 +7,7 @@ using Content.Server.Roles;
 using Content.Server.Sticky.Events;
 using Content.Shared.Interaction;
 using Content.Shared.Ninja.Components;
+using Content.Shared.Ninja.Systems;
 using Robust.Shared.GameObjects;
 
 namespace Content.Server.Ninja.Systems;
@@ -14,7 +15,7 @@ namespace Content.Server.Ninja.Systems;
 /// <summary>
 /// Prevents planting a spider charge outside of its location and handles greentext.
 /// </summary>
-public sealed class SpiderChargeSystem : EntitySystem
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
 {
     [Dependency] private readonly MindSystem _mind = default!;
     [Dependency] private readonly PopupSystem _popup = default!;
index 1768606ad20f2312c3979a02c86426c825afac32..822486cff52f75ea381a2ef8e0de078aea198310 100644 (file)
@@ -6,10 +6,11 @@ using Content.Shared.Ninja.Components;
 using Content.Shared.Ninja.Systems;
 using Content.Shared.Popups;
 using Content.Shared.Stunnable;
-using Robust.Shared.Prototypes;
+using Content.Shared.Timing;
+using Content.Shared.Whitelist;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Timing;
-using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
 
 namespace Content.Server.Ninja.Systems;
 
@@ -20,12 +21,12 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
 {
     [Dependency] private readonly BatterySystem _battery = default!;
     [Dependency] private readonly DamageableSystem _damageable = default!;
-    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly SharedStunSystem _stun = default!;
-    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+    [Dependency] private readonly UseDelaySystem _useDelay = default!;
 
     public override void Initialize()
     {
@@ -38,16 +39,18 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
     /// <summary>
     /// Stun clicked mobs on the whitelist, if there is enough power.
     /// </summary>
-    private void OnBeforeInteractHand(EntityUid uid, StunProviderComponent comp, BeforeInteractHandEvent args)
+    private void OnBeforeInteractHand(Entity<StunProviderComponent> ent, ref BeforeInteractHandEvent args)
     {
         // TODO: generic check
+        var (uid, comp) = ent;
         if (args.Handled || comp.BatteryUid == null || !_gloves.AbilityCheck(uid, args, out var target))
             return;
 
-        if (target == uid || _whitelistSystem.IsWhitelistFail(comp.Whitelist, target))
+        if (target == uid || _whitelist.IsWhitelistFail(comp.Whitelist, target))
             return;
 
-        if (_timing.CurTime < comp.NextStun)
+        var useDelay = EnsureComp<UseDelayComponent>(uid);
+        if (_useDelay.IsDelayed((uid, useDelay), id: comp.DelayId))
             return;
 
         // take charge from battery
@@ -63,13 +66,14 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
         _stun.TryParalyze(target, comp.StunTime, refresh: false);
 
         // short cooldown to prevent instant stunlocking
-        comp.NextStun = _timing.CurTime + comp.Cooldown;
+        _useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId);
+        _useDelay.TryResetDelay((uid, useDelay), id: comp.DelayId);
 
         args.Handled = true;
     }
 
-    private void OnBatteryChanged(EntityUid uid, StunProviderComponent comp, ref NinjaBatteryChangedEvent args)
+    private void OnBatteryChanged(Entity<StunProviderComponent> ent, ref NinjaBatteryChangedEvent args)
     {
-        SetBattery(uid, args.Battery, comp);
+        SetBattery((ent, ent.Comp), args.Battery);
     }
 }
index 7ba312f4bb9ec8a6fbad6a3442cfedd4c66e1501..fbc58dafe821325fb2f707f927cda0dcf3a1935f 100644 (file)
@@ -35,20 +35,6 @@ public sealed class CodeConditionSystem : EntitySystem
         return ent.Comp.Completed;
     }
 
-    /// <summary>
-    /// Returns true if a mob's objective with a certain prototype is completed.
-    /// </summary>
-    public bool IsCompleted(Entity<MindContainerComponent?> mob, string prototype)
-    {
-        if (_mind.GetMind(mob, mob.Comp) is not {} mindId)
-            return false;
-
-        if (!_mind.TryFindObjective(mindId, prototype, out var obj))
-            return false;
-
-        return IsCompleted(obj.Value);
-    }
-
     /// <summary>
     /// Sets an objective's completed field.
     /// </summary>
index 4155a4f6becf388e41a33727a692c45e5be2e433..9ebd677f47337b0dbeb78e608b533bf4a4bc3379 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.Power.Components;
+using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.PowerCell;
 using Content.Shared.PowerCell.Components;
 
@@ -10,22 +11,20 @@ public sealed partial class PowerCellSystem
      * Handles PowerCellDraw
      */
 
-    private static readonly TimeSpan Delay = TimeSpan.FromSeconds(1);
-
     public override void Update(float frameTime)
     {
         base.Update(frameTime);
-        var query = EntityQueryEnumerator<PowerCellDrawComponent, PowerCellSlotComponent>();
+        var query = EntityQueryEnumerator<PowerCellDrawComponent, PowerCellSlotComponent, ItemToggleComponent>();
 
-        while (query.MoveNext(out var uid, out var comp, out var slot))
+        while (query.MoveNext(out var uid, out var comp, out var slot, out var toggle))
         {
-            if (!comp.Drawing)
+            if (!comp.Enabled || !toggle.Activated)
                 continue;
 
             if (Timing.CurTime < comp.NextUpdateTime)
                 continue;
 
-            comp.NextUpdateTime += Delay;
+            comp.NextUpdateTime += comp.Delay;
 
             if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot))
                 continue;
@@ -33,7 +32,8 @@ public sealed partial class PowerCellSystem
             if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery))
                 continue;
 
-            comp.Drawing = false;
+            Toggle.TryDeactivate((uid, toggle));
+
             var ev = new PowerCellSlotEmptyEvent();
             RaiseLocalEvent(uid, ref ev);
         }
@@ -42,26 +42,9 @@ public sealed partial class PowerCellSystem
     private void OnDrawChargeChanged(EntityUid uid, PowerCellDrawComponent component, ref ChargeChangedEvent args)
     {
         // Update the bools for client prediction.
-        bool canDraw;
-        bool canUse;
-
-        if (component.UseRate > 0f)
-        {
-            canUse = args.Charge > component.UseRate;
-        }
-        else
-        {
-            canUse = true;
-        }
+        var canUse = component.UseRate <= 0f || args.Charge > component.UseRate;
 
-        if (component.DrawRate > 0f)
-        {
-            canDraw = args.Charge > 0f;
-        }
-        else
-        {
-            canDraw = true;
-        }
+        var canDraw = component.DrawRate <= 0f || args.Charge > 0f;
 
         if (canUse != component.CanUse || canDraw != component.CanDraw)
         {
@@ -76,6 +59,9 @@ public sealed partial class PowerCellSystem
         var canDraw = !args.Ejected && HasCharge(uid, float.MinValue);
         var canUse = !args.Ejected && HasActivatableCharge(uid, component);
 
+        if (!canDraw)
+            Toggle.TryDeactivate(uid);
+
         if (canUse != component.CanUse || canDraw != component.CanDraw)
         {
             component.CanDraw = canDraw;
index f45a01b2e1b07f271bb5232b31beed733a9243ef..0d2d9012bc79f99b2b09c2012ce7dc44ad5d0f1a 100644 (file)
@@ -39,8 +39,8 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
         SubscribeLocalEvent<PowerCellDrawComponent, ChargeChangedEvent>(OnDrawChargeChanged);
         SubscribeLocalEvent<PowerCellDrawComponent, PowerCellChangedEvent>(OnDrawCellChanged);
 
-        // funny
         SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnCellSlotExamined);
+        // funny
         SubscribeLocalEvent<PowerCellSlotComponent, BeingMicrowavedEvent>(OnSlotMicrowaved);
     }
 
index 746d75f0d856d7ef379a7d4b3e751eab0d42622a..f289752b7cff34e74721b2bb0af23bb4ee708e63 100644 (file)
@@ -29,7 +29,7 @@ public sealed partial class BorgSystem
 
         if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
             args.Container != chassisComp.ModuleContainer ||
-            !chassisComp.Activated)
+            !Toggle.IsActivated(chassis))
             return;
 
         if (!_powerCell.HasDrawCharge(uid))
@@ -143,6 +143,7 @@ public sealed partial class BorgSystem
         var ev = new BorgModuleSelectedEvent(chassis);
         RaiseLocalEvent(moduleUid, ref ev);
         chassisComp.SelectedModule = moduleUid;
+        Dirty(chassis, chassisComp);
     }
 
     /// <summary>
@@ -162,6 +163,7 @@ public sealed partial class BorgSystem
         var ev = new BorgModuleUnselectedEvent(chassis);
         RaiseLocalEvent(chassisComp.SelectedModule.Value, ref ev);
         chassisComp.SelectedModule = null;
+        Dirty(chassis, chassisComp);
     }
 
     private void OnItemModuleSelected(EntityUid uid, ItemBorgModuleComponent component, ref BorgModuleSelectedEvent args)
index c97ca9cbc0db39e6dad84b86fd70e83521f777aa..1c40e9489ebda719bbe41179a6c30404e2b76063 100644 (file)
@@ -10,6 +10,7 @@ using Content.Shared.Alert;
 using Content.Shared.Database;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Interaction;
+using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.Mind;
 using Content.Shared.Mind.Components;
 using Content.Shared.Mobs;
@@ -73,6 +74,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
         SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
         SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
         SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
+        SubscribeLocalEvent<BorgChassisComponent, ItemToggledEvent>(OnToggled);
 
         SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
         SubscribeLocalEvent<BorgBrainComponent, PointAttemptEvent>(OnBrainPointAttempt);
@@ -173,11 +175,11 @@ public sealed partial class BorgSystem : SharedBorgSystem
         if (args.NewMobState == MobState.Alive)
         {
             if (_mind.TryGetMind(uid, out _, out _))
-                _powerCell.SetPowerCellDrawEnabled(uid, true);
+                _powerCell.SetDrawEnabled(uid, true);
         }
         else
         {
-            _powerCell.SetPowerCellDrawEnabled(uid, false);
+            _powerCell.SetDrawEnabled(uid, false);
         }
     }
 
@@ -185,24 +187,10 @@ public sealed partial class BorgSystem : SharedBorgSystem
     {
         UpdateBatteryAlert((uid, component));
 
-        if (!TryComp<PowerCellDrawComponent>(uid, out var draw))
-            return;
-
-        // if we eject the battery or run out of charge, then disable
-        if (args.Ejected || !_powerCell.HasDrawCharge(uid))
-        {
-            DisableBorgAbilities(uid, component);
-            return;
-        }
-
         // if we aren't drawing and suddenly get enough power to draw again, reeanble.
-        if (_powerCell.HasDrawCharge(uid, draw))
+        if (_powerCell.HasDrawCharge(uid))
         {
-            // only reenable the powerdraw if a player has the role.
-            if (!draw.Drawing && _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(uid))
-                _powerCell.SetPowerCellDrawEnabled(uid, true);
-
-            EnableBorgAbilities(uid, component);
+            Toggle.TryActivate(uid);
         }
 
         UpdateUI(uid, component);
@@ -210,7 +198,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
 
     private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
     {
-        DisableBorgAbilities(uid, component);
+        Toggle.TryDeactivate(uid);
         UpdateUI(uid, component);
     }
 
@@ -219,6 +207,23 @@ public sealed partial class BorgSystem : SharedBorgSystem
         args.Dead = true;
     }
 
+    private void OnToggled(Entity<BorgChassisComponent> ent, ref ItemToggledEvent args)
+    {
+        var (uid, comp) = ent;
+        if (args.Activated)
+            InstallAllModules(uid, comp);
+        else
+            DisableAllModules(uid, comp);
+
+        // only enable the powerdraw if there is a player in the chassis
+        var drawing = _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(ent);
+        _powerCell.SetDrawEnabled(uid, drawing);
+
+        UpdateUI(uid, comp);
+
+        _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
+    }
+
     private void OnBrainMindAdded(EntityUid uid, BorgBrainComponent component, MindAddedMessage args)
     {
         if (!Container.TryGetOuterContainer(uid, Transform(uid), out var container))
@@ -271,44 +276,14 @@ public sealed partial class BorgSystem : SharedBorgSystem
         _alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent);
     }
 
-    /// <summary>
-    /// Activates the borg, enabling all of its modules.
-    /// </summary>
-    public void EnableBorgAbilities(EntityUid uid, BorgChassisComponent component, PowerCellDrawComponent? powerCell = null)
-    {
-        if (component.Activated)
-            return;
-
-        component.Activated = true;
-        InstallAllModules(uid, component);
-        Dirty(uid, component);
-        _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
-    }
-
-    /// <summary>
-    /// Deactivates the borg, disabling all of its modules and decreasing its speed.
-    /// </summary>
-    public void DisableBorgAbilities(EntityUid uid, BorgChassisComponent component)
-    {
-        if (!component.Activated)
-            return;
-
-        component.Activated = false;
-        DisableAllModules(uid, component);
-        Dirty(uid, component);
-        _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
-    }
-
     /// <summary>
     /// Activates a borg when a player occupies it
     /// </summary>
     public void BorgActivate(EntityUid uid, BorgChassisComponent component)
     {
         Popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(uid, EntityManager))), uid);
-        _powerCell.SetPowerCellDrawEnabled(uid, true);
-        _access.SetAccessEnabled(uid, true);
+        Toggle.TryActivate(uid);
         _appearance.SetData(uid, BorgVisuals.HasPlayer, true);
-        Dirty(uid, component);
     }
 
     /// <summary>
@@ -317,10 +292,8 @@ public sealed partial class BorgSystem : SharedBorgSystem
     public void BorgDeactivate(EntityUid uid, BorgChassisComponent component)
     {
         Popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(uid, EntityManager))), uid);
-        _powerCell.SetPowerCellDrawEnabled(uid, false);
-        _access.SetAccessEnabled(uid, false);
+        Toggle.TryDeactivate(uid);
         _appearance.SetData(uid, BorgVisuals.HasPlayer, false);
-        Dirty(uid, component);
     }
 
     /// <summary>
diff --git a/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs
deleted file mode 100644 (file)
index d758247..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Server.StationEvents.Events;
-
-namespace Content.Server.StationEvents.Components;
-
-/// <summary>
-/// Configuration component for the Space Ninja antag.
-/// </summary>
-[RegisterComponent, Access(typeof(NinjaSpawnRule))]
-public sealed partial class NinjaSpawnRuleComponent : Component
-{
-    /// <summary>
-    /// Distance that the ninja spawns from the station's half AABB radius
-    /// </summary>
-    [DataField("spawnDistance")]
-    public float SpawnDistance = 20f;
-}
diff --git a/Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs
new file mode 100644 (file)
index 0000000..a016807
--- /dev/null
@@ -0,0 +1,25 @@
+using Content.Server.StationEvents.Events;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.StationEvents.Components;
+
+/// <summary>
+/// Component for spawning antags in space around a station.
+/// Requires <c>AntagSelectionComponent</c>.
+/// </summary>
+[RegisterComponent, Access(typeof(SpaceSpawnRule))]
+public sealed partial class SpaceSpawnRuleComponent : Component
+{
+    /// <summary>
+    /// Distance that the entity spawns from the station's half AABB radius
+    /// </summary>
+    [DataField]
+    public float SpawnDistance = 20f;
+
+    /// <summary>
+    /// Location that was picked.
+    /// </summary>
+    [DataField]
+    public MapCoordinates? Coords;
+}
similarity index 53%
rename from Content.Server/StationEvents/Events/NinjaSpawnRule.cs
rename to Content.Server/StationEvents/Events/SpaceSpawnRule.cs
index 9cbc193ce61fd35353e0b0cf5092cece378a263e..6fccaaa5cfcbb8751324c9c2243cc8c9c27bbd3e 100644 (file)
@@ -1,5 +1,5 @@
+using Content.Server.Antag;
 using Content.Server.GameTicking.Rules.Components;
-using Content.Server.Ninja.Systems;
 using Content.Server.Station.Components;
 using Content.Server.StationEvents.Components;
 using Content.Shared.GameTicking.Components;
@@ -9,18 +9,28 @@ using Robust.Shared.Map.Components;
 namespace Content.Server.StationEvents.Events;
 
 /// <summary>
-/// Event for spawning a Space Ninja mid-game.
+/// Station event component for spawning this rules antags in space around a station.
 /// </summary>
-public sealed class NinjaSpawnRule : StationEventSystem<NinjaSpawnRuleComponent>
+public sealed class SpaceSpawnRule : StationEventSystem<SpaceSpawnRuleComponent>
 {
     [Dependency] private readonly SharedTransformSystem _transform = default!;
 
-    protected override void Started(EntityUid uid, NinjaSpawnRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
+    public override void Initialize()
     {
-        base.Started(uid, comp, gameRule, args);
+        base.Initialize();
+
+        SubscribeLocalEvent<SpaceSpawnRuleComponent, AntagSelectLocationEvent>(OnSelectLocation);
+    }
+
+    protected override void Added(EntityUid uid, SpaceSpawnRuleComponent comp, GameRuleComponent gameRule, GameRuleAddedEvent args)
+    {
+        base.Added(uid, comp, gameRule, args);
 
         if (!TryGetRandomStation(out var station))
+        {
+            ForceEndSelf(uid, gameRule);
             return;
+        }
 
         var stationData = Comp<StationDataComponent>(station.Value);
 
@@ -28,22 +38,28 @@ public sealed class NinjaSpawnRule : StationEventSystem<NinjaSpawnRuleComponent>
         var gridUid = StationSystem.GetLargestGrid(stationData);
         if (gridUid == null || !TryComp<MapGridComponent>(gridUid, out var grid))
         {
-            Sawmill.Warning("Chosen station has no grids, cannot spawn space ninja!");
+            Sawmill.Warning("Chosen station has no grids, cannot pick location for {ToPrettyString(uid):rule}");
+            ForceEndSelf(uid, gameRule);
             return;
         }
 
-        // figure out its AABB size and use that as a guide to how far ninja should be
+        // figure out its AABB size and use that as a guide to how far the spawner should be
         var size = grid.LocalAABB.Size.Length() / 2;
         var distance = size + comp.SpawnDistance;
         var angle = RobustRandom.NextAngle();
         // position relative to station center
         var location = angle.ToVec() * distance;
 
-        // create the spawner, the ninja will appear when a ghost has picked the role
+        // create the spawner!
         var xform = Transform(gridUid.Value);
         var position = _transform.GetWorldPosition(xform) + location;
-        var coords = new MapCoordinates(position, xform.MapID);
-        Sawmill.Info($"Creating ninja spawnpoint at {coords}");
-        Spawn("SpawnPointGhostSpaceNinja", coords);
+        comp.Coords = new MapCoordinates(position, xform.MapID);
+        Sawmill.Info($"Picked location {comp.Coords} for {ToPrettyString(uid):rule}");
+    }
+
+    private void OnSelectLocation(Entity<SpaceSpawnRuleComponent> ent, ref AntagSelectLocationEvent args)
+    {
+        if (ent.Comp.Coords is {} coords)
+            args.Coordinates.Add(coords);
     }
 }
index c1782efabaf1be3a9e9ca60aba0ea0fdfa1a6f1a..97dd2c7e7359bbbf4f4a8d04b661a65af49c65dd 100644 (file)
@@ -19,7 +19,7 @@ namespace Content.Server.Stunnable.Systems
         [Dependency] private readonly RiggableSystem _riggableSystem = default!;
         [Dependency] private readonly SharedPopupSystem _popup = default!;
         [Dependency] private readonly BatterySystem _battery = default!;
-        [Dependency] private readonly SharedItemToggleSystem _itemToggle = default!;
+        [Dependency] private readonly ItemToggleSystem _itemToggle = default!;
 
         public override void Initialize()
         {
index f6aafe376d6f93639071c37a6e2abc711e67890d..2bf53d46f4bdc24f0f43cab3248c016fa8214fd2 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.PowerCell;
+using Content.Shared.Item.ItemToggle;
 using Content.Shared.PowerCell;
 using Content.Shared.Weapons.Misc;
 using Robust.Shared.Physics.Components;
@@ -8,6 +9,7 @@ namespace Content.Server.Weapons.Misc;
 public sealed class TetherGunSystem : SharedTetherGunSystem
 {
     [Dependency] private readonly PowerCellSystem _cell = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
 
     public override void Initialize()
     {
@@ -36,12 +38,12 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
         PhysicsComponent? targetPhysics = null, TransformComponent? targetXform = null)
     {
         base.StartTether(gunUid, component, target, user, targetPhysics, targetXform);
-        _cell.SetPowerCellDrawEnabled(gunUid, true);
+        _toggle.TryActivate(gunUid);
     }
 
     protected override void StopTether(EntityUid gunUid, BaseForceGunComponent component, bool land = true, bool transfer = false)
     {
         base.StopTether(gunUid, component, land, transfer);
-        _cell.SetPowerCellDrawEnabled(gunUid, false);
+        _toggle.TryDeactivate(gunUid);
     }
 }
index c6f56a2750820dee0a00fd6043439042b1979351..a585a9ef4525e22ba0302a016d920a071815462f 100644 (file)
@@ -2,6 +2,7 @@ using System.Linq;
 using Content.Server.Salvage;
 using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
 using Content.Shared.Clothing;
+using Content.Shared.Item.ItemToggle.Components;
 
 namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
 
@@ -29,11 +30,11 @@ public sealed class ArtifactMagnetTriggerSystem : EntitySystem
 
         _toActivate.Clear();
 
-        //assume that there's more instruments than artifacts
-        var query = EntityQueryEnumerator<MagbootsComponent, TransformComponent>();
-        while (query.MoveNext(out _, out var magboot, out var magXform))
+        //assume that there's more magboots than artifacts
+        var query = EntityQueryEnumerator<MagbootsComponent, TransformComponent, ItemToggleComponent>();
+        while (query.MoveNext(out _, out var magboot, out var magXform, out var toggle))
         {
-            if (!magboot.On)
+            if (!toggle.Activated)
                 continue;
 
             var artiQuery = EntityQueryEnumerator<ArtifactMagnetTriggerComponent, TransformComponent>();
diff --git a/Content.Shared/Access/Components/AccessToggleComponent.cs b/Content.Shared/Access/Components/AccessToggleComponent.cs
new file mode 100644 (file)
index 0000000..60a606a
--- /dev/null
@@ -0,0 +1,11 @@
+using Content.Shared.Access.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Access.Components;
+
+/// <summary>
+/// Toggles an access provider with <c>ItemToggle</c>.
+/// Requires <see cref="AccessComponent"/>.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(AccessToggleSystem))]
+public sealed partial class AccessToggleComponent : Component;
diff --git a/Content.Shared/Access/Systems/AccessToggleSystem.cs b/Content.Shared/Access/Systems/AccessToggleSystem.cs
new file mode 100644 (file)
index 0000000..564aca0
--- /dev/null
@@ -0,0 +1,21 @@
+using Content.Shared.Access.Components;
+using Content.Shared.Item.ItemToggle.Components;
+
+namespace Content.Shared.Access.Systems;
+
+public sealed class AccessToggleSystem : EntitySystem
+{
+    [Dependency] private readonly SharedAccessSystem _access = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<AccessToggleComponent, ItemToggledEvent>(OnToggled);
+    }
+
+    private void OnToggled(Entity<AccessToggleComponent> ent, ref ItemToggledEvent args)
+    {
+        _access.SetAccessEnabled(ent, args.Activated);
+    }
+}
index 54d242709c4d4f88dd82ae9e7f3d01bf3f23b2a9..f6efbb10f3e7dc460b14431b36a08ec3106b78a0 100644 (file)
@@ -10,15 +10,12 @@ namespace Content.Shared.Beeper.Components;
 /// This is used for an item that beeps based on
 /// proximity to a specified component.
 /// </summary>
+/// <remarks>
+/// Requires <c>ItemToggleComponent</c> to control it.
+/// </remarks>
 [RegisterComponent, NetworkedComponent, Access(typeof(BeeperSystem)), AutoGenerateComponentState]
 public sealed partial class BeeperComponent : Component
 {
-    /// <summary>
-    /// Whether or not it's on.
-    /// </summary>
-    [DataField, AutoNetworkedField]
-    public bool Enabled = true;
-
     /// <summary>
     /// How much to scale the interval by (< 0 = min, > 1 = max)
     /// </summary>
@@ -56,7 +53,7 @@ public sealed partial class BeeperComponent : Component
     /// Is the beep muted
     /// </summary>
     [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
-    public bool IsMuted = false;
+    public bool IsMuted;
 
     /// <summary>
     /// The sound played when the locator beeps.
index c51eef4da9d3b57a16f7b5b776017cb776e621e0..a52e19f75525badd51966f590326f43bf9775393 100644 (file)
@@ -1,5 +1,7 @@
 using Content.Shared.Beeper.Components;
 using Content.Shared.FixedPoint;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Network;
 using Robust.Shared.Timing;
@@ -11,34 +13,20 @@ namespace Content.Shared.Beeper.Systems;
 public sealed class BeeperSystem : EntitySystem
 {
     [Dependency] private readonly IGameTiming _timing = default!;
-    [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly INetManager _net = default!;
-
-    public override void Initialize()
-    {
-    }
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
 
     public override void Update(float frameTime)
     {
-        var query = EntityQueryEnumerator<BeeperComponent>();
-        while (query.MoveNext(out var uid, out var beeper))
+        var query = EntityQueryEnumerator<BeeperComponent, ItemToggleComponent>();
+        while (query.MoveNext(out var uid, out var beeper, out var toggle))
         {
-            if (!beeper.Enabled)
-                continue;
-            RunUpdate_Internal(uid, beeper);
+            if (toggle.Activated)
+                RunUpdate_Internal(uid, beeper);
         }
     }
 
-    public void SetEnable(EntityUid owner, bool isEnabled, BeeperComponent? beeper = null)
-    {
-        if (!Resolve(owner, ref beeper) || beeper.Enabled == isEnabled)
-            return;
-        beeper.Enabled = isEnabled;
-
-        RunUpdate_Internal(owner, beeper);
-        Dirty(owner, beeper);
-    }
-
     public void SetIntervalScaling(EntityUid owner, BeeperComponent beeper, FixedPoint2 newScaling)
     {
         newScaling = FixedPoint2.Clamp(newScaling, 0, 1);
@@ -70,6 +58,7 @@ public sealed class BeeperSystem : EntitySystem
         if (!Resolve(owner, ref comp))
             return;
         comp.IsMuted = isMuted;
+        Dirty(owner, comp);
     }
 
     private void UpdateBeepInterval(EntityUid owner, BeeperComponent beeper)
@@ -91,19 +80,17 @@ public sealed class BeeperSystem : EntitySystem
 
     private void RunUpdate_Internal(EntityUid owner, BeeperComponent beeper)
     {
-        if (!beeper.Enabled)
-        {
+        if (!_toggle.IsActivated(owner))
             return;
-        }
+
         UpdateBeepInterval(owner, beeper);
         if (beeper.NextBeep >= _timing.CurTime)
             return;
+
         var beepEvent = new BeepPlayedEvent(beeper.IsMuted);
         RaiseLocalEvent(owner, ref beepEvent);
         if (!beeper.IsMuted && _net.IsServer)
-        {
             _audio.PlayPvs(beeper.BeepSound, owner);
-        }
         beeper.LastBeepTime = _timing.CurTime;
     }
 }
index bd857d4c29b0ef6958300c04532e97ce36ab4ec2..ed3c6366c1335f17e7bc7cfb3ccf9d360f391382 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Shared.Beeper.Components;
-using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle;
 using Content.Shared.Pinpointer;
-using Content.Shared.PowerCell;
 using Content.Shared.ProximityDetection;
 using Content.Shared.ProximityDetection.Components;
 using Content.Shared.ProximityDetection.Systems;
@@ -9,20 +8,17 @@ using Content.Shared.ProximityDetection.Systems;
 namespace Content.Shared.Beeper.Systems;
 
 /// <summary>
-/// This handles logic for implementing proximity beeper as a handheld tool />
+/// This handles controlling a beeper from proximity detector events.
 /// </summary>
 public sealed class ProximityBeeperSystem : EntitySystem
 {
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-    [Dependency] private readonly SharedPowerCellSystem _powerCell = default!;
     [Dependency] private readonly ProximityDetectionSystem _proximity = default!;
     [Dependency] private readonly BeeperSystem _beeper = default!;
 
     /// <inheritdoc/>
     public override void Initialize()
     {
-        SubscribeLocalEvent<ProximityBeeperComponent, UseInHandEvent>(OnUseInHand);
-        SubscribeLocalEvent<ProximityBeeperComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
         SubscribeLocalEvent<ProximityBeeperComponent, NewProximityTargetEvent>(OnNewProximityTarget);
         SubscribeLocalEvent<ProximityBeeperComponent, ProximityTargetUpdatedEvent>(OnProximityTargetUpdate);
     }
@@ -33,82 +29,16 @@ public sealed class ProximityBeeperSystem : EntitySystem
             return;
         if (args.Target == null)
         {
-            _beeper.SetEnable(owner, false, beeper);
+            _beeper.SetMute(owner, true, beeper);
             return;
         }
-        _beeper.SetIntervalScaling(owner,args.Distance/args.Detector.Range, beeper);
-        _beeper.SetEnable(owner, true, beeper);
-    }
-
-    private void OnNewProximityTarget(EntityUid owner, ProximityBeeperComponent proxBeeper, ref NewProximityTargetEvent args)
-    {
-        _beeper.SetEnable(owner, args.Target != null);
-    }
 
-    private void OnUseInHand(EntityUid uid, ProximityBeeperComponent proxBeeper, UseInHandEvent args)
-    {
-        if (args.Handled)
-            return;
-        args.Handled = TryToggle(uid, proxBeeper, user: args.User);
+        _beeper.SetIntervalScaling(owner, args.Distance / args.Detector.Range, beeper);
+        _beeper.SetMute(owner, false, beeper);
     }
 
-    private void OnPowerCellSlotEmpty(EntityUid uid, ProximityBeeperComponent beeper, ref PowerCellSlotEmptyEvent args)
-    {
-        if (_proximity.GetEnable(uid))
-            TryDisable(uid);
-    }
-    public bool TryEnable(EntityUid owner, BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null,
-        PowerCellDrawComponent? draw = null,EntityUid? user = null)
-    {
-        if (!Resolve(owner, ref beeper, ref detector))
-            return false;
-        if (Resolve(owner, ref draw, false) && !_powerCell.HasActivatableCharge(owner, battery: draw, user: user))
-                return false;
-        Enable(owner, beeper, detector, draw);
-        return true;
-    }
-    private void Enable(EntityUid owner, BeeperComponent beeper,
-        ProximityDetectorComponent detector, PowerCellDrawComponent? draw)
-    {
-        _proximity.SetEnable(owner, true, detector);
-        _appearance.SetData(owner, ProximityBeeperVisuals.Enabled, true);
-        _powerCell.SetPowerCellDrawEnabled(owner, true, draw);
-    }
-
-
-    /// <summary>
-    /// Disables the proximity beeper
-    /// </summary>
-    public bool TryDisable(EntityUid owner,BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null, PowerCellDrawComponent? draw = null)
-    {
-        if (!Resolve(owner, ref beeper, ref detector))
-            return false;
-
-        if (!detector.Enabled)
-            return false;
-        Disable(owner, beeper, detector, draw);
-        return true;
-    }
-    private void Disable(EntityUid owner, BeeperComponent beeper,
-        ProximityDetectorComponent detector, PowerCellDrawComponent? draw)
-    {
-        _proximity.SetEnable(owner, false, detector);
-        _appearance.SetData(owner, ProximityBeeperVisuals.Enabled, false);
-        _beeper.SetEnable(owner, false, beeper);
-        _powerCell.SetPowerCellDrawEnabled(owner, false, draw);
-    }
-
-    /// <summary>
-    /// toggles the proximity beeper
-    /// </summary>
-    public bool TryToggle(EntityUid owner, ProximityBeeperComponent? proxBeeper = null, BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null,
-        PowerCellDrawComponent? draw = null, EntityUid? user = null)
+    private void OnNewProximityTarget(EntityUid owner, ProximityBeeperComponent proxBeeper, ref NewProximityTargetEvent args)
     {
-        if (!Resolve(owner,  ref proxBeeper, ref beeper, ref detector))
-            return false;
-
-        return detector.Enabled
-            ? TryDisable(owner, beeper, detector, draw)
-            : TryEnable(owner, beeper, detector, draw,user);
+        _beeper.SetMute(owner, args.Target != null);
     }
 }
index 5de1383cde0244528277e5e83b70f7b49b73bfc6..7f95ef184e49a14a31e1493f94f8cd6ea0af2ab8 100644 (file)
@@ -5,10 +5,14 @@ namespace Content.Shared.Charges.Systems;
 
 public abstract class SharedChargesSystem : EntitySystem
 {
+    protected EntityQuery<LimitedChargesComponent> Query;
+
     public override void Initialize()
     {
         base.Initialize();
 
+        Query = GetEntityQuery<LimitedChargesComponent>();
+
         SubscribeLocalEvent<LimitedChargesComponent, ExaminedEvent>(OnExamine);
     }
 
@@ -30,9 +34,9 @@ public abstract class SharedChargesSystem : EntitySystem
     /// <summary>
     /// Tries to add a number of charges. If it over or underflows it will be clamped, wasting the extra charges.
     /// </summary>
-    public void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
+    public virtual void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
     {
-        if (!Resolve(uid, ref comp, false))
+        if (!Query.Resolve(uid, ref comp, false))
             return;
 
         var old = comp.Charges;
@@ -47,7 +51,7 @@ public abstract class SharedChargesSystem : EntitySystem
     public bool IsEmpty(EntityUid uid, LimitedChargesComponent? comp = null)
     {
         // can't be empty if there are no limited charges
-        if (!Resolve(uid, ref comp, false))
+        if (!Query.Resolve(uid, ref comp, false))
             return false;
 
         return comp.Charges <= 0;
@@ -56,10 +60,24 @@ public abstract class SharedChargesSystem : EntitySystem
     /// <summary>
     /// Uses a single charge. Must check IsEmpty beforehand to prevent using with 0 charge.
     /// </summary>
-    public virtual void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+    public void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+    {
+        AddCharges(uid, -1, comp);
+    }
+
+    /// <summary>
+    /// Checks IsEmpty and uses a charge if it isn't empty.
+    /// </summary>
+    public bool TryUseCharge(Entity<LimitedChargesComponent?> ent)
     {
-        if (Resolve(uid, ref comp, false))
-            AddCharges(uid, -1, comp);
+        if (!Query.Resolve(ent, ref ent.Comp, false))
+            return true;
+
+        if (IsEmpty(ent, ent.Comp))
+            return false;
+
+        UseCharge(ent, ent.Comp);
+        return true;
     }
 
     /// <summary>
@@ -80,7 +98,6 @@ public abstract class SharedChargesSystem : EntitySystem
     /// </summary>
     public virtual void UseCharges(EntityUid uid, int chargesUsed, LimitedChargesComponent? comp = null)
     {
-        if (Resolve(uid, ref comp, false))
-            AddCharges(uid, -chargesUsed, comp);
+        AddCharges(uid, -chargesUsed, comp);
     }
 }
index c3c4baf19d0eb34ecacae01d44f303a2e5ac168f..866ce38a5721b7a3e7dc492688ce7e6c4112d5f1 100644 (file)
@@ -3,20 +3,18 @@ using Robust.Shared.Serialization;
 
 namespace Content.Shared.Clothing;
 
+/// <summary>
+/// Modifies speed when worn and activated.
+/// Supports <c>ItemToggleComponent</c>.
+/// </summary>
 [RegisterComponent, NetworkedComponent, Access(typeof(ClothingSpeedModifierSystem))]
 public sealed partial class ClothingSpeedModifierComponent : Component
 {
-    [DataField("walkModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float WalkModifier = 1.0f;
 
-    [DataField("sprintModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float SprintModifier = 1.0f;
-
-    /// <summary>
-    ///     Is this clothing item currently 'actively' slowing you down?
-    ///     e.g. magboots can be turned on and off.
-    /// </summary>
-    [DataField("enabled")] public bool Enabled = true;
 }
 
 [Serializable, NetSerializable]
@@ -25,12 +23,9 @@ public sealed class ClothingSpeedModifierComponentState : ComponentState
     public float WalkModifier;
     public float SprintModifier;
 
-    public bool Enabled;
-
-    public ClothingSpeedModifierComponentState(float walkModifier, float sprintModifier, bool enabled)
+    public ClothingSpeedModifierComponentState(float walkModifier, float sprintModifier)
     {
         WalkModifier = walkModifier;
         SprintModifier = sprintModifier;
-        Enabled = enabled;
     }
 }
index 4198c5c165bd8a3809572f4ab56f5784097a9562..c1efe0b3ddda36909549badc7a2d557e49b3f26a 100644 (file)
@@ -1,11 +1,10 @@
-using Content.Shared.Actions;
 using Content.Shared.Clothing.Components;
 using Content.Shared.Examine;
-using Content.Shared.IdentityManagement;
 using Content.Shared.Inventory;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.Movement.Systems;
 using Content.Shared.PowerCell;
-using Content.Shared.Toggleable;
 using Content.Shared.Verbs;
 using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
@@ -15,12 +14,12 @@ namespace Content.Shared.Clothing;
 
 public sealed class ClothingSpeedModifierSystem : EntitySystem
 {
-    [Dependency] private readonly SharedActionsSystem _actions = default!;
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
     [Dependency] private readonly ClothingSpeedModifierSystem _clothingSpeedModifier = default!;
     [Dependency] private readonly SharedContainerSystem _container = default!;
     [Dependency] private readonly ExamineSystemShared _examine = default!;
     [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
     [Dependency] private readonly SharedPowerCellSystem _powerCell = default!;
 
     public override void Initialize()
@@ -31,39 +30,12 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
         SubscribeLocalEvent<ClothingSpeedModifierComponent, ComponentHandleState>(OnHandleState);
         SubscribeLocalEvent<ClothingSpeedModifierComponent, InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent>>(OnRefreshMoveSpeed);
         SubscribeLocalEvent<ClothingSpeedModifierComponent, GetVerbsEvent<ExamineVerb>>(OnClothingVerbExamine);
-
-        SubscribeLocalEvent<ToggleClothingSpeedComponent, GetVerbsEvent<ActivationVerb>>(AddToggleVerb);
-        SubscribeLocalEvent<ToggleClothingSpeedComponent, GetItemActionsEvent>(OnGetActions);
-        SubscribeLocalEvent<ToggleClothingSpeedComponent, ToggleClothingSpeedEvent>(OnToggleSpeed);
-        SubscribeLocalEvent<ToggleClothingSpeedComponent, MapInitEvent>(OnMapInit);
-        SubscribeLocalEvent<ToggleClothingSpeedComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
+        SubscribeLocalEvent<ClothingSpeedModifierComponent, ItemToggledEvent>(OnToggled);
     }
 
-    // Public API
-
-    public void SetClothingSpeedModifierEnabled(EntityUid uid, bool enabled, ClothingSpeedModifierComponent? component = null)
-    {
-        if (!Resolve(uid, ref component, false))
-            return;
-
-        if (component.Enabled != enabled)
-        {
-            component.Enabled = enabled;
-            Dirty(uid, component);
-
-            // inventory system will automatically hook into the event raised by this and update accordingly
-            if (_container.TryGetContainingContainer(uid, out var container))
-            {
-                _movementSpeed.RefreshMovementSpeedModifiers(container.Owner);
-            }
-        }
-    }
-
-    // Event handlers
-
     private void OnGetState(EntityUid uid, ClothingSpeedModifierComponent component, ref ComponentGetState args)
     {
-        args.State = new ClothingSpeedModifierComponentState(component.WalkModifier, component.SprintModifier, component.Enabled);
+        args.State = new ClothingSpeedModifierComponentState(component.WalkModifier, component.SprintModifier);
     }
 
     private void OnHandleState(EntityUid uid, ClothingSpeedModifierComponent component, ref ComponentHandleState args)
@@ -71,13 +43,11 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
         if (args.Current is not ClothingSpeedModifierComponentState state)
             return;
 
-        var diff = component.Enabled != state.Enabled ||
-                   !MathHelper.CloseTo(component.SprintModifier, state.SprintModifier) ||
+        var diff = !MathHelper.CloseTo(component.SprintModifier, state.SprintModifier) ||
                    !MathHelper.CloseTo(component.WalkModifier, state.WalkModifier);
 
         component.WalkModifier = state.WalkModifier;
         component.SprintModifier = state.SprintModifier;
-        component.Enabled = state.Enabled;
 
         // Avoid raising the event for the container if nothing changed.
         // We'll still set the values in case they're slightly different but within tolerance.
@@ -89,10 +59,8 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
 
     private void OnRefreshMoveSpeed(EntityUid uid, ClothingSpeedModifierComponent component, InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent> args)
     {
-        if (!component.Enabled)
-            return;
-
-        args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
+        if (_toggle.IsActivated(uid))
+            args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
     }
 
     private void OnClothingVerbExamine(EntityUid uid, ClothingSpeedModifierComponent component, GetVerbsEvent<ExamineVerb> args)
@@ -142,60 +110,15 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
         _examine.AddDetailedExamineVerb(args, component, msg, Loc.GetString("clothing-speed-examinable-verb-text"), "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png", Loc.GetString("clothing-speed-examinable-verb-message"));
     }
 
-    private void OnMapInit(Entity<ToggleClothingSpeedComponent> uid, ref MapInitEvent args)
-    {
-        _actions.AddAction(uid, ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
-    }
-
-    private void OnToggleSpeed(Entity<ToggleClothingSpeedComponent> uid, ref ToggleClothingSpeedEvent args)
-    {
-        if (args.Handled)
-            return;
-
-        args.Handled = true;
-        SetSpeedToggleEnabled(uid, !uid.Comp.Enabled, args.Performer);
-    }
-
-    private void SetSpeedToggleEnabled(Entity<ToggleClothingSpeedComponent> uid, bool value, EntityUid? user)
+    private void OnToggled(Entity<ClothingSpeedModifierComponent> ent, ref ItemToggledEvent args)
     {
-        if (uid.Comp.Enabled == value)
-            return;
-
-        TryComp<PowerCellDrawComponent>(uid, out var draw);
-        if (value && !_powerCell.HasDrawCharge(uid, draw, user: user))
-            return;
+        // make sentient boots slow or fast too
+        _movementSpeed.RefreshMovementSpeedModifiers(ent);
 
-        uid.Comp.Enabled = value;
-
-        _appearance.SetData(uid, ToggleVisuals.Toggled, uid.Comp.Enabled);
-        _actions.SetToggled(uid.Comp.ToggleActionEntity, uid.Comp.Enabled);
-        _clothingSpeedModifier.SetClothingSpeedModifierEnabled(uid.Owner, uid.Comp.Enabled);
-        _powerCell.SetPowerCellDrawEnabled(uid, uid.Comp.Enabled, draw);
-        Dirty(uid, uid.Comp);
-    }
-
-    private void AddToggleVerb(Entity<ToggleClothingSpeedComponent> uid, ref GetVerbsEvent<ActivationVerb> args)
-    {
-        if (!args.CanAccess || !args.CanInteract)
-            return;
-
-        var user = args.User;
-        ActivationVerb verb = new()
+        if (_container.TryGetContainingContainer(ent.Owner, out var container))
         {
-            Text = Loc.GetString("toggle-clothing-verb-text",
-                ("entity", Identity.Entity(uid, EntityManager))),
-            Act = () => SetSpeedToggleEnabled(uid, !uid.Comp.Enabled, user)
-        };
-        args.Verbs.Add(verb);
-    }
-
-    private void OnGetActions(Entity<ToggleClothingSpeedComponent> uid, ref GetItemActionsEvent args)
-    {
-        args.AddAction(ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
-    }
-
-    private void OnPowerCellSlotEmpty(Entity<ToggleClothingSpeedComponent> uid, ref PowerCellSlotEmptyEvent args)
-    {
-        SetSpeedToggleEnabled(uid, false, null);
+            // inventory system will automatically hook into the event raised by this and update accordingly
+            _movementSpeed.RefreshMovementSpeedModifiers(container.Owner);
+        }
     }
 }
diff --git a/Content.Shared/Clothing/Components/StealthClothingComponent.cs b/Content.Shared/Clothing/Components/StealthClothingComponent.cs
deleted file mode 100644 (file)
index fedf48b..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Clothing.EntitySystems;
-using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.Clothing.Components;
-
-/// <summary>
-/// Adds StealthComponent to the user when enabled, either by an action or the system's SetEnabled method.
-/// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(StealthClothingSystem))]
-public sealed partial class StealthClothingComponent : Component
-{
-    /// <summary>
-    /// Whether stealth effect is enabled.
-    /// </summary>
-    [DataField("enabled"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
-    public bool Enabled;
-
-    /// <summary>
-    /// Number added to MinVisibility when stealthed, to make the user not fully invisible.
-    /// </summary>
-    [DataField("visibility"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
-    public float Visibility;
-
-    [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string ToggleAction = "ActionTogglePhaseCloak";
-
-    /// <summary>
-    /// The action for enabling and disabling stealth.
-    /// </summary>
-    [DataField, AutoNetworkedField] public EntityUid? ToggleActionEntity;
-}
-
-/// <summary>
-/// When stealth is enabled, disables it.
-/// When it is disabled, raises <see cref="AttemptStealthEvent"/> before enabling.
-/// Put any checks in a handler for that event to cancel it.
-/// </summary>
-public sealed partial class ToggleStealthEvent : InstantActionEvent
-{
-}
diff --git a/Content.Shared/Clothing/Components/ToggleClothingComponent.cs b/Content.Shared/Clothing/Components/ToggleClothingComponent.cs
new file mode 100644 (file)
index 0000000..c77aa03
--- /dev/null
@@ -0,0 +1,40 @@
+using Content.Shared.Actions;
+using Content.Shared.Clothing.EntitySystems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Clothing.Components;
+
+/// <summary>
+/// Clothing that can be enabled and disabled with an action.
+/// Requires <see cref="ItemToggleComponent"/>.
+/// </summary>
+/// <remarks>
+/// Not to be confused with <see cref="ToggleableClothingComponent"/> for hardsuit helmets and such.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(ToggleClothingSystem))]
+public sealed partial class ToggleClothingComponent : Component
+{
+    /// <summary>
+    /// The action to add when equipped, even if not worn.
+    /// This must raise <see cref="ToggleActionEvent"/> to then get handled.
+    /// </summary>
+    [DataField(required: true)]
+    public EntProtoId<InstantActionComponent> Action = string.Empty;
+
+    [DataField, AutoNetworkedField]
+    public EntityUid? ActionEntity;
+
+    /// <summary>
+    /// If true, automatically disable the clothing after unequipping it.
+    /// </summary>
+    [DataField]
+    public bool DisableOnUnequip;
+}
+
+/// <summary>
+/// Raised on the clothing when being equipped to see if it should add the action.
+/// </summary>
+[ByRefEvent]
+public record struct ToggleClothingCheckEvent(EntityUid User, bool Cancelled = false);
diff --git a/Content.Shared/Clothing/Components/ToggleClothingSpeedComponent.cs b/Content.Shared/Clothing/Components/ToggleClothingSpeedComponent.cs
deleted file mode 100644 (file)
index 90b2d73..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-using Content.Shared.Actions;
-using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Clothing.Components;
-
-/// <summary>
-/// This is used for a clothing item that gives a speed modification that is toggleable.
-/// </summary>
-[RegisterComponent, NetworkedComponent, Access(typeof(ClothingSpeedModifierSystem)), AutoGenerateComponentState]
-public sealed partial class ToggleClothingSpeedComponent : Component
-{
-    /// <summary>
-    /// The action for toggling the clothing.
-    /// </summary>
-    [DataField]
-    public EntProtoId ToggleAction = "ActionToggleSpeedBoots";
-
-    /// <summary>
-    /// The action entity
-    /// </summary>
-    [DataField, AutoNetworkedField]
-    public EntityUid? ToggleActionEntity;
-
-    /// <summary>
-    /// The state of the toggle.
-    /// </summary>
-    [DataField, AutoNetworkedField]
-    public bool Enabled;
-}
-
-public sealed partial class ToggleClothingSpeedEvent : InstantActionEvent
-{
-
-}
diff --git a/Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs
deleted file mode 100644 (file)
index e96d9f8..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Clothing.Components;
-using Content.Shared.Inventory.Events;
-using Content.Shared.Stealth;
-using Content.Shared.Stealth.Components;
-
-namespace Content.Shared.Clothing.EntitySystems;
-
-/// <summary>
-/// Handles the toggle action and disables stealth when clothing is unequipped.
-/// </summary>
-public sealed class StealthClothingSystem : EntitySystem
-{
-    [Dependency] private readonly SharedStealthSystem _stealth = default!;
-    [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<StealthClothingComponent, GetItemActionsEvent>(OnGetItemActions);
-        SubscribeLocalEvent<StealthClothingComponent, ToggleStealthEvent>(OnToggleStealth);
-        SubscribeLocalEvent<StealthClothingComponent, AfterAutoHandleStateEvent>(OnHandleState);
-        SubscribeLocalEvent<StealthClothingComponent, GotUnequippedEvent>(OnUnequipped);
-        SubscribeLocalEvent<StealthClothingComponent, MapInitEvent>(OnMapInit);
-    }
-
-    private void OnMapInit(EntityUid uid, StealthClothingComponent component, MapInitEvent args)
-    {
-        _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
-        Dirty(uid, component);
-    }
-
-    /// <summary>
-    /// Sets the clothing's stealth effect for the user.
-    /// </summary>
-    /// <returns>True if it was changed, false otherwise</returns>
-    public bool SetEnabled(EntityUid uid, EntityUid user, bool enabled, StealthClothingComponent? comp = null)
-    {
-        if (!Resolve(uid, ref comp) || comp.Enabled == enabled)
-            return false;
-
-        // TODO remove this when clothing unequip on delete is less sus
-        // prevent debug assert when ending round and its disabled
-        if (MetaData(user).EntityLifeStage >= EntityLifeStage.Terminating)
-            return false;
-
-        comp.Enabled = enabled;
-        Dirty(uid, comp);
-
-        var stealth = EnsureComp<StealthComponent>(user);
-        // slightly visible, but doesn't change when moving so it's ok
-        var visibility = enabled ? stealth.MinVisibility + comp.Visibility : stealth.MaxVisibility;
-        _stealth.SetVisibility(user, visibility, stealth);
-        _stealth.SetEnabled(user, enabled, stealth);
-        return true;
-    }
-
-    /// <summary>
-    /// Raise <see cref="AddStealthActionEvent"/> then add the toggle action if it was not cancelled.
-    /// </summary>
-    private void OnGetItemActions(EntityUid uid, StealthClothingComponent comp, GetItemActionsEvent args)
-    {
-        var ev = new AddStealthActionEvent(args.User);
-        RaiseLocalEvent(uid, ev);
-        if (ev.Cancelled)
-            return;
-
-        args.AddAction(ref comp.ToggleActionEntity, comp.ToggleAction);
-    }
-
-    /// <summary>
-    /// Raises <see cref="AttemptStealthEvent"/> if enabling.
-    /// </summary>
-    private void OnToggleStealth(EntityUid uid, StealthClothingComponent comp, ToggleStealthEvent args)
-    {
-        args.Handled = true;
-        var user = args.Performer;
-        if (comp.Enabled)
-        {
-            SetEnabled(uid, user, false, comp);
-            return;
-        }
-
-        var ev = new AttemptStealthEvent(user);
-        RaiseLocalEvent(uid, ev);
-        if (ev.Cancelled)
-            return;
-
-        SetEnabled(uid, user, true, comp);
-    }
-
-    /// <summary>
-    /// Calls <see cref="SetEnabled"/> when server sends new state.
-    /// </summary>
-    private void OnHandleState(EntityUid uid, StealthClothingComponent comp, ref AfterAutoHandleStateEvent args)
-    {
-        // SetEnabled checks if it is the same, so change it to before state was received from the server
-        var enabled = comp.Enabled;
-        comp.Enabled = !enabled;
-        var user = Transform(uid).ParentUid;
-        SetEnabled(uid, user, enabled, comp);
-    }
-
-    /// <summary>
-    /// Force unstealths the user, doesnt remove StealthComponent since other things might use it
-    /// </summary>
-    private void OnUnequipped(EntityUid uid, StealthClothingComponent comp, GotUnequippedEvent args)
-    {
-        SetEnabled(uid, args.Equipee, false, comp);
-    }
-}
-
-/// <summary>
-/// Raised on the stealth clothing when attempting to add an action.
-/// </summary>
-public sealed class AddStealthActionEvent : CancellableEntityEventArgs
-{
-    /// <summary>
-    /// User that equipped the stealth clothing.
-    /// </summary>
-    public EntityUid User;
-
-    public AddStealthActionEvent(EntityUid user)
-    {
-        User = user;
-    }
-}
-
-/// <summary>
-/// Raised on the stealth clothing when the user is attemping to enable it.
-/// </summary>
-public sealed class AttemptStealthEvent : CancellableEntityEventArgs
-{
-    /// <summary>
-    /// User that is attempting to enable the stealth clothing.
-    /// </summary>
-    public EntityUid User;
-
-    public AttemptStealthEvent(EntityUid user)
-    {
-        User = user;
-    }
-}
diff --git a/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs
new file mode 100644 (file)
index 0000000..9889376
--- /dev/null
@@ -0,0 +1,58 @@
+using Content.Shared.Actions;
+using Content.Shared.Clothing;
+using Content.Shared.Clothing.Components;
+using Content.Shared.Inventory;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Toggleable;
+
+namespace Content.Shared.Clothing.EntitySystems;
+
+/// <summary>
+/// Handles adding and using a toggle action for <see cref="ToggleClothingComponent"/>.
+/// </summary>
+public sealed class ToggleClothingSystem : EntitySystem
+{
+    [Dependency] private readonly SharedActionsSystem _actions = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ToggleClothingComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<ToggleClothingComponent, GetItemActionsEvent>(OnGetActions);
+        SubscribeLocalEvent<ToggleClothingComponent, ToggleActionEvent>(OnToggleAction);
+        SubscribeLocalEvent<ToggleClothingComponent, ClothingGotUnequippedEvent>(OnUnequipped);
+    }
+
+    private void OnMapInit(Entity<ToggleClothingComponent> ent, ref MapInitEvent args)
+    {
+        var (uid, comp) = ent;
+        // test funny
+        if (string.IsNullOrEmpty(comp.Action))
+            return;
+
+        _actions.AddAction(uid, ref comp.ActionEntity, comp.Action);
+        _actions.SetToggled(comp.ActionEntity, _toggle.IsActivated(ent.Owner));
+        Dirty(uid, comp);
+    }
+
+    private void OnGetActions(Entity<ToggleClothingComponent> ent, ref GetItemActionsEvent args)
+    {
+        var ev = new ToggleClothingCheckEvent(args.User);
+        RaiseLocalEvent(ent, ref ev);
+        if (!ev.Cancelled)
+            args.AddAction(ent.Comp.ActionEntity);
+    }
+
+    private void OnToggleAction(Entity<ToggleClothingComponent> ent, ref ToggleActionEvent args)
+    {
+        args.Handled = _toggle.Toggle(ent.Owner, args.Performer);
+    }
+
+    private void OnUnequipped(Entity<ToggleClothingComponent> ent, ref ClothingGotUnequippedEvent args)
+    {
+        if (ent.Comp.DisableOnUnequip)
+            _toggle.TryDeactivate(ent.Owner, args.Wearer);
+    }
+}
index b3fb607a38be4c5ad92105d73a2aac5808db2303..4bef74fd33506da8c06f047abfaed925e1f76eca 100644 (file)
@@ -1,23 +1,13 @@
 using Content.Shared.Alert;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
 namespace Content.Shared.Clothing;
 
-[RegisterComponent, NetworkedComponent(), AutoGenerateComponentState]
+[RegisterComponent, NetworkedComponent]
 [Access(typeof(SharedMagbootsSystem))]
 public sealed partial class MagbootsComponent : Component
 {
-    [DataField]
-    public EntProtoId ToggleAction = "ActionToggleMagboots";
-
-    [DataField, AutoNetworkedField]
-    public EntityUid? ToggleActionEntity;
-
-    [DataField("on"), AutoNetworkedField]
-    public bool On;
-
     [DataField]
     public ProtoId<AlertPrototype> MagbootsAlert = "Magboots";
 
@@ -26,4 +16,10 @@ public sealed partial class MagbootsComponent : Component
     /// </summary>
     [DataField]
     public bool RequiresGrid = true;
+
+    /// <summary>
+    /// Slot the clothing has to be worn in to work.
+    /// </summary>
+    [DataField]
+    public string Slot = "shoes";
 }
diff --git a/Content.Shared/Clothing/MagbootsSystem.cs b/Content.Shared/Clothing/MagbootsSystem.cs
new file mode 100644 (file)
index 0000000..88d987a
--- /dev/null
@@ -0,0 +1,90 @@
+using Content.Shared.Actions;
+using Content.Shared.Alert;
+using Content.Shared.Atmos.Components;
+using Content.Shared.Clothing.EntitySystems;
+using Content.Shared.Gravity;
+using Content.Shared.Inventory;
+using Content.Shared.Item;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
+using Robust.Shared.Containers;
+
+namespace Content.Shared.Clothing;
+
+public sealed class SharedMagbootsSystem : EntitySystem
+{
+    [Dependency] private readonly AlertsSystem _alerts = default!;
+    [Dependency] private readonly ClothingSystem _clothing = default!;
+    [Dependency] private readonly InventorySystem _inventory = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
+    [Dependency] private readonly SharedContainerSystem _container = default!;
+    [Dependency] private readonly SharedGravitySystem _gravity = default!;
+    [Dependency] private readonly SharedItemSystem _item = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<MagbootsComponent, ItemToggledEvent>(OnToggled);
+        SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
+        SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
+        SubscribeLocalEvent<MagbootsComponent, IsWeightlessEvent>(OnIsWeightless);
+        SubscribeLocalEvent<MagbootsComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
+    }
+
+    private void OnToggled(Entity<MagbootsComponent> ent, ref ItemToggledEvent args)
+    {
+        var (uid, comp) = ent;
+        // only stick to the floor if being worn in the correct slot
+        if (_container.TryGetContainingContainer(uid, out var container) &&
+            _inventory.TryGetSlotEntity(container.Owner, comp.Slot, out var worn)
+            && uid == worn)
+        {
+            UpdateMagbootEffects(container.Owner, ent, args.Activated);
+        }
+
+        var prefix = args.Activated ? "on" : null;
+        _item.SetHeldPrefix(ent, prefix);
+        _clothing.SetEquippedPrefix(ent, prefix);
+    }
+
+    private void OnGotUnequipped(Entity<MagbootsComponent> ent, ref ClothingGotUnequippedEvent args)
+    {
+        UpdateMagbootEffects(args.Wearer, ent, false);
+    }
+
+    private void OnGotEquipped(Entity<MagbootsComponent> ent, ref ClothingGotEquippedEvent args)
+    {
+        UpdateMagbootEffects(args.Wearer, ent, _toggle.IsActivated(ent.Owner));
+    }
+
+    public void UpdateMagbootEffects(EntityUid user, Entity<MagbootsComponent> ent, bool state)
+    {
+        // TODO: public api for this and add access
+        if (TryComp<MovedByPressureComponent>(user, out var moved))
+            moved.Enabled = !state;
+
+        if (state)
+            _alerts.ShowAlert(user, ent.Comp.MagbootsAlert);
+        else
+            _alerts.ClearAlert(user, ent.Comp.MagbootsAlert);
+    }
+
+    private void OnIsWeightless(Entity<MagbootsComponent> ent, ref IsWeightlessEvent args)
+    {
+        if (args.Handled || !_toggle.IsActivated(ent.Owner))
+            return;
+
+        // do not cancel weightlessness if the person is in off-grid.
+        if (ent.Comp.RequiresGrid && !_gravity.EntityOnGravitySupportingGridOrMap(ent.Owner))
+            return;
+
+        args.IsWeightless = false;
+        args.Handled = true;
+    }
+
+    private void OnIsWeightless(Entity<MagbootsComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
+    {
+        OnIsWeightless(ent, ref args.Args);
+    }
+}
diff --git a/Content.Shared/Clothing/SharedMagbootsSystem.cs b/Content.Shared/Clothing/SharedMagbootsSystem.cs
deleted file mode 100644 (file)
index 6814593..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Alert;
-using Content.Shared.Atmos.Components;
-using Content.Shared.Clothing.EntitySystems;
-using Content.Shared.Gravity;
-using Content.Shared.Inventory;
-using Content.Shared.Item;
-using Content.Shared.Slippery;
-using Content.Shared.Toggleable;
-using Content.Shared.Verbs;
-using Robust.Shared.Containers;
-
-namespace Content.Shared.Clothing;
-
-public sealed class SharedMagbootsSystem : EntitySystem
-{
-    [Dependency] private readonly AlertsSystem _alerts = default!;
-    [Dependency] private readonly ClothingSpeedModifierSystem _clothingSpeedModifier = default!;
-    [Dependency] private readonly ClothingSystem _clothing = default!;
-    [Dependency] private readonly SharedGravitySystem _gravity = default!;
-    [Dependency] private readonly InventorySystem _inventory = default!;
-    [Dependency] private readonly SharedActionsSystem _sharedActions = default!;
-    [Dependency] private readonly SharedActionsSystem _actionContainer = default!;
-    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-    [Dependency] private readonly SharedContainerSystem _sharedContainer = default!;
-    [Dependency] private readonly SharedItemSystem _item = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<MagbootsComponent, GetVerbsEvent<ActivationVerb>>(AddToggleVerb);
-        SubscribeLocalEvent<MagbootsComponent, InventoryRelayedEvent<SlipAttemptEvent>>(OnSlipAttempt);
-        SubscribeLocalEvent<MagbootsComponent, GetItemActionsEvent>(OnGetActions);
-        SubscribeLocalEvent<MagbootsComponent, ToggleMagbootsEvent>(OnToggleMagboots);
-        SubscribeLocalEvent<MagbootsComponent, MapInitEvent>(OnMapInit);
-
-        SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
-        SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
-
-        SubscribeLocalEvent<MagbootsComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
-    }
-
-    private void OnMapInit(EntityUid uid, MagbootsComponent component, MapInitEvent args)
-    {
-        _actionContainer.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
-        Dirty(uid, component);
-    }
-
-    private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, ref ClothingGotUnequippedEvent args)
-    {
-        UpdateMagbootEffects(args.Wearer, uid, false, component);
-    }
-
-    private void OnGotEquipped(EntityUid uid, MagbootsComponent component, ref ClothingGotEquippedEvent args)
-    {
-        UpdateMagbootEffects(args.Wearer, uid, true, component);
-    }
-
-    private void OnToggleMagboots(EntityUid uid, MagbootsComponent component, ToggleMagbootsEvent args)
-    {
-        if (args.Handled)
-            return;
-
-        args.Handled = true;
-
-        ToggleMagboots(uid, component);
-    }
-
-    private void ToggleMagboots(EntityUid uid, MagbootsComponent magboots)
-    {
-        magboots.On = !magboots.On;
-
-        if (_sharedContainer.TryGetContainingContainer((uid, Transform(uid)), out var container) &&
-            _inventory.TryGetSlotEntity(container.Owner, "shoes", out var entityUid) && entityUid == uid)
-        {
-            UpdateMagbootEffects(container.Owner, uid, true, magboots);
-        }
-
-        if (TryComp<ItemComponent>(uid, out var item))
-        {
-            _item.SetHeldPrefix(uid, magboots.On ? "on" : null, component: item);
-            _clothing.SetEquippedPrefix(uid, magboots.On ? "on" : null);
-        }
-
-        _appearance.SetData(uid, ToggleVisuals.Toggled, magboots.On);
-        OnChanged(uid, magboots);
-        Dirty(uid, magboots);
-    }
-
-    public void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component)
-    {
-        if (!Resolve(uid, ref component))
-            return;
-        state = state && component.On;
-
-        if (TryComp(parent, out MovedByPressureComponent? movedByPressure))
-        {
-            movedByPressure.Enabled = !state;
-        }
-
-        if (state)
-        {
-            _alerts.ShowAlert(parent, component.MagbootsAlert);
-        }
-        else
-        {
-            _alerts.ClearAlert(parent, component.MagbootsAlert);
-        }
-    }
-
-    private void OnChanged(EntityUid uid, MagbootsComponent component)
-    {
-        _sharedActions.SetToggled(component.ToggleActionEntity, component.On);
-        _clothingSpeedModifier.SetClothingSpeedModifierEnabled(uid, component.On);
-    }
-
-    private void AddToggleVerb(EntityUid uid, MagbootsComponent component, GetVerbsEvent<ActivationVerb> args)
-    {
-        if (!args.CanAccess || !args.CanInteract)
-            return;
-
-        ActivationVerb verb = new()
-        {
-            Text = Loc.GetString("toggle-magboots-verb-get-data-text"),
-            Act = () => ToggleMagboots(uid, component),
-            // TODO VERB ICON add toggle icon? maybe a computer on/off symbol?
-        };
-        args.Verbs.Add(verb);
-    }
-
-    private void OnSlipAttempt(EntityUid uid, MagbootsComponent component, InventoryRelayedEvent<SlipAttemptEvent> args)
-    {
-        if (component.On)
-            args.Args.Cancel();
-    }
-
-    private void OnGetActions(EntityUid uid, MagbootsComponent component, GetItemActionsEvent args)
-    {
-        args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
-    }
-
-    private void OnIsWeightless(Entity<MagbootsComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
-    {
-        if (args.Args.Handled)
-            return;
-
-        if (!ent.Comp.On)
-            return;
-
-        // do not cancel weightlessness if the person is in off-grid.
-        if (ent.Comp.RequiresGrid && !_gravity.EntityOnGravitySupportingGridOrMap(ent.Owner))
-            return;
-
-        args.Args.IsWeightless = false;
-        args.Args.Handled = true;
-    }
-}
-
-public sealed partial class ToggleMagbootsEvent : InstantActionEvent;
diff --git a/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs b/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs
new file mode 100644 (file)
index 0000000..760cefe
--- /dev/null
@@ -0,0 +1,26 @@
+using Content.Shared.Item.ItemToggle.Components;
+
+namespace Content.Shared.Item.ItemToggle;
+
+/// <summary>
+/// Handles <see cref="ComponentTogglerComponent"/> component manipulation.
+/// </summary>
+public sealed class ComponentTogglerSystem : EntitySystem
+{
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ComponentTogglerComponent, ItemToggledEvent>(OnToggled);
+    }
+
+    private void OnToggled(Entity<ComponentTogglerComponent> ent, ref ItemToggledEvent args)
+    {
+        var target = ent.Comp.Parent ? Transform(ent).ParentUid : ent.Owner;
+
+        if (args.Activated)
+            EntityManager.AddComponents(target, ent.Comp.Components);
+        else
+            EntityManager.RemoveComponents(target, ent.Comp.RemoveComponents ?? ent.Comp.Components);
+    }
+}
diff --git a/Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs b/Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs
new file mode 100644 (file)
index 0000000..20ef0a0
--- /dev/null
@@ -0,0 +1,32 @@
+using Content.Shared.Item.ItemToggle;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Item.ItemToggle.Components;
+
+/// <summary>
+/// Adds or removes components when toggled.
+/// Requires <see cref="ItemToggleComponent"/>.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(ComponentTogglerSystem))]
+public sealed partial class ComponentTogglerComponent : Component
+{
+    /// <summary>
+    /// The components to add when activated.
+    /// </summary>
+    [DataField(required: true)]
+    public ComponentRegistry Components = new();
+
+    /// <summary>
+    /// The components to remove when deactivated.
+    /// If this is null <see cref="Components"/> is reused.
+    /// </summary>
+    [DataField]
+    public ComponentRegistry? RemoveComponents;
+
+    /// <summary>
+    /// If true, adds components on the entity's parent instead of the entity itself.
+    /// </summary>
+    [DataField]
+    public bool Parent;
+}
index 6d534713578bfcdc3b60be8a38dc09375ff6501f..cdac49ae6d6114de22a946d54c91e9ca39a5efb3 100644 (file)
@@ -12,12 +12,12 @@ public sealed partial class ItemToggleActiveSoundComponent : Component
     /// <summary>
     ///     The continuous noise this item makes when it's activated (like an e-sword's hum).
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+    [DataField(required: true), AutoNetworkedField]
     public SoundSpecifier? ActiveSound;
 
     /// <summary>
     ///     Used when the item emits sound while active.
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField]
+    [DataField]
     public EntityUid? PlayingStream;
 }
index 620ddfd19422470e4febcb321e9524663e3ed334..46249fdd0defde3734047775ffce9df99e8e15ae 100644 (file)
@@ -8,7 +8,7 @@ namespace Content.Shared.Item.ItemToggle.Components;
 /// </summary>
 /// <remarks>
 /// If you need extended functionality (e.g. requiring power) then add a new component and use events:
-/// ItemToggleActivateAttemptEvent, ItemToggleDeactivateAttemptEvent or ItemToggleForceToggleEvent.
+/// ItemToggleActivateAttemptEvent, ItemToggleDeactivateAttemptEvent, ItemToggledEvent.
 /// </remarks>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class ItemToggleComponent : Component
@@ -19,6 +19,13 @@ public sealed partial class ItemToggleComponent : Component
     [DataField, AutoNetworkedField]
     public bool Activated = false;
 
+    /// <summary>
+    /// If this is set to false then the item can't be toggled by pressing Z.
+    /// Use another system to do it then.
+    /// </summary>
+    [DataField]
+    public bool OnUse = true;
+
     /// <summary>
     ///     Whether the item's toggle can be predicted by the client.
     /// </summary>
diff --git a/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs b/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs
new file mode 100644 (file)
index 0000000..b673c55
--- /dev/null
@@ -0,0 +1,18 @@
+using Content.Shared.Item.ItemToggle;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Item.ItemToggle.Components;
+
+/// <summary>
+/// Adds a verb for toggling something, requires <see cref="ItemToggleComponent"/>.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(ToggleVerbSystem))]
+public sealed partial class ToggleVerbComponent : Component
+{
+    /// <summary>
+    /// Text the verb will have.
+    /// Gets passed "entity" as the entity's identity string.
+    /// </summary>
+    [DataField(required: true)]
+    public LocId Text = string.Empty;
+}
similarity index 54%
rename from Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs
rename to Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
index 523f67bac3dedba06994998b2d18bdecbac1fc16..6b969d1d62b7eddae07afebfb017079da368d2a0 100644 (file)
@@ -15,12 +15,12 @@ namespace Content.Shared.Item.ItemToggle;
 /// <remarks>
 /// If you need extended functionality (e.g. requiring power) then add a new component and use events.
 /// </remarks>
-public abstract class SharedItemToggleSystem : EntitySystem
+public sealed class ItemToggleSystem : EntitySystem
 {
-    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly INetManager _netManager = default!;
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedPointLightSystem _light = default!;
-    [Dependency] private readonly INetManager _netManager = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
 
     public override void Initialize()
@@ -28,8 +28,9 @@ public abstract class SharedItemToggleSystem : EntitySystem
         base.Initialize();
 
         SubscribeLocalEvent<ItemToggleComponent, ComponentStartup>(OnStartup);
-        SubscribeLocalEvent<ItemToggleComponent, ItemUnwieldedEvent>(TurnOffonUnwielded);
-        SubscribeLocalEvent<ItemToggleComponent, ItemWieldedEvent>(TurnOnonWielded);
+        SubscribeLocalEvent<ItemToggleComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<ItemToggleComponent, ItemUnwieldedEvent>(TurnOffOnUnwielded);
+        SubscribeLocalEvent<ItemToggleComponent, ItemWieldedEvent>(TurnOnOnWielded);
         SubscribeLocalEvent<ItemToggleComponent, UseInHandEvent>(OnUseInHand);
 
         SubscribeLocalEvent<ItemToggleHotComponent, IsHotEvent>(OnIsHotEvent);
@@ -42,57 +43,76 @@ public abstract class SharedItemToggleSystem : EntitySystem
         UpdateVisuals(ent);
     }
 
-    private void OnUseInHand(EntityUid uid, ItemToggleComponent itemToggle, UseInHandEvent args)
+    private void OnMapInit(Entity<ItemToggleComponent> ent, ref MapInitEvent args)
+    {
+        if (!ent.Comp.Activated)
+            return;
+
+        var ev = new ItemToggledEvent(Predicted: ent.Comp.Predictable, Activated: ent.Comp.Activated, User: null);
+        RaiseLocalEvent(ent, ref ev);
+    }
+
+    private void OnUseInHand(Entity<ItemToggleComponent> ent, ref UseInHandEvent args)
     {
-        if (args.Handled)
+        if (args.Handled || !ent.Comp.OnUse)
             return;
 
         args.Handled = true;
 
-        Toggle(uid, args.User, predicted: itemToggle.Predictable, itemToggle: itemToggle);
+        Toggle((ent, ent.Comp), args.User, predicted: ent.Comp.Predictable);
     }
 
     /// <summary>
     /// Used when an item is attempted to be toggled.
+    /// Sets its state to the opposite of what it is.
     /// </summary>
-    public void Toggle(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
+    /// <returns>Same as <see cref="TrySetActive"/></returns>
+    public bool Toggle(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
     {
-        if (!Resolve(uid, ref itemToggle))
-            return;
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
 
-        if (itemToggle.Activated)
-        {
-            TryDeactivate(uid, user, itemToggle: itemToggle, predicted: predicted);
-        }
+        return TrySetActive(ent, !ent.Comp.Activated, user, predicted);
+    }
+
+    /// <summary>
+    /// Tries to set the activated bool from a value.
+    /// </summary>
+    /// <returns>false if the attempt fails for any reason</returns>
+    public bool TrySetActive(Entity<ItemToggleComponent?> ent, bool active, EntityUid? user = null, bool predicted = true)
+    {
+        if (active)
+            return TryActivate(ent, user, predicted: predicted);
         else
-        {
-            TryActivate(uid, user, itemToggle: itemToggle, predicted: predicted);
-        }
+            return TryDeactivate(ent, user, predicted: predicted);
     }
 
     /// <summary>
     /// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation.
     /// </summary>
-    public bool TryActivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
+    public bool TryActivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
     {
-        if (!Resolve(uid, ref itemToggle))
+        if (!Resolve(ent, ref ent.Comp))
             return false;
 
-        if (itemToggle.Activated)
+        var uid = ent.Owner;
+        var comp = ent.Comp;
+        if (comp.Activated)
             return true;
 
-        if (!itemToggle.Predictable && _netManager.IsClient)
+        if (!comp.Predictable && _netManager.IsClient)
             return true;
 
         var attempt = new ItemToggleActivateAttemptEvent(user);
         RaiseLocalEvent(uid, ref attempt);
 
+        if (!comp.Predictable) predicted = false;
         if (attempt.Cancelled)
         {
             if (predicted)
-                _audio.PlayPredicted(itemToggle.SoundFailToActivate, uid, user);
+                _audio.PlayPredicted(comp.SoundFailToActivate, uid, user);
             else
-                _audio.PlayPvs(itemToggle.SoundFailToActivate, uid);
+                _audio.PlayPvs(comp.SoundFailToActivate, uid);
 
             if (attempt.Popup != null && user != null)
             {
@@ -105,7 +125,7 @@ public abstract class SharedItemToggleSystem : EntitySystem
             return false;
         }
 
-        Activate(uid, itemToggle, predicted, user);
+        Activate((uid, comp), predicted, user);
 
         return true;
     }
@@ -113,75 +133,65 @@ public abstract class SharedItemToggleSystem : EntitySystem
     /// <summary>
     /// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation.
     /// </summary>
-    public bool TryDeactivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
+    public bool TryDeactivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
     {
-        if (!Resolve(uid, ref itemToggle))
+        if (!Resolve(ent, ref ent.Comp))
             return false;
 
-        if (!itemToggle.Predictable && _netManager.IsClient)
+        var uid = ent.Owner;
+        var comp = ent.Comp;
+        if (!comp.Activated)
             return true;
 
-        if (!itemToggle.Activated)
+        if (!comp.Predictable && _netManager.IsClient)
             return true;
 
         var attempt = new ItemToggleDeactivateAttemptEvent(user);
         RaiseLocalEvent(uid, ref attempt);
 
         if (attempt.Cancelled)
-        {
             return false;
-        }
 
-        Deactivate(uid, itemToggle, predicted, user);
+        if (!comp.Predictable) predicted = false;
+        Deactivate((uid, comp), predicted, user);
         return true;
     }
 
-    private void Activate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null)
+    private void Activate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
     {
-        // TODO: Fix this hardcoding
-        TryComp(uid, out AppearanceComponent? appearance);
-        _appearance.SetData(uid, ToggleableLightVisuals.Enabled, true, appearance);
-        _appearance.SetData(uid, ToggleVisuals.Toggled, true, appearance);
-
-        if (_light.TryGetLight(uid, out var light))
-        {
-            _light.SetEnabled(uid, true, light);
-        }
-
-        var soundToPlay = itemToggle.SoundActivate;
+        var (uid, comp) = ent;
+        var soundToPlay = comp.SoundActivate;
         if (predicted)
             _audio.PlayPredicted(soundToPlay, uid, user);
         else
             _audio.PlayPvs(soundToPlay, uid);
 
-        // END FIX HARDCODING
+        comp.Activated = true;
+        UpdateVisuals((uid, comp));
+        Dirty(uid, comp);
 
         var toggleUsed = new ItemToggledEvent(predicted, Activated: true, user);
         RaiseLocalEvent(uid, ref toggleUsed);
-
-        itemToggle.Activated = true;
-        UpdateVisuals((uid, itemToggle));
-        Dirty(uid, itemToggle);
     }
 
     /// <summary>
     /// Used to make the actual changes to the item's components on deactivation.
     /// </summary>
-    private void Deactivate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null)
+    private void Deactivate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
     {
-        var soundToPlay = itemToggle.SoundDeactivate;
+        var (uid, comp) = ent;
+        var soundToPlay = comp.SoundDeactivate;
         if (predicted)
             _audio.PlayPredicted(soundToPlay, uid, user);
         else
             _audio.PlayPvs(soundToPlay, uid);
-        // END FIX HARDCODING
+
+        comp.Activated = false;
+        UpdateVisuals((uid, comp));
+        Dirty(uid, comp);
 
         var toggleUsed = new ItemToggledEvent(predicted, Activated: false, user);
         RaiseLocalEvent(uid, ref toggleUsed);
-
-        itemToggle.Activated = false;
-        UpdateVisuals((uid, itemToggle));
-        Dirty(uid, itemToggle);
     }
 
     private void UpdateVisuals(Entity<ItemToggleComponent> ent)
@@ -204,55 +214,56 @@ public abstract class SharedItemToggleSystem : EntitySystem
     /// <summary>
     /// Used for items that require to be wielded in both hands to activate. For instance the dual energy sword will turn off if not wielded.
     /// </summary>
-    private void TurnOffonUnwielded(EntityUid uid, ItemToggleComponent itemToggle, ItemUnwieldedEvent args)
+    private void TurnOffOnUnwielded(Entity<ItemToggleComponent> ent, ref ItemUnwieldedEvent args)
     {
-        if (itemToggle.Activated)
-            TryDeactivate(uid, args.User, itemToggle: itemToggle);
+        TryDeactivate((ent, ent.Comp), args.User);
     }
 
     /// <summary>
     /// Wieldable items will automatically turn on when wielded.
     /// </summary>
-    private void TurnOnonWielded(EntityUid uid, ItemToggleComponent itemToggle, ref ItemWieldedEvent args)
+    private void TurnOnOnWielded(Entity<ItemToggleComponent> ent, ref ItemWieldedEvent args)
     {
-        if (!itemToggle.Activated)
-            TryActivate(uid, itemToggle: itemToggle);
+        // FIXME: for some reason both client and server play sound
+        TryActivate((ent, ent.Comp));
     }
 
-    public bool IsActivated(EntityUid uid, ItemToggleComponent? comp = null)
+    public bool IsActivated(Entity<ItemToggleComponent?> ent)
     {
-        if (!Resolve(uid, ref comp, false))
+        if (!Resolve(ent, ref ent.Comp, false))
             return true; // assume always activated if no component
 
-        return comp.Activated;
+        return ent.Comp.Activated;
     }
 
     /// <summary>
     /// Used to make the item hot when activated.
     /// </summary>
-    private void OnIsHotEvent(EntityUid uid, ItemToggleHotComponent itemToggleHot, IsHotEvent args)
+    private void OnIsHotEvent(Entity<ItemToggleHotComponent> ent, ref IsHotEvent args)
     {
-        args.IsHot |= IsActivated(uid);
+        args.IsHot |= IsActivated(ent.Owner);
     }
 
     /// <summary>
     /// Used to update the looping active sound linked to the entity.
     /// </summary>
-    private void UpdateActiveSound(EntityUid uid, ItemToggleActiveSoundComponent activeSound, ref ItemToggledEvent args)
+    private void UpdateActiveSound(Entity<ItemToggleActiveSoundComponent> ent, ref ItemToggledEvent args)
     {
-        if (args.Activated)
+        var (uid, comp) = ent;
+        if (!args.Activated)
         {
-            if (activeSound.ActiveSound != null && activeSound.PlayingStream == null)
-            {
-                if (args.Predicted)
-                    activeSound.PlayingStream = _audio.PlayPredicted(activeSound.ActiveSound, uid, args.User, AudioParams.Default.WithLoop(true)).Value.Entity;
-                else
-                    activeSound.PlayingStream = _audio.PlayPvs(activeSound.ActiveSound, uid, AudioParams.Default.WithLoop(true)).Value.Entity;
-            }
+            comp.PlayingStream = _audio.Stop(comp.PlayingStream);
+            return;
         }
-        else
+
+        if (comp.ActiveSound != null && comp.PlayingStream == null)
         {
-            activeSound.PlayingStream = _audio.Stop(activeSound.PlayingStream);
+            var loop = AudioParams.Default.WithLoop(true);
+            var stream = args.Predicted
+                ? _audio.PlayPredicted(comp.ActiveSound, uid, args.User, loop)
+                : _audio.PlayPvs(comp.ActiveSound, uid, loop);
+            if (stream?.Entity is {} entity)
+                comp.PlayingStream = entity;
         }
     }
 }
diff --git a/Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs b/Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs
new file mode 100644 (file)
index 0000000..858cd9b
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Shared.IdentityManagement;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Verbs;
+
+namespace Content.Shared.Item.ItemToggle;
+
+/// <summary>
+/// Adds a verb for toggling something with <see cref="ToggleVerbComponent"/>.
+/// </summary>
+public sealed class ToggleVerbSystem : EntitySystem
+{
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ToggleVerbComponent, GetVerbsEvent<ActivationVerb>>(OnGetVerbs);
+    }
+
+    private void OnGetVerbs(Entity<ToggleVerbComponent> ent, ref GetVerbsEvent<ActivationVerb> args)
+    {
+        if (!args.CanAccess || !args.CanInteract)
+            return;
+
+        var name = Identity.Entity(ent, EntityManager);
+        var user = args.User;
+        args.Verbs.Add(new ActivationVerb()
+        {
+            Text = Loc.GetString(ent.Comp.Text, ("entity", name)),
+            Act = () => _toggle.Toggle(ent.Owner, user)
+        });
+    }
+}
index 61a02187d09be615be097ae1dcef5e31fa7b2721..e4cd8077d26c0fe4d18a2d5401a046db930f018f 100644 (file)
@@ -10,16 +10,11 @@ namespace Content.Shared.Medical;
 /// <summary>
 /// This is used for defibrillators; a machine that shocks a dead
 /// person back into the world of the living.
+/// Uses <c>ItemToggleComponent</c>
 /// </summary>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentPause]
 public sealed partial class DefibrillatorComponent : Component
 {
-    /// <summary>
-    /// Whether or not it's turned on and able to be used.
-    /// </summary>
-    [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
-    public bool Enabled;
-
     /// <summary>
     /// The time at which the zap cooldown will be completed
     /// </summary>
@@ -72,15 +67,6 @@ public sealed partial class DefibrillatorComponent : Component
     [ViewVariables(VVAccess.ReadWrite), DataField("zapSound")]
     public SoundSpecifier? ZapSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_zap.ogg");
 
-    /// <summary>
-    /// The sound when the defib is powered on.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField("powerOnSound")]
-    public SoundSpecifier? PowerOnSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_safety_on.ogg");
-
-    [ViewVariables(VVAccess.ReadWrite), DataField("powerOffSound")]
-    public SoundSpecifier? PowerOffSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_safety_off.ogg");
-
     [ViewVariables(VVAccess.ReadWrite), DataField("chargeSound")]
     public SoundSpecifier? ChargeSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_charge.ogg");
 
index 55bcdd0f0a56fa402c5b4635434d8d3eabf8f647..9c39c4724ced85c4597adcf0f6106a339005a9c8 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Shared.Ninja.Systems;
 using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
 
 namespace Content.Shared.Ninja.Components;
 
@@ -7,32 +8,33 @@ namespace Content.Shared.Ninja.Components;
 /// Component for draining power from APCs/substations/SMESes, when ProviderUid is set to a battery cell.
 /// Does not rely on relay, simply being on the user and having BatteryUid set is enough.
 /// </summary>
-[RegisterComponent, Access(typeof(SharedBatteryDrainerSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedBatteryDrainerSystem))]
 public sealed partial class BatteryDrainerComponent : Component
 {
     /// <summary>
     /// The powercell entity to drain power into.
     /// Determines whether draining is possible.
     /// </summary>
-    [DataField("batteryUid"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField, AutoNetworkedField]
     public EntityUid? BatteryUid;
 
     /// <summary>
     /// Conversion rate between joules in a device and joules added to battery.
     /// Should be very low since powercells store nothing compared to even an APC.
     /// </summary>
-    [DataField("drainEfficiency"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float DrainEfficiency = 0.001f;
 
     /// <summary>
     /// Time that the do after takes to drain charge from a battery, in seconds
     /// </summary>
-    [DataField("drainTime"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float DrainTime = 1f;
 
     /// <summary>
     /// Sound played after the doafter ends.
     /// </summary>
-    [DataField("sparkSound")]
+    [DataField]
     public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks");
 }
index bf0eaec84be0925728ca61de92ae98e96b7362f0..c429eb6880e3269db5836e92a8cb810fddfef75c 100644 (file)
@@ -4,6 +4,4 @@ namespace Content.Shared.Ninja.Components;
 /// Makes this warp point a valid bombing target for ninja's spider charge.
 /// </summary>
 [RegisterComponent]
-public sealed partial class BombingTargetComponent : Component
-{
-}
+public sealed partial class BombingTargetComponent : Component;
index ba4060c703503b7b03b20cd569fffa84bb4da802..464f48f187ed3aabc2cf053e0e0bd24da5a5a033 100644 (file)
@@ -8,6 +8,7 @@ namespace Content.Shared.Ninja.Components;
 
 /// <summary>
 /// Adds an action to dash, teleport to clicked position, when this item is held.
+/// Cancel <see cref="CheckDashEvent"/> to prevent using it.
 /// </summary>
 [RegisterComponent, NetworkedComponent, Access(typeof(DashAbilitySystem)), AutoGenerateComponentState]
 public sealed partial class DashAbilityComponent : Component
@@ -16,19 +17,10 @@ public sealed partial class DashAbilityComponent : Component
     /// The action id for dashing.
     /// </summary>
     [DataField]
-    public EntProtoId DashAction = "ActionEnergyKatanaDash";
+    public EntProtoId<WorldTargetActionComponent> DashAction = "ActionEnergyKatanaDash";
 
     [DataField, AutoNetworkedField]
     public EntityUid? DashActionEntity;
-
-    /// <summary>
-    /// Sound played when using dash action.
-    /// </summary>
-    [DataField("blinkSound"), ViewVariables(VVAccess.ReadWrite)]
-    public SoundSpecifier BlinkSound = new SoundPathSpecifier("/Audio/Magic/blink.ogg")
-    {
-        Params = AudioParams.Default.WithVolume(5f)
-    };
 }
 
-public sealed partial class DashEvent : WorldTargetActionEvent { }
+public sealed partial class DashEvent : WorldTargetActionEvent;
index db7678f61d71501c62636d574a6ec9376b7af0b3..ae3e85cbe42ade57097b8a3dc489e30c25272709 100644 (file)
@@ -2,7 +2,7 @@ using Content.Shared.Ninja.Systems;
 using Content.Shared.Tag;
 using Content.Shared.Whitelist;
 using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
 
 namespace Content.Shared.Ninja.Components;
 
@@ -10,19 +10,18 @@ namespace Content.Shared.Ninja.Components;
 /// Component for emagging things on click.
 /// No charges but checks against a whitelist.
 /// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
-[Access(typeof(EmagProviderSystem))]
+[RegisterComponent, NetworkedComponent, Access(typeof(EmagProviderSystem))]
 public sealed partial class EmagProviderComponent : Component
 {
     /// <summary>
     /// The tag that marks an entity as immune to emagging.
     /// </summary>
-    [DataField("emagImmuneTag", customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>))]
-    public string EmagImmuneTag = "EmagImmune";
+    [DataField]
+    public ProtoId<TagPrototype> EmagImmuneTag = "EmagImmune";
 
     /// <summary>
     /// Whitelist that entities must be on to work.
     /// </summary>
-    [DataField("whitelist"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
-    public EntityWhitelist? Whitelist = null;
+    [DataField]
+    public EntityWhitelist? Whitelist;
 }
index 33b8fc7893391bc51034caa0acb8c9dae72f7626..84c58bb64800a2f81c09068ebf7afbee5a4e940c 100644 (file)
@@ -7,6 +7,4 @@ namespace Content.Shared.Ninja.Components;
 /// Requires a ninja with a suit for abilities to work.
 /// </summary>
 [RegisterComponent, NetworkedComponent]
-public sealed partial class EnergyKatanaComponent : Component
-{
-}
+public sealed partial class EnergyKatanaComponent : Component;
diff --git a/Content.Shared/Ninja/Components/ItemCreatorComponent.cs b/Content.Shared/Ninja/Components/ItemCreatorComponent.cs
new file mode 100644 (file)
index 0000000..d9f66d2
--- /dev/null
@@ -0,0 +1,52 @@
+using Content.Shared.Actions;
+using Content.Shared.Ninja.Systems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Ninja.Components;
+
+/// <summary>
+/// Uses battery charge to spawn an item and place it in the user's hands.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedItemCreatorSystem))]
+public sealed partial class ItemCreatorComponent : Component
+{
+    /// <summary>
+    /// The battery entity to use charge from
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid? Battery;
+
+    /// <summary>
+    /// The action id for creating an item.
+    /// </summary>
+    [DataField(required: true)]
+    public EntProtoId<InstantActionComponent> Action = string.Empty;
+
+    [DataField, AutoNetworkedField]
+    public EntityUid? ActionEntity;
+
+    /// <summary>
+    /// Battery charge used to create an item.
+    /// </summary>
+    [DataField(required: true)]
+    public float Charge = 14.4f;
+
+    /// <summary>
+    /// Item to create with the action
+    /// </summary>
+    [DataField(required: true)]
+    public EntProtoId SpawnedPrototype = string.Empty;
+
+    /// <summary>
+    /// Popup shown to the user when there isn't enough power to create an item.
+    /// </summary>
+    [DataField(required: true)]
+    public LocId NoPowerPopup = string.Empty;
+}
+
+/// <summary>
+/// Action event to use an <see cref="ItemCreator"/>.
+/// </summary>
+public sealed partial class CreateItemEvent : InstantActionEvent;
index 7b57926330b64a936128e6f1e3506c4db5eceb99..3b9e2a5e35668160edf8899e2cd7d7330867d910 100644 (file)
@@ -1,20 +1,17 @@
-using Content.Shared.DoAfter;
 using Content.Shared.Ninja.Systems;
-using Content.Shared.Toggleable;
-using Content.Shared.Whitelist;
+using Content.Shared.Objectives.Components;
 using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Robust.Shared.Utility;
 
 namespace Content.Shared.Ninja.Components;
 
 /// <summary>
 /// Component for toggling glove powers.
-/// Powers being enabled is controlled by User not being null.
 /// </summary>
+/// <remarks>
+/// Requires <c>ItemToggleComponent</c>.
+/// </remarks>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 [Access(typeof(SharedNinjaGlovesSystem))]
 public sealed partial class NinjaGlovesComponent : Component
@@ -22,24 +19,33 @@ public sealed partial class NinjaGlovesComponent : Component
     /// <summary>
     /// Entity of the ninja using these gloves, usually means enabled
     /// </summary>
-    [DataField("user"), AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public EntityUid? User;
 
     /// <summary>
-    /// The action id for toggling ninja gloves abilities
+    /// Abilities to give to the user when enabled.
     /// </summary>
-    [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string ToggleAction = "ActionToggleNinjaGloves";
+    [DataField(required: true)]
+    public List<NinjaGloveAbility> Abilities = new();
+}
 
-    [DataField, AutoNetworkedField]
-    public EntityUid? ToggleActionEntity;
+/// <summary>
+/// An ability that adds components to the user when the gloves are enabled.
+/// </summary>
+[DataRecord]
+public record struct NinjaGloveAbility()
+{
+    /// <summary>
+    /// If not null, checks if an objective with this prototype has been completed.
+    /// If it has, the ability components are skipped to prevent doing the objective twice.
+    /// The objective must have <c>CodeConditionComponent</c> to be checked.
+    /// </summary>
+    [DataField]
+    public EntProtoId<ObjectiveComponent>? Objective;
 
     /// <summary>
-    /// The whitelist used for the emag provider to emag airlocks only (not regular doors).
+    /// Components to add and remove.
     /// </summary>
-    [DataField("doorjackWhitelist")]
-    public EntityWhitelist DoorjackWhitelist = new()
-    {
-        Components = new[] {"Airlock"}
-    };
+    [DataField(required: true)]
+    public ComponentRegistry Components = new();
 }
index 7e7b1ffcd3017aae0fad04d3e90118ea55704874..8b477b2aa5f96aad227fc1e2fda3e1d409a47d7f 100644 (file)
@@ -3,9 +3,6 @@ using Content.Shared.Ninja.Systems;
 using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Ninja.Components;
@@ -14,68 +11,27 @@ namespace Content.Shared.Ninja.Components;
 /// Component for ninja suit abilities and power consumption.
 /// As an implementation detail, dashing with katana is a suit action which isn't ideal.
 /// </summary>
-[RegisterComponent, NetworkedComponent, Access(typeof(SharedNinjaSuitSystem)), AutoGenerateComponentState]
-[AutoGenerateComponentPause]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedNinjaSuitSystem))]
 public sealed partial class NinjaSuitComponent : Component
 {
-    /// <summary>
-    /// Battery charge used passively, in watts. Will last 1000 seconds on a small-capacity power cell.
-    /// </summary>
-    [DataField("passiveWattage")]
-    public float PassiveWattage = 0.36f;
-
-    /// <summary>
-    /// Battery charge used while cloaked, stacks with passive. Will last 200 seconds while cloaked on a small-capacity power cell.
-    /// </summary>
-    [DataField("cloakWattage")]
-    public float CloakWattage = 1.44f;
-
     /// <summary>
     /// Sound played when a ninja is hit while cloaked.
     /// </summary>
-    [DataField("revealSound")]
+    [DataField]
     public SoundSpecifier RevealSound = new SoundPathSpecifier("/Audio/Effects/chime.ogg");
 
     /// <summary>
-    /// How long to disable all abilities when revealed.
-    /// Normally, ninjas are revealed when attacking or getting damaged.
-    /// </summary>
-    [DataField("disableTime")]
-    public TimeSpan DisableTime = TimeSpan.FromSeconds(5);
-
-    /// <summary>
-    /// Time at which we will be able to use our abilities again
+    /// ID of the use delay to disable all ninja abilities.
     /// </summary>
-    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
-    [AutoPausedField]
-    public TimeSpan DisableCooldown;
-
-    /// <summary>
-    /// The action id for creating throwing stars.
-    /// </summary>
-    [DataField("createThrowingStarAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string CreateThrowingStarAction = "ActionCreateThrowingStar";
-
-    [DataField, AutoNetworkedField]
-    public EntityUid? CreateThrowingStarActionEntity;
-
-    /// <summary>
-    /// Battery charge used to create a throwing star. Can do it 25 times on a small-capacity power cell.
-    /// </summary>
-    [DataField("throwingStarCharge")]
-    public float ThrowingStarCharge = 14.4f;
-
-    /// <summary>
-    /// Throwing star item to create with the action
-    /// </summary>
-    [DataField("throwingStarPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string ThrowingStarPrototype = "ThrowingStarNinja";
+    [DataField]
+    public string DisableDelayId = "suit_powers";
 
     /// <summary>
     /// The action id for recalling a bound energy katana
     /// </summary>
-    [DataField("recallKatanaAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string RecallKatanaAction = "ActionRecallKatana";
+    [DataField]
+    public EntProtoId RecallKatanaAction = "ActionRecallKatana";
 
     [DataField, AutoNetworkedField]
     public EntityUid? RecallKatanaActionEntity;
@@ -84,14 +40,14 @@ public sealed partial class NinjaSuitComponent : Component
     /// Battery charge used per tile the katana teleported.
     /// Uses 1% of a default battery per tile.
     /// </summary>
-    [DataField("recallCharge")]
+    [DataField]
     public float RecallCharge = 3.6f;
 
     /// <summary>
     /// The action id for creating an EMP burst
     /// </summary>
-    [DataField("empAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string EmpAction = "ActionNinjaEmp";
+    [DataField]
+    public EntProtoId EmpAction = "ActionNinjaEmp";
 
     [DataField, AutoNetworkedField]
     public EntityUid? EmpActionEntity;
@@ -99,36 +55,29 @@ public sealed partial class NinjaSuitComponent : Component
     /// <summary>
     /// Battery charge used to create an EMP burst. Can do it 2 times on a small-capacity power cell.
     /// </summary>
-    [DataField("empCharge")]
+    [DataField]
     public float EmpCharge = 180f;
 
+    // TODO: EmpOnTrigger bruh
     /// <summary>
     /// Range of the EMP in tiles.
     /// </summary>
-    [DataField("empRange")]
+    [DataField]
     public float EmpRange = 6f;
 
     /// <summary>
     /// Power consumed from batteries by the EMP
     /// </summary>
-    [DataField("empConsumption")]
+    [DataField]
     public float EmpConsumption = 100000f;
 
     /// <summary>
     /// How long the EMP effects last for, in seconds
     /// </summary>
-    [DataField("empDuration")]
+    [DataField]
     public float EmpDuration = 60f;
 }
 
-public sealed partial class CreateThrowingStarEvent : InstantActionEvent
-{
-}
-
-public sealed partial class RecallKatanaEvent : InstantActionEvent
-{
-}
+public sealed partial class RecallKatanaEvent : InstantActionEvent;
 
-public sealed partial class NinjaEmpEvent : InstantActionEvent
-{
-}
+public sealed partial class NinjaEmpEvent : InstantActionEvent;
index 91c816df5c935cd8ca7b720d375e91e193081b0c..a19537be1c82d1edcb7a20b261f93f5898f17274 100644 (file)
@@ -7,34 +7,28 @@ namespace Content.Shared.Ninja.Components;
 
 /// <summary>
 /// Component placed on a mob to make it a space ninja, able to use suit and glove powers.
-/// Contains ids of all ninja equipment and the game rule.
+/// Contains ids of all ninja equipment.
 /// </summary>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 [Access(typeof(SharedSpaceNinjaSystem))]
 public sealed partial class SpaceNinjaComponent : Component
 {
-    /// <summary>
-    /// The ninja game rule that spawned this ninja.
-    /// </summary>
-    [DataField("rule")]
-    public EntityUid? Rule;
-
     /// <summary>
     /// Currently worn suit
     /// </summary>
-    [DataField("suit"), AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public EntityUid? Suit;
 
     /// <summary>
-    /// Currently worn gloves
+    /// Currently worn gloves, if enabled.
     /// </summary>
-    [DataField("gloves"), AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public EntityUid? Gloves;
 
     /// <summary>
     /// Bound katana, set once picked up and never removed
     /// </summary>
-    [DataField("katana"), AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public EntityUid? Katana;
 
     /// <summary>
@@ -55,6 +49,9 @@ public sealed partial class SpaceNinjaComponent : Component
     [DataField]
     public EntProtoId SpiderChargeObjective = "SpiderChargeObjective";
 
+    /// <summary>
+    /// Alert to show for suit power.
+    /// </summary>
     [DataField]
     public ProtoId<AlertPrototype> SuitPowerAlert = "SuitPower";
 }
index dacf47bb235a67b8b7bf40587283a11edf407a23..3ba4494cca43b9c776a7f013b72be7f03b733dcf 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.Ninja.Systems;
 using Robust.Shared.GameStates;
 
 namespace Content.Shared.Ninja.Components;
@@ -6,14 +7,14 @@ namespace Content.Shared.Ninja.Components;
 /// Component for the Space Ninja's unique Spider Charge.
 /// Only this component detonating can trigger the ninja's objective.
 /// </summary>
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedSpiderChargeSystem))]
 public sealed partial class SpiderChargeComponent : Component
 {
     /// Range for planting within the target area
-    [DataField("range")]
+    [DataField]
     public float Range = 10f;
 
     /// The ninja that planted this charge
-    [DataField("planter")]
-    public EntityUid? Planter = null;
+    [DataField]
+    public EntityUid? Planter;
 }
index 37a27074a49f1929a2a66a1da71662574aeefe20..2da094291d7519bd5ba3cc494200f7be5412f672 100644 (file)
@@ -3,7 +3,6 @@ using Content.Shared.Ninja.Systems;
 using Content.Shared.Whitelist;
 using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
 namespace Content.Shared.Ninja.Components;
 
@@ -11,32 +10,33 @@ namespace Content.Shared.Ninja.Components;
 /// Component for stunning mobs on click outside of harm mode.
 /// Knocks them down for a bit and deals shock damage.
 /// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStunProviderSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedStunProviderSystem))]
 public sealed partial class StunProviderComponent : Component
 {
     /// <summary>
     /// The powercell entity to take power from.
     /// Determines whether stunning is possible.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public EntityUid? BatteryUid;
 
     /// <summary>
     /// Sound played when stunning someone.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public SoundSpecifier Sound = new SoundCollectionSpecifier("sparks");
 
     /// <summary>
     /// Joules required in the battery to stun someone. Defaults to 10 uses on a small battery.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float StunCharge = 36f;
 
     /// <summary>
     /// Damage dealt when stunning someone
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public DamageSpecifier StunDamage = new()
     {
         DamageDict = new()
@@ -48,34 +48,30 @@ public sealed partial class StunProviderComponent : Component
     /// <summary>
     /// Time that someone is stunned for, stacks if done multiple times.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public TimeSpan StunTime = TimeSpan.FromSeconds(5);
 
     /// <summary>
     /// How long stunning is disabled after stunning something.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public TimeSpan Cooldown = TimeSpan.FromSeconds(2);
 
     /// <summary>
-    /// Locale string to popup when there is no power
+    /// ID of the cooldown use delay.
     /// </summary>
-    [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
-    public string NoPowerPopup = string.Empty;
+    [DataField]
+    public string DelayId = "stun_cooldown";
 
     /// <summary>
-    /// Whitelist for what counts as a mob.
+    /// Locale string to popup when there is no power
     /// </summary>
-    [DataField]
-    public EntityWhitelist Whitelist = new()
-    {
-        Components = new[] {"Stamina"}
-    };
+    [DataField(required: true)]
+    public LocId NoPowerPopup = string.Empty;
 
     /// <summary>
-    /// When someone can next be stunned.
-    /// Essentially a UseDelay unique to this component.
+    /// Whitelist for what counts as a mob.
     /// </summary>
-    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan NextStun = TimeSpan.Zero;
+    [DataField(required: true)]
+    public EntityWhitelist Whitelist = new();
 }
index 4853968b61f6d6350b0fc2c5f7fa603b7cbc0c99..1385219e4737119efd753b8adff6dc4c99478519 100644 (file)
@@ -16,6 +16,7 @@ namespace Content.Shared.Ninja.Systems;
 /// </summary>
 public sealed class DashAbilitySystem : EntitySystem
 {
+    [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedChargesSystem _charges = default!;
@@ -23,48 +24,40 @@ public sealed class DashAbilitySystem : EntitySystem
     [Dependency] private readonly ExamineSystemShared _examine = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly SharedTransformSystem _transform = default!;
-    [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
 
     public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<DashAbilityComponent, GetItemActionsEvent>(OnGetItemActions);
+        SubscribeLocalEvent<DashAbilityComponent, GetItemActionsEvent>(OnGetActions);
         SubscribeLocalEvent<DashAbilityComponent, DashEvent>(OnDash);
         SubscribeLocalEvent<DashAbilityComponent, MapInitEvent>(OnMapInit);
     }
 
-    private void OnMapInit(EntityUid uid, DashAbilityComponent component, MapInitEvent args)
+    private void OnMapInit(Entity<DashAbilityComponent> ent, ref MapInitEvent args)
     {
-        _actionContainer.EnsureAction(uid, ref component.DashActionEntity, component.DashAction);
-        Dirty(uid, component);
+        var (uid, comp) = ent;
+        _actionContainer.EnsureAction(uid, ref comp.DashActionEntity, comp.DashAction);
+        Dirty(uid, comp);
     }
 
-    private void OnGetItemActions(EntityUid uid, DashAbilityComponent comp, GetItemActionsEvent args)
+    private void OnGetActions(Entity<DashAbilityComponent> ent, ref GetItemActionsEvent args)
     {
-        var ev = new AddDashActionEvent(args.User);
-        RaiseLocalEvent(uid, ev);
-
-        if (ev.Cancelled)
-            return;
-
-        args.AddAction(ref comp.DashActionEntity, comp.DashAction);
+        if (CheckDash(ent, args.User))
+            args.AddAction(ent.Comp.DashActionEntity);
     }
 
     /// <summary>
     /// Handle charges and teleport to a visible location.
     /// </summary>
-    private void OnDash(EntityUid uid, DashAbilityComponent comp, DashEvent args)
+    private void OnDash(Entity<DashAbilityComponent> ent, ref DashEvent args)
     {
         if (!_timing.IsFirstTimePredicted)
             return;
 
+        var (uid, comp) = ent;
         var user = args.Performer;
-        args.Handled = true;
-
-        var ev = new DashAttemptEvent(user);
-        RaiseLocalEvent(uid, ev);
-        if (ev.Cancelled)
+        if (!CheckDash(uid, user))
             return;
 
         if (!_hands.IsHolding(user, uid, out var _))
@@ -73,15 +66,8 @@ public sealed class DashAbilitySystem : EntitySystem
             return;
         }
 
-        TryComp<LimitedChargesComponent>(uid, out var charges);
-        if (_charges.IsEmpty(uid, charges))
-        {
-            _popup.PopupClient(Loc.GetString("dash-ability-no-charges", ("item", uid)), user, user);
-            return;
-        }
         var origin = _transform.GetMapCoordinates(user);
         var target = args.Target.ToMap(EntityManager, _transform);
-        // prevent collision with the user duh
         if (!_examine.InRangeUnOccluded(origin, target, SharedInteractionSystem.MaxRaycastRange, null))
         {
             // can only dash if the destination is visible on screen
@@ -89,36 +75,28 @@ public sealed class DashAbilitySystem : EntitySystem
             return;
         }
 
-        _transform.SetCoordinates(user, args.Target);
-        _transform.AttachToGridOrMap(user);
-        _audio.PlayPredicted(comp.BlinkSound, user, user);
-        if (charges != null)
-            _charges.UseCharge(uid, charges);
-    }
-}
+        if (!_charges.TryUseCharge(uid))
+        {
+            _popup.PopupClient(Loc.GetString("dash-ability-no-charges", ("item", uid)), user, user);
+            return;
+        }
 
-/// <summary>
-/// Raised on the item before adding the dash action
-/// </summary>
-public sealed class AddDashActionEvent : CancellableEntityEventArgs
-{
-    public EntityUid User;
+        var xform = Transform(user);
+        _transform.SetCoordinates(user, xform, args.Target);
+        _transform.AttachToGridOrMap(user, xform);
+        args.Handled = true;
+    }
 
-    public AddDashActionEvent(EntityUid user)
+    public bool CheckDash(EntityUid uid, EntityUid user)
     {
-        User = user;
+        var ev = new CheckDashEvent(user);
+        RaiseLocalEvent(uid, ref ev);
+        return !ev.Cancelled;
     }
 }
 
 /// <summary>
-/// Raised on the item before dashing is done.
+/// Raised on the item before adding the dash action and when using the action.
 /// </summary>
-public sealed class DashAttemptEvent : CancellableEntityEventArgs
-{
-    public EntityUid User;
-
-    public DashAttemptEvent(EntityUid user)
-    {
-        User = user;
-    }
-}
+[ByRefEvent]
+public record struct CheckDashEvent(EntityUid User, bool Cancelled = false);
index 6838e7982cb0cb36ae3090286959e7d4c172ddb3..ae0bacaf5f656b036f11b6f50d137597d4aef14d 100644 (file)
@@ -1,6 +1,6 @@
 using Content.Shared.Administration.Logs;
-using Content.Shared.Emag.Systems;
 using Content.Shared.Database;
+using Content.Shared.Emag.Systems;
 using Content.Shared.Interaction;
 using Content.Shared.Ninja.Components;
 using Content.Shared.Tag;
@@ -14,10 +14,10 @@ namespace Content.Shared.Ninja.Systems;
 public sealed class EmagProviderSystem : EntitySystem
 {
     [Dependency] private readonly EmagSystem _emag = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
     [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
     [Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
-    [Dependency] private readonly TagSystem _tags = default!;
-    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+    [Dependency] private readonly TagSystem _tag = default!;
 
     public override void Initialize()
     {
@@ -29,18 +29,20 @@ public sealed class EmagProviderSystem : EntitySystem
     /// <summary>
     /// Emag clicked entities that are on the whitelist.
     /// </summary>
-    private void OnBeforeInteractHand(EntityUid uid, EmagProviderComponent comp, BeforeInteractHandEvent args)
+    private void OnBeforeInteractHand(Entity<EmagProviderComponent> ent, ref BeforeInteractHandEvent args)
     {
         // TODO: change this into a generic check event thing
-        if (args.Handled || !_gloves.AbilityCheck(uid, args, out var target))
+        if (args.Handled || !_gloves.AbilityCheck(ent, args, out var target))
             return;
 
+        var (uid, comp) = ent;
+
         // only allowed to emag entities on the whitelist
-        if (_whitelistSystem.IsWhitelistFail(comp.Whitelist, target))
+        if (_whitelist.IsWhitelistFail(comp.Whitelist, target))
             return;
 
         // only allowed to emag non-immune entities
-        if (_tags.HasTag(target, comp.EmagImmuneTag))
+        if (_tag.HasTag(target, comp.EmagImmuneTag))
             return;
 
         var handled = _emag.DoEmagEffect(uid, target);
@@ -52,18 +54,6 @@ public sealed class EmagProviderSystem : EntitySystem
         RaiseLocalEvent(uid, ref ev);
         args.Handled = true;
     }
-
-    /// <summary>
-    /// Set the whitelist for emagging something outside of yaml.
-    /// </summary>
-    public void SetWhitelist(EntityUid uid, EntityWhitelist? whitelist, EmagProviderComponent? comp = null)
-    {
-        if (!Resolve(uid, ref comp))
-            return;
-
-        comp.Whitelist = whitelist;
-        Dirty(uid, comp);
-    }
 }
 
 /// <summary>
index d427ffa39b41ab8cc98bfd27f5840ac3451a8375..281b97a648acb8576e2d9a4efdb0cef65c3e9ad7 100644 (file)
@@ -15,33 +15,20 @@ public sealed class EnergyKatanaSystem : EntitySystem
         base.Initialize();
 
         SubscribeLocalEvent<EnergyKatanaComponent, GotEquippedEvent>(OnEquipped);
-        SubscribeLocalEvent<EnergyKatanaComponent, AddDashActionEvent>(OnAddDashAction);
-        SubscribeLocalEvent<EnergyKatanaComponent, DashAttemptEvent>(OnDashAttempt);
+        SubscribeLocalEvent<EnergyKatanaComponent, CheckDashEvent>(OnCheckDash);
     }
 
     /// <summary>
     /// When equipped by a ninja, try to bind it.
     /// </summary>
-    private void OnEquipped(EntityUid uid, EnergyKatanaComponent comp, GotEquippedEvent args)
+    private void OnEquipped(Entity<EnergyKatanaComponent> ent, ref GotEquippedEvent args)
     {
-        // check if user isnt a ninja or already has a katana bound
-        var user = args.Equipee;
-        if (!TryComp<SpaceNinjaComponent>(user, out var ninja) || ninja.Katana != null)
-            return;
-
-        // bind it since its unbound
-        _ninja.BindKatana(user, uid, ninja);
-    }
-
-    private void OnAddDashAction(EntityUid uid, EnergyKatanaComponent comp, AddDashActionEvent args)
-    {
-        if (!HasComp<SpaceNinjaComponent>(args.User))
-            args.Cancel();
+        _ninja.BindKatana(args.Equipee, ent);
     }
 
-    private void OnDashAttempt(EntityUid uid, EnergyKatanaComponent comp, DashAttemptEvent args)
+    private void OnCheckDash(Entity<EnergyKatanaComponent> ent, ref CheckDashEvent args)
     {
-        if (!TryComp<SpaceNinjaComponent>(args.User, out var ninja) || ninja.Katana != uid)
-            args.Cancel();
+        if (!_ninja.IsNinja(args.User))
+            args.Cancelled = true;
     }
 }
diff --git a/Content.Shared/Ninja/Systems/ItemCreatorSystem.cs b/Content.Shared/Ninja/Systems/ItemCreatorSystem.cs
new file mode 100644 (file)
index 0000000..56112e9
--- /dev/null
@@ -0,0 +1,56 @@
+using Content.Shared.Actions;
+using Content.Shared.Ninja.Components;
+
+namespace Content.Shared.Ninja.Systems;
+
+/// <summary>
+/// Handles predicting that the action exists, creating items is done serverside.
+/// </summary>
+public abstract class SharedItemCreatorSystem : EntitySystem
+{
+    [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ItemCreatorComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<ItemCreatorComponent, GetItemActionsEvent>(OnGetActions);
+    }
+
+    private void OnMapInit(Entity<ItemCreatorComponent> ent, ref MapInitEvent args)
+    {
+        var (uid, comp) = ent;
+        // test funny dont mind me
+        if (string.IsNullOrEmpty(comp.Action))
+            return;
+
+        _actionContainer.EnsureAction(uid, ref comp.ActionEntity, comp.Action);
+        Dirty(uid, comp);
+    }
+
+    private void OnGetActions(Entity<ItemCreatorComponent> ent, ref GetItemActionsEvent args)
+    {
+        if (CheckItemCreator(ent, args.User))
+            args.AddAction(ent.Comp.ActionEntity);
+    }
+
+    public bool CheckItemCreator(EntityUid uid, EntityUid user)
+    {
+        var ev = new CheckItemCreatorEvent(user);
+        RaiseLocalEvent(uid, ref ev);
+        return !ev.Cancelled;
+    }
+}
+
+/// <summary>
+/// Raised on the item creator before adding the action.
+/// </summary>
+[ByRefEvent]
+public record struct CheckItemCreatorEvent(EntityUid User, bool Cancelled = false);
+
+/// <summary>
+/// Raised on the item creator before creating an item.
+/// </summary>
+[ByRefEvent]
+public record struct CreateItemAttemptEvent(EntityUid User, bool Cancelled = false);
index ac11063eb71b27df029344f014404e2cef3569a7..0abcca7d1bdbf8a03e3c3b590a1487a5c2cd0840 100644 (file)
@@ -18,34 +18,32 @@ public abstract class SharedBatteryDrainerSystem : EntitySystem
     }
 
     /// <summary>
-    /// Cancel any drain doafters if the battery is removed or gets filled.
+    /// Cancel any drain doafters if the battery is removed or, on the server, gets filled.
     /// </summary>
-    protected virtual void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent<DrainDoAfterEvent> args)
+    protected virtual void OnDoAfterAttempt(Entity<BatteryDrainerComponent> ent, ref DoAfterAttemptEvent<DrainDoAfterEvent> args)
     {
-        if (comp.BatteryUid == null)
-        {
+        if (ent.Comp.BatteryUid == null)
             args.Cancel();
-        }
     }
 
     /// <summary>
     /// Drain power from a power source (on server) and repeat if it succeeded.
     /// Client will predict always succeeding since power is serverside.
     /// </summary>
-    private void OnDoAfter(EntityUid uid, BatteryDrainerComponent comp, DrainDoAfterEvent args)
+    private void OnDoAfter(Entity<BatteryDrainerComponent> ent, ref DrainDoAfterEvent args)
     {
-        if (args.Cancelled || args.Handled || args.Target == null)
+        if (args.Cancelled || args.Handled || args.Target is not {} target)
             return;
 
         // repeat if there is still power to drain
-        args.Repeat = TryDrainPower(uid, comp, args.Target.Value);
+        args.Repeat = TryDrainPower(ent, target);
     }
 
     /// <summary>
     /// Attempt to drain as much power as possible into the powercell.
     /// Client always predicts this as succeeding since power is serverside and it can only fail once, when the powercell is filled or the target is emptied.
     /// </summary>
-    protected virtual bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
+    protected virtual bool TryDrainPower(Entity<BatteryDrainerComponent> ent, EntityUid target)
     {
         return true;
     }
@@ -53,12 +51,13 @@ public abstract class SharedBatteryDrainerSystem : EntitySystem
     /// <summary>
     /// Sets the battery field on the drainer.
     /// </summary>
-    public void SetBattery(EntityUid uid, EntityUid? battery, BatteryDrainerComponent? comp = null)
+    public void SetBattery(Entity<BatteryDrainerComponent?> ent, EntityUid? battery)
     {
-        if (!Resolve(uid, ref comp))
+        if (!Resolve(ent, ref ent.Comp) || ent.Comp.BatteryUid == battery)
             return;
 
-        comp.BatteryUid = battery;
+        ent.Comp.BatteryUid = battery;
+        Dirty(ent, ent.Comp);
     }
 }
 
@@ -66,4 +65,4 @@ public abstract class SharedBatteryDrainerSystem : EntitySystem
 /// DoAfter event for <see cref="BatteryDrainerComponent"/>.
 /// </summary>
 [Serializable, NetSerializable]
-public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent { }
+public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent;
index f61d0c6a9084cf0a750aa471113dc9bba562ff5c..8b892190b7b6d1f489a2fb5e6530e8d1ab0418e3 100644 (file)
@@ -1,15 +1,13 @@
-using Content.Shared.Actions;
+using Content.Shared.Clothing.Components;
 using Content.Shared.CombatMode;
-using Content.Shared.Communications;
-using Content.Shared.CriminalRecords.Components;
 using Content.Shared.Examine;
 using Content.Shared.Hands.Components;
 using Content.Shared.Interaction;
 using Content.Shared.Inventory.Events;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.Ninja.Components;
 using Content.Shared.Popups;
-using Content.Shared.Research.Components;
-using Content.Shared.Toggleable;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Ninja.Systems;
@@ -20,85 +18,105 @@ namespace Content.Shared.Ninja.Systems;
 public abstract class SharedNinjaGlovesSystem : EntitySystem
 {
     [Dependency] private readonly IGameTiming _timing = default!;
-    [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
     [Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
-    [Dependency] protected readonly SharedInteractionSystem Interaction = default!;
-    [Dependency] protected readonly SharedPopupSystem Popup = default!;
-    [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
+    [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!;
 
     public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<NinjaGlovesComponent, GetItemActionsEvent>(OnGetItemActions);
+        SubscribeLocalEvent<NinjaGlovesComponent, ToggleClothingCheckEvent>(OnToggleCheck);
+        SubscribeLocalEvent<NinjaGlovesComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
+        SubscribeLocalEvent<NinjaGlovesComponent, ItemToggledEvent>(OnToggled);
         SubscribeLocalEvent<NinjaGlovesComponent, ExaminedEvent>(OnExamined);
-        SubscribeLocalEvent<NinjaGlovesComponent, GotUnequippedEvent>(OnUnequipped);
-        SubscribeLocalEvent<NinjaGlovesComponent, MapInitEvent>(OnMapInit);
-    }
-
-    private void OnMapInit(EntityUid uid, NinjaGlovesComponent component, MapInitEvent args)
-    {
-        _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
-        Dirty(uid, component);
     }
 
     /// <summary>
     /// Disable glove abilities and show the popup if they were enabled previously.
     /// </summary>
-    public void DisableGloves(EntityUid uid, NinjaGlovesComponent? comp = null)
+    private void DisableGloves(Entity<NinjaGlovesComponent> ent)
     {
+        var (uid, comp) = ent;
+
         // already disabled?
-        if (!Resolve(uid, ref comp) || comp.User == null)
+        if (comp.User is not {} user)
             return;
 
-        var user = comp.User.Value;
         comp.User = null;
         Dirty(uid, comp);
 
-        Appearance.SetData(uid, ToggleVisuals.Toggled, false);
-        Popup.PopupClient(Loc.GetString("ninja-gloves-off"), user, user);
-
-        RemComp<BatteryDrainerComponent>(user);
-        RemComp<EmagProviderComponent>(user);
-        RemComp<StunProviderComponent>(user);
-        RemComp<ResearchStealerComponent>(user);
-        RemComp<CommsHackerComponent>(user);
-        RemComp<CriminalRecordsHackerComponent>(user);
+        foreach (var ability in comp.Abilities)
+        {
+            EntityManager.RemoveComponents(user, ability.Components);
+        }
     }
 
     /// <summary>
-    /// Adds the toggle action when equipped.
+    /// Adds the toggle action when equipped by a ninja only.
     /// </summary>
-    private void OnGetItemActions(EntityUid uid, NinjaGlovesComponent comp, GetItemActionsEvent args)
+    private void OnToggleCheck(Entity<NinjaGlovesComponent> ent, ref ToggleClothingCheckEvent args)
     {
-        if (HasComp<SpaceNinjaComponent>(args.User))
-            args.AddAction(ref comp.ToggleActionEntity, comp.ToggleAction);
+        if (!_ninja.IsNinja(args.User))
+            args.Cancelled = true;
     }
 
     /// <summary>
     /// Show if the gloves are enabled when examining.
     /// </summary>
-    private void OnExamined(EntityUid uid, NinjaGlovesComponent comp, ExaminedEvent args)
+    private void OnExamined(Entity<NinjaGlovesComponent> ent, ref ExaminedEvent args)
     {
         if (!args.IsInDetailsRange)
             return;
 
-        args.PushText(Loc.GetString(comp.User != null ? "ninja-gloves-examine-on" : "ninja-gloves-examine-off"));
+        var on = _toggle.IsActivated(ent.Owner) ? "on" : "off";
+        args.PushText(Loc.GetString($"ninja-gloves-examine-{on}"));
     }
 
-    /// <summary>
-    /// Disable gloves when unequipped and clean up ninja's gloves reference
-    /// </summary>
-    private void OnUnequipped(EntityUid uid, NinjaGlovesComponent comp, GotUnequippedEvent args)
+    private void OnActivateAttempt(Entity<NinjaGlovesComponent> ent, ref ItemToggleActivateAttemptEvent args)
     {
-        if (comp.User != null)
+        if (args.User is not {} user
+            || !_ninja.NinjaQuery.TryComp(user, out var ninja)
+            // need to wear suit to enable gloves
+            || !HasComp<NinjaSuitComponent>(ninja.Suit))
         {
-            var user = comp.User.Value;
-            Popup.PopupClient(Loc.GetString("ninja-gloves-off"), user, user);
-            DisableGloves(uid, comp);
+            args.Cancelled = true;
+            args.Popup = Loc.GetString("ninja-gloves-not-wearing-suit");
+            return;
         }
     }
 
+    private void OnToggled(Entity<NinjaGlovesComponent> ent, ref ItemToggledEvent args)
+    {
+        if ((args.User ?? ent.Comp.User) is not {} user)
+            return;
+
+        var message = Loc.GetString(args.Activated ? "ninja-gloves-on" : "ninja-gloves-off");
+        _popup.PopupClient(message, user, user);
+
+        if (args.Activated && _ninja.NinjaQuery.TryComp(user, out var ninja))
+            EnableGloves(ent, (user, ninja));
+        else
+            DisableGloves(ent);
+    }
+
+    protected virtual void EnableGloves(Entity<NinjaGlovesComponent> ent, Entity<SpaceNinjaComponent> user)
+    {
+        var (uid, comp) = ent;
+        comp.User = user;
+        Dirty(uid, comp);
+        _ninja.AssignGloves(user, uid);
+
+        // yeah this is just ComponentToggler but with objective checking
+        foreach (var ability in comp.Abilities)
+        {
+            // can't predict the objective related abilities
+            if (ability.Objective == null)
+                EntityManager.AddComponents(user, ability.Components);
+        }
+    }
 
     // TODO: generic event thing
     /// <summary>
@@ -112,6 +130,6 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem
             && !_combatMode.IsInCombatMode(uid)
             && TryComp<HandsComponent>(uid, out var hands)
             && hands.ActiveHandEntity == null
-            && Interaction.InRangeUnobstructed(uid, target);
+            && _interaction.InRangeUnobstructed(uid, target);
     }
 }
index fed41eaed8afd7da46ba173eaa16ac83ba473031..3800d15b2675f8c265005700da056b3142309df9 100644 (file)
@@ -1,11 +1,14 @@
 using Content.Shared.Actions;
+using Content.Shared.Clothing;
 using Content.Shared.Clothing.Components;
 using Content.Shared.Clothing.EntitySystems;
 using Content.Shared.Inventory.Events;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.Ninja.Components;
 using Content.Shared.Popups;
+using Content.Shared.Timing;
 using Robust.Shared.Audio.Systems;
-using Robust.Shared.Timing;
 
 namespace Content.Shared.Ninja.Systems;
 
@@ -14,137 +17,158 @@ namespace Content.Shared.Ninja.Systems;
 /// </summary>
 public abstract class SharedNinjaSuitSystem : EntitySystem
 {
-    [Dependency] protected readonly IGameTiming GameTiming = default!;
-    [Dependency] private readonly SharedAudioSystem _audio = default!;
-    [Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
-    [Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!;
     [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
     [Dependency] protected readonly SharedPopupSystem Popup = default!;
-    [Dependency] protected readonly StealthClothingSystem StealthClothing = default!;
+    [Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!;
+    [Dependency] private readonly UseDelaySystem _useDelay = default!;
 
     public override void Initialize()
     {
         base.Initialize();
 
         SubscribeLocalEvent<NinjaSuitComponent, MapInitEvent>(OnMapInit);
-
-        SubscribeLocalEvent<NinjaSuitComponent, GotEquippedEvent>(OnEquipped);
+        SubscribeLocalEvent<NinjaSuitComponent, ClothingGotEquippedEvent>(OnEquipped);
         SubscribeLocalEvent<NinjaSuitComponent, GetItemActionsEvent>(OnGetItemActions);
-        SubscribeLocalEvent<NinjaSuitComponent, AddStealthActionEvent>(OnAddStealthAction);
+        SubscribeLocalEvent<NinjaSuitComponent, ToggleClothingCheckEvent>(OnCloakCheck);
+        SubscribeLocalEvent<NinjaSuitComponent, CheckItemCreatorEvent>(OnStarCheck);
+        SubscribeLocalEvent<NinjaSuitComponent, CreateItemAttemptEvent>(OnCreateStarAttempt);
+        SubscribeLocalEvent<NinjaSuitComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
         SubscribeLocalEvent<NinjaSuitComponent, GotUnequippedEvent>(OnUnequipped);
     }
 
-    private void OnMapInit(EntityUid uid, NinjaSuitComponent component, MapInitEvent args)
+    private void OnEquipped(Entity<NinjaSuitComponent> ent, ref ClothingGotEquippedEvent args)
     {
-        _actionContainer.EnsureAction(uid, ref component.RecallKatanaActionEntity, component.RecallKatanaAction);
-        _actionContainer.EnsureAction(uid, ref component.CreateThrowingStarActionEntity, component.CreateThrowingStarAction);
-        _actionContainer.EnsureAction(uid, ref component.EmpActionEntity, component.EmpAction);
-        Dirty(uid, component);
+        var user = args.Wearer;
+        if (_ninja.NinjaQuery.TryComp(user, out var ninja))
+            NinjaEquipped(ent, (user, ninja));
     }
 
-    /// <summary>
-    /// Call the shared and serverside code for when a ninja equips the suit.
-    /// </summary>
-    private void OnEquipped(EntityUid uid, NinjaSuitComponent comp, GotEquippedEvent args)
+    protected virtual void NinjaEquipped(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
     {
-        var user = args.Equipee;
-        if (!TryComp<SpaceNinjaComponent>(user, out var ninja))
-            return;
+        // mark the user as wearing this suit, used when being attacked among other things
+        _ninja.AssignSuit(user, ent);
+    }
 
-        NinjaEquippedSuit(uid, comp, user, ninja);
+    private void OnMapInit(Entity<NinjaSuitComponent> ent, ref MapInitEvent args)
+    {
+        var (uid, comp) = ent;
+        _actionContainer.EnsureAction(uid, ref comp.RecallKatanaActionEntity, comp.RecallKatanaAction);
+        _actionContainer.EnsureAction(uid, ref comp.EmpActionEntity, comp.EmpAction);
+        Dirty(uid, comp);
     }
 
     /// <summary>
     /// Add all the actions when a suit is equipped by a ninja.
     /// </summary>
-    private void OnGetItemActions(EntityUid uid, NinjaSuitComponent comp, GetItemActionsEvent args)
+    private void OnGetItemActions(Entity<NinjaSuitComponent> ent, ref GetItemActionsEvent args)
     {
-        if (!HasComp<SpaceNinjaComponent>(args.User))
+        if (!_ninja.IsNinja(args.User))
             return;
 
+        var comp = ent.Comp;
         args.AddAction(ref comp.RecallKatanaActionEntity, comp.RecallKatanaAction);
-        args.AddAction(ref comp.CreateThrowingStarActionEntity, comp.CreateThrowingStarAction);
         args.AddAction(ref comp.EmpActionEntity, comp.EmpAction);
     }
 
     /// <summary>
-    /// Only add stealth clothing's toggle action when equipped by a ninja.
+    /// Only add toggle cloak action when equipped by a ninja.
     /// </summary>
-    private void OnAddStealthAction(EntityUid uid, NinjaSuitComponent comp, AddStealthActionEvent args)
+    private void OnCloakCheck(Entity<NinjaSuitComponent> ent, ref ToggleClothingCheckEvent args)
     {
-        if (!HasComp<SpaceNinjaComponent>(args.User))
-            args.Cancel();
+        if (!_ninja.IsNinja(args.User))
+            args.Cancelled = true;
     }
 
-    /// <summary>
-    /// Call the shared and serverside code for when anyone unequips a suit.
-    /// </summary>
-    private void OnUnequipped(EntityUid uid, NinjaSuitComponent comp, GotUnequippedEvent args)
+    private void OnStarCheck(Entity<NinjaSuitComponent> ent, ref CheckItemCreatorEvent args)
+    {
+        if (!_ninja.IsNinja(args.User))
+            args.Cancelled = true;
+    }
+
+    private void OnCreateStarAttempt(Entity<NinjaSuitComponent> ent, ref CreateItemAttemptEvent args)
     {
-        UserUnequippedSuit(uid, comp, args.Equipee);
+        if (CheckDisabled(ent, args.User))
+            args.Cancelled = true;
     }
 
     /// <summary>
-    /// Called when a suit is equipped by a space ninja.
-    /// In the future it might be changed to an explicit activation toggle/verb like gloves are.
+    /// Call the shared and serverside code for when anyone unequips a suit.
     /// </summary>
-    protected virtual void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
+    private void OnUnequipped(Entity<NinjaSuitComponent> ent, ref GotUnequippedEvent args)
     {
-        // mark the user as wearing this suit, used when being attacked among other things
-        _ninja.AssignSuit(user, uid, ninja);
-
-        // initialize phase cloak, but keep it off
-        StealthClothing.SetEnabled(uid, user, false);
+        var user = args.Equipee;
+        if (_ninja.NinjaQuery.TryComp(user, out var ninja))
+            UserUnequippedSuit(ent, (user, ninja));
     }
 
     /// <summary>
     /// Force uncloaks the user and disables suit abilities.
     /// </summary>
-    public void RevealNinja(EntityUid uid, EntityUid user, bool disable = true, NinjaSuitComponent? comp = null, StealthClothingComponent? stealthClothing = null)
+    public void RevealNinja(Entity<NinjaSuitComponent?> ent, EntityUid user, bool disable = true)
     {
-        if (!Resolve(uid, ref comp, ref stealthClothing))
+        if (!Resolve(ent, ref ent.Comp))
             return;
 
-        if (!StealthClothing.SetEnabled(uid, user, false, stealthClothing))
-            return;
-
-        if (!disable)
+        var uid = ent.Owner;
+        var comp = ent.Comp;
+        if (_toggle.TryDeactivate(uid, user) || !disable)
             return;
 
         // previously cloaked, disable abilities for a short time
         _audio.PlayPredicted(comp.RevealSound, uid, user);
         Popup.PopupClient(Loc.GetString("ninja-revealed"), user, user, PopupType.MediumCaution);
-        comp.DisableCooldown = GameTiming.CurTime + comp.DisableTime;
+        _useDelay.TryResetDelay(uid, id: comp.DisableDelayId);
+    }
+
+    private void OnActivateAttempt(Entity<NinjaSuitComponent> ent, ref ItemToggleActivateAttemptEvent args)
+    {
+        if (!_ninja.IsNinja(args.User))
+        {
+            args.Cancelled = true;
+            return;
+        }
+
+        if (IsDisabled((ent, ent.Comp, null)))
+        {
+            args.Cancelled = true;
+            args.Popup = Loc.GetString("ninja-suit-cooldown");
+        }
     }
 
-    // TODO: modify PowerCellDrain
     /// <summary>
-    /// Returns the power used by a suit
+    /// Returns true if the suit is currently disabled
     /// </summary>
-    public float SuitWattage(EntityUid uid, NinjaSuitComponent? suit = null)
+    public bool IsDisabled(Entity<NinjaSuitComponent?, UseDelayComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2))
+            return false;
+
+        return _useDelay.IsDelayed((ent, ent.Comp2), ent.Comp1.DisableDelayId);
+    }
+
+    protected bool CheckDisabled(Entity<NinjaSuitComponent> ent, EntityUid user)
     {
-        if (!Resolve(uid, ref suit))
-            return 0f;
+        if (IsDisabled((ent, ent.Comp, null)))
+        {
+            Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
+            return true;
+        }
 
-        float wattage = suit.PassiveWattage;
-        if (TryComp<StealthClothingComponent>(uid, out var stealthClothing) && stealthClothing.Enabled)
-            wattage += suit.CloakWattage;
-        return wattage;
+        return false;
     }
 
     /// <summary>
     /// Called when a suit is unequipped, not necessarily by a space ninja.
     /// In the future it might be changed to also have explicit deactivation via toggle.
     /// </summary>
-    protected virtual void UserUnequippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user)
+    protected virtual void UserUnequippedSuit(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
     {
-        if (!TryComp<SpaceNinjaComponent>(user, out var ninja))
-            return;
-
         // mark the user as not wearing a suit
-        _ninja.AssignSuit(user, null, ninja);
+        _ninja.AssignSuit(user, null);
         // disable glove abilities
-        if (ninja.Gloves != null && TryComp<NinjaGlovesComponent>(ninja.Gloves.Value, out var gloves))
-            _gloves.DisableGloves(ninja.Gloves.Value, gloves);
+        if (user.Comp.Gloves is {} uid)
+            _toggle.TryDeactivate(uid, user: user);
     }
 }
index 522f29fe420f71f0303b80a3161222114a444eb5..d738f2dd8a216183111ed86d58fcde0164863521 100644 (file)
@@ -3,6 +3,7 @@ using Content.Shared.Ninja.Components;
 using Content.Shared.Weapons.Melee.Events;
 using Content.Shared.Weapons.Ranged.Events;
 using Content.Shared.Popups;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Content.Shared.Ninja.Systems;
 
@@ -14,49 +15,59 @@ public abstract class SharedSpaceNinjaSystem : EntitySystem
     [Dependency] protected readonly SharedNinjaSuitSystem Suit = default!;
     [Dependency] protected readonly SharedPopupSystem Popup = default!;
 
+    public EntityQuery<SpaceNinjaComponent> NinjaQuery;
+
     public override void Initialize()
     {
         base.Initialize();
 
+        NinjaQuery = GetEntityQuery<SpaceNinjaComponent>();
+
         SubscribeLocalEvent<SpaceNinjaComponent, AttackedEvent>(OnNinjaAttacked);
         SubscribeLocalEvent<SpaceNinjaComponent, MeleeAttackEvent>(OnNinjaAttack);
         SubscribeLocalEvent<SpaceNinjaComponent, ShotAttemptedEvent>(OnShotAttempted);
     }
 
+    public bool IsNinja([NotNullWhen(true)] EntityUid? uid)
+    {
+        return NinjaQuery.HasComp(uid);
+    }
+
     /// <summary>
     /// Set the ninja's worn suit entity
     /// </summary>
-    public void AssignSuit(EntityUid uid, EntityUid? suit, SpaceNinjaComponent? comp = null)
+    public void AssignSuit(Entity<SpaceNinjaComponent> ent, EntityUid? suit)
     {
-        if (!Resolve(uid, ref comp) || comp.Suit == suit)
+        if (ent.Comp.Suit == suit)
             return;
 
-        comp.Suit = suit;
-        Dirty(uid, comp);
+        ent.Comp.Suit = suit;
+        Dirty(ent, ent.Comp);
     }
 
     /// <summary>
     /// Set the ninja's worn gloves entity
     /// </summary>
-    public void AssignGloves(EntityUid uid, EntityUid? gloves, SpaceNinjaComponent? comp = null)
+    public void AssignGloves(Entity<SpaceNinjaComponent> ent, EntityUid? gloves)
     {
-        if (!Resolve(uid, ref comp) || comp.Gloves == gloves)
+        if (ent.Comp.Gloves == gloves)
             return;
 
-        comp.Gloves = gloves;
-        Dirty(uid, comp);
+        ent.Comp.Gloves = gloves;
+        Dirty(ent, ent.Comp);
     }
 
     /// <summary>
     /// Bind a katana entity to a ninja, letting it be recalled and dash.
+    /// Does nothing if the player is not a ninja or already has a katana bound.
     /// </summary>
-    public void BindKatana(EntityUid uid, EntityUid? katana, SpaceNinjaComponent? comp = null)
+    public void BindKatana(Entity<SpaceNinjaComponent?> ent, EntityUid katana)
     {
-        if (!Resolve(uid, ref comp) || comp.Katana == katana)
+        if (!NinjaQuery.Resolve(ent, ref ent.Comp) || ent.Comp.Katana != null)
             return;
 
-        comp.Katana = katana;
-        Dirty(uid, comp);
+        ent.Comp.Katana = katana;
+        Dirty(ent, ent.Comp);
     }
 
     /// <summary>
@@ -71,32 +82,32 @@ public abstract class SharedSpaceNinjaSystem : EntitySystem
     /// <summary>
     /// Handle revealing ninja if cloaked when attacked.
     /// </summary>
-    private void OnNinjaAttacked(EntityUid uid, SpaceNinjaComponent comp, AttackedEvent args)
+    private void OnNinjaAttacked(Entity<SpaceNinjaComponent> ent, ref AttackedEvent args)
     {
-        if (comp.Suit != null && TryComp<StealthClothingComponent>(comp.Suit, out var stealthClothing) && stealthClothing.Enabled)
-        {
-            Suit.RevealNinja(comp.Suit.Value, uid, true, null, stealthClothing);
-        }
+        TryRevealNinja(ent, disable: true);
     }
 
     /// <summary>
     /// Handle revealing ninja if cloaked when attacking.
     /// Only reveals, there is no cooldown.
     /// </summary>
-    private void OnNinjaAttack(EntityUid uid, SpaceNinjaComponent comp, ref MeleeAttackEvent args)
+    private void OnNinjaAttack(Entity<SpaceNinjaComponent> ent, ref MeleeAttackEvent args)
+    {
+        TryRevealNinja(ent, disable: false);
+    }
+
+    private void TryRevealNinja(Entity<SpaceNinjaComponent> ent, bool disable)
     {
-        if (comp.Suit != null && TryComp<StealthClothingComponent>(comp.Suit, out var stealthClothing) && stealthClothing.Enabled)
-        {
-            Suit.RevealNinja(comp.Suit.Value, uid, false, null, stealthClothing);
-        }
+        if (ent.Comp.Suit is {} uid && TryComp<NinjaSuitComponent>(ent.Comp.Suit, out var suit))
+            Suit.RevealNinja((uid, suit), ent, disable: disable);
     }
 
     /// <summary>
     /// Require ninja to fight with HONOR, no guns!
     /// </summary>
-    private void OnShotAttempted(EntityUid uid, SpaceNinjaComponent comp, ref ShotAttemptedEvent args)
+    private void OnShotAttempted(Entity<SpaceNinjaComponent> ent, ref ShotAttemptedEvent args)
     {
-        Popup.PopupClient(Loc.GetString("gun-disabled"), uid, uid);
+        Popup.PopupClient(Loc.GetString("gun-disabled"), ent, ent);
         args.Cancel();
     }
 }
diff --git a/Content.Shared/Ninja/Systems/SharedSpiderChargeSystem.cs b/Content.Shared/Ninja/Systems/SharedSpiderChargeSystem.cs
new file mode 100644 (file)
index 0000000..f4b158a
--- /dev/null
@@ -0,0 +1,6 @@
+namespace Content.Shared.Ninja.Systems;
+
+/// <summary>
+/// Sticking triggering and exploding are all in server so this is just for access.
+/// </summary>
+public abstract class SharedSpiderChargeSystem : EntitySystem;
index 61b6e4313ed5aad24b6ee424b1cb94bacdc66639..061c019c9b6e535dff2621d0ecf1f097fc742f05 100644 (file)
@@ -11,22 +11,12 @@ public abstract class SharedStunProviderSystem : EntitySystem
     /// <summary>
     /// Set the battery field on the stun provider.
     /// </summary>
-    public void SetBattery(EntityUid uid, EntityUid? battery, StunProviderComponent? comp = null)
+    public void SetBattery(Entity<StunProviderComponent?> ent, EntityUid? battery)
     {
-        if (!Resolve(uid, ref comp))
+        if (!Resolve(ent, ref ent.Comp) || ent.Comp.BatteryUid == battery)
             return;
 
-        comp.BatteryUid = battery;
-    }
-
-    /// <summary>
-    /// Set the no power popup field on the stun provider.
-    /// </summary>
-    public void SetNoPowerPopup(EntityUid uid, string popup, StunProviderComponent? comp = null)
-    {
-        if (!Resolve(uid, ref comp))
-            return;
-
-        comp.NoPowerPopup = popup;
+        ent.Comp.BatteryUid = battery;
+        Dirty(ent, ent.Comp);
     }
 }
index 07032a00ce988e7f3b88def3f01b914124924195..8d2c4dcfebe2887667126b4c850b218572c9d51e 100644 (file)
@@ -92,7 +92,7 @@ public abstract class SharedObjectivesSystem : EntitySystem
     }
 
     /// <summary>
-    /// Get the title, description, icon and progress of an objective using <see cref="ObjectiveGetInfoEvent"/>.
+    /// Get the title, description, icon and progress of an objective using <see cref="ObjectiveGetProgressEvent"/>.
     /// If any of them are null it is logged and null is returned.
     /// </summary>
     /// <param name="uid"/>ID of the condition entity</param>
@@ -103,20 +103,43 @@ public abstract class SharedObjectivesSystem : EntitySystem
         if (!Resolve(mindId, ref mind))
             return null;
 
-        var ev = new ObjectiveGetProgressEvent(mindId, mind);
-        RaiseLocalEvent(uid, ref ev);
+        if (GetProgress(uid, (mindId, mind)) is not {} progress)
+            return null;
 
         var comp = Comp<ObjectiveComponent>(uid);
         var meta = MetaData(uid);
         var title = meta.EntityName;
         var description = meta.EntityDescription;
-        if (comp.Icon == null || ev.Progress == null)
+        if (comp.Icon == null)
         {
-            Log.Error($"An objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind)} is missing icon or progress ({ev.Progress})");
+            Log.Error($"An objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind)} is missing an icon!");
             return null;
         }
 
-        return new ObjectiveInfo(title, description, comp.Icon, ev.Progress.Value);
+        return new ObjectiveInfo(title, description, comp.Icon, progress);
+    }
+
+    /// <summary>
+    /// Gets the progress of an objective using <see cref="ObjectiveGetProgressEvent"/>.
+    /// Returning null is a programmer error.
+    /// </summary>
+    public float? GetProgress(EntityUid uid, Entity<MindComponent> mind)
+    {
+        var ev = new ObjectiveGetProgressEvent(mind, mind.Comp);
+        RaiseLocalEvent(uid, ref ev);
+        if (ev.Progress != null)
+            return ev.Progress;
+
+        Log.Error($"Objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind.Comp)} didn't set a progress value!");
+        return null;
+    }
+
+    /// <summary>
+    /// Returns true if an objective is completed.
+    /// </summary>
+    public bool IsCompleted(EntityUid uid, Entity<MindComponent> mind)
+    {
+        return (GetProgress(uid, mind) ?? 0f) >= 0.999f;
     }
 
     /// <summary>
diff --git a/Content.Shared/Pinpointer/SharedProximityBeeper.cs b/Content.Shared/Pinpointer/SharedProximityBeeper.cs
deleted file mode 100644 (file)
index 5163112..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Pinpointer;
-
-[Serializable, NetSerializable]
-public enum ProximityBeeperVisuals : byte
-{
-    Enabled
-}
index 708a86a8eaf9d2e630d9b93a2fb167e3b7433561..94de7c77878375261f1d2c08860877f31c010d51 100644 (file)
@@ -6,6 +6,10 @@ namespace Content.Shared.PowerCell;
 /// <summary>
 /// Indicates that the entity's ActivatableUI requires power or else it closes.
 /// </summary>
+/// <remarks>
+/// With ActivatableUI it will activate and deactivate when the ui is opened and closed, drawing power inbetween.
+/// Requires <see cref="ItemToggleComponent"/> to work.
+/// </remarks>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
 public sealed partial class PowerCellDrawComponent : Component
 {
@@ -26,10 +30,12 @@ public sealed partial class PowerCellDrawComponent : Component
     #endregion
 
     /// <summary>
-    /// Is this power cell currently drawing power every tick.
+    /// Whether drawing is enabled, regardless of ItemToggle.
+    /// Having no cell will still disable it.
+    /// Only use this if you really don't want it to use power for some time.
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField("enabled")]
-    public bool Drawing;
+    [DataField, AutoNetworkedField]
+    public bool Enabled = true;
 
     /// <summary>
     /// How much the entity draws while the UI is open.
@@ -51,4 +57,10 @@ public sealed partial class PowerCellDrawComponent : Component
     [DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
     [AutoPausedField]
     public TimeSpan NextUpdateTime;
+
+    /// <summary>
+    /// How long to wait between power drawing.
+    /// </summary>
+    [DataField]
+    public TimeSpan Delay = TimeSpan.FromSeconds(1);
 }
index 508bfc85f08e7b5fa4ec8979243e57a573e620c2..2b2a836633cc1e1ed8cb3041f94795bf6ef8eafc 100644 (file)
@@ -1,4 +1,6 @@
 using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.PowerCell.Components;
 using Content.Shared.Rejuvenate;
 using Robust.Shared.Containers;
@@ -11,14 +13,19 @@ public abstract class SharedPowerCellSystem : EntitySystem
     [Dependency] protected readonly IGameTiming Timing = default!;
     [Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] protected readonly ItemToggleSystem Toggle = default!;
 
     public override void Initialize()
     {
         base.Initialize();
+
         SubscribeLocalEvent<PowerCellSlotComponent, RejuvenateEvent>(OnRejuvenate);
         SubscribeLocalEvent<PowerCellSlotComponent, EntInsertedIntoContainerMessage>(OnCellInserted);
         SubscribeLocalEvent<PowerCellSlotComponent, EntRemovedFromContainerMessage>(OnCellRemoved);
         SubscribeLocalEvent<PowerCellSlotComponent, ContainerIsInsertingAttemptEvent>(OnCellInsertAttempt);
+
+        SubscribeLocalEvent<PowerCellDrawComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
+        SubscribeLocalEvent<PowerCellDrawComponent, ItemToggledEvent>(OnToggled);
     }
 
     private void OnRejuvenate(EntityUid uid, PowerCellSlotComponent component, RejuvenateEvent args)
@@ -63,13 +70,25 @@ public abstract class SharedPowerCellSystem : EntitySystem
         RaiseLocalEvent(uid, new PowerCellChangedEvent(true), false);
     }
 
-    public void SetPowerCellDrawEnabled(EntityUid uid, bool enabled, PowerCellDrawComponent? component = null)
+    private void OnActivateAttempt(Entity<PowerCellDrawComponent> ent, ref ItemToggleActivateAttemptEvent args)
+    {
+        if (!HasDrawCharge(ent, ent.Comp, user: args.User)
+            || !HasActivatableCharge(ent, ent.Comp, user: args.User))
+            args.Cancelled = true;
+    }
+
+    private void OnToggled(Entity<PowerCellDrawComponent> ent, ref ItemToggledEvent args)
+    {
+        ent.Comp.NextUpdateTime = Timing.CurTime;
+    }
+
+    public void SetDrawEnabled(Entity<PowerCellDrawComponent?> ent, bool enabled)
     {
-        if (!Resolve(uid, ref component, false) || enabled == component.Drawing)
+        if (!Resolve(ent, ref ent.Comp, false) || ent.Comp.Enabled == enabled)
             return;
 
-        component.Drawing = enabled;
-        component.NextUpdateTime = Timing.CurTime;
+        ent.Comp.Enabled = enabled;
+        Dirty(ent, ent.Comp);
     }
 
     /// <summary>
index 09cb7f06d5d9ca357b389dcf59fe2a07d3d8019f..7e2bb4dfe620f14dca77e892520559e8b86349e8 100644 (file)
@@ -10,12 +10,6 @@ namespace Content.Shared.ProximityDetection.Components;
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState ,Access(typeof(ProximityDetectionSystem))]
 public sealed partial class ProximityDetectorComponent : Component
 {
-    /// <summary>
-    /// Whether or not it's on.
-    /// </summary>
-    [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
-    public bool Enabled = true;
-
     /// <summary>
     /// The criteria used to filter entities
     /// Note: RequireAll is only supported for tags, all components are required to count as a match!
@@ -35,13 +29,13 @@ public sealed partial class ProximityDetectorComponent : Component
     [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
     public FixedPoint2 Distance = -1;
 
-
     /// <summary>
     /// The farthest distance to search for targets
     /// </summary>
     [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
     public FixedPoint2 Range = 10f;
 
+    // TODO: use timespans not this
     public float AccumulatedFrameTime;
 
     [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
index db25e8bc511731b7fc8fdde0c0e92e59a7998065..df302f94771e3d75619faa295207d91177e1aa0f 100644 (file)
@@ -1,4 +1,6 @@
-using Content.Shared.ProximityDetection.Components;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.ProximityDetection.Components;
 using Content.Shared.Tag;
 using Robust.Shared.Network;
 
@@ -9,6 +11,7 @@ namespace Content.Shared.ProximityDetection.Systems;
 public sealed class ProximityDetectionSystem : EntitySystem
 {
     [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
     [Dependency] private readonly SharedTransformSystem _transform = default!;
     [Dependency] private readonly TagSystem _tagSystem = default!;
     [Dependency] private readonly INetManager _net = default!;
@@ -17,10 +20,10 @@ public sealed class ProximityDetectionSystem : EntitySystem
 
     public override void Initialize()
     {
-        SubscribeLocalEvent<ProximityDetectorComponent, EntityPausedEvent>(OnPaused);
-        SubscribeLocalEvent<ProximityDetectorComponent, EntityUnpausedEvent>(OnUnpaused);
-        SubscribeLocalEvent<ProximityDetectorComponent, ComponentInit>(OnCompInit);
+        base.Initialize();
 
+        SubscribeLocalEvent<ProximityDetectorComponent, ComponentInit>(OnCompInit);
+        SubscribeLocalEvent<ProximityDetectorComponent, ItemToggledEvent>(OnToggled);
     }
 
     private void OnCompInit(EntityUid uid, ProximityDetectorComponent component, ComponentInit args)
@@ -30,57 +33,39 @@ public sealed class ProximityDetectionSystem : EntitySystem
         Log.Debug("DetectorComponent only supports requireAll = false for tags. All components are required for a match!");
     }
 
-    private void OnPaused(EntityUid owner, ProximityDetectorComponent component, EntityPausedEvent args)
-    {
-        SetEnable_Internal(owner,component,false);
-    }
-
-    private void OnUnpaused(EntityUid owner, ProximityDetectorComponent detector, ref EntityUnpausedEvent args)
-    {
-        SetEnable_Internal(owner, detector,true);
-    }
-    public void SetEnable(EntityUid owner, bool enabled, ProximityDetectorComponent? detector = null)
-    {
-        if (!Resolve(owner, ref detector) || detector.Enabled == enabled)
-            return;
-        SetEnable_Internal(owner ,detector, enabled);
-    }
-
     public override void Update(float frameTime)
     {
         if (_net.IsClient)
             return;
+
         var query = EntityQueryEnumerator<ProximityDetectorComponent>();
         while (query.MoveNext(out var owner, out var detector))
         {
-            if (!detector.Enabled)
+            if (!_toggle.IsActivated(owner))
                 continue;
+
             detector.AccumulatedFrameTime += frameTime;
             if (detector.AccumulatedFrameTime < detector.UpdateRate)
                 continue;
+
             detector.AccumulatedFrameTime -= detector.UpdateRate;
             RunUpdate_Internal(owner, detector);
         }
     }
 
-    public bool GetEnable(EntityUid owner, ProximityDetectorComponent? detector = null)
+    private void OnToggled(Entity<ProximityDetectorComponent> ent, ref ItemToggledEvent args)
     {
-        return Resolve(owner, ref detector, false) && detector.Enabled;
-    }
-
-    private void SetEnable_Internal(EntityUid owner,ProximityDetectorComponent detector, bool enabled)
-    {
-        detector.Enabled = enabled;
-        var noDetectEvent = new ProximityTargetUpdatedEvent(detector, detector.TargetEnt, detector.Distance);
-        RaiseLocalEvent(owner, ref noDetectEvent);
-        if (!enabled)
+        if (args.Activated)
         {
-            detector.AccumulatedFrameTime = 0;
-            RunUpdate_Internal(owner, detector);
-            Dirty(owner, detector);
+            RunUpdate_Internal(ent, ent.Comp);
             return;
         }
-        RunUpdate_Internal(owner, detector);
+
+        var noDetectEvent = new ProximityTargetUpdatedEvent(ent.Comp, Target: null, ent.Comp.Distance);
+        RaiseLocalEvent(ent, ref noDetectEvent);
+
+        ent.Comp.AccumulatedFrameTime = 0;
+        Dirty(ent, ent.Comp);
     }
 
     public void ForceUpdate(EntityUid owner, ProximityDetectorComponent? detector = null)
@@ -90,11 +75,31 @@ public sealed class ProximityDetectionSystem : EntitySystem
         RunUpdate_Internal(owner, detector);
     }
 
+    private void ClearTarget(Entity<ProximityDetectorComponent> ent)
+    {
+        var (uid, comp) = ent;
+        if (comp.TargetEnt == null)
+            return;
+
+        comp.Distance = -1;
+        comp.TargetEnt = null;
+        var noDetectEvent = new ProximityTargetUpdatedEvent(comp, null, -1);
+        RaiseLocalEvent(uid, ref noDetectEvent);
+        var newTargetEvent = new NewProximityTargetEvent(comp, null);
+        RaiseLocalEvent(uid, ref newTargetEvent);
+        Dirty(uid, comp);
+    }
 
     private void RunUpdate_Internal(EntityUid owner,ProximityDetectorComponent detector)
     {
         if (!_net.IsServer) //only run detection checks on the server!
             return;
+
+        if (Deleted(detector.TargetEnt))
+        {
+            ClearTarget((owner, detector));
+        }
+
         var xformQuery = GetEntityQuery<TransformComponent>();
         var xform = xformQuery.GetComponent(owner);
         List<(EntityUid TargetEnt, float Distance)> detections = new();
@@ -173,15 +178,7 @@ public sealed class ProximityDetectionSystem : EntitySystem
     {
         if (detections.Count == 0)
         {
-            if (detector.TargetEnt == null)
-                return;
-            detector.Distance = -1;
-            detector.TargetEnt = null;
-            var noDetectEvent = new ProximityTargetUpdatedEvent(detector, null, -1);
-            RaiseLocalEvent(owner, ref noDetectEvent);
-            var newTargetEvent = new NewProximityTargetEvent(detector, null);
-            RaiseLocalEvent(owner, ref newTargetEvent);
-            Dirty(owner, detector);
+            ClearTarget((owner, detector));
             return;
         }
         var closestDistance = detections[0].Distance;
@@ -198,6 +195,7 @@ public sealed class ProximityDetectionSystem : EntitySystem
         var newData = newTarget || detector.Distance != closestDistance;
         detector.TargetEnt = closestEnt;
         detector.Distance = closestDistance;
+        Dirty(owner, detector);
         if (newTarget)
         {
             var newTargetEvent = new NewProximityTargetEvent(detector, closestEnt);
index e1776873da9107f671f19d402911e05b111d53c6..de0fe0bce381812d2a951627dcef465f20c78891 100644 (file)
@@ -15,12 +15,6 @@ namespace Content.Shared.Silicons.Borgs.Components;
 [RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem)), AutoGenerateComponentState]
 public sealed partial class BorgChassisComponent : Component
 {
-    /// <summary>
-    /// Whether or not the borg is activated, meaning it has access to modules and a heightened movement speed
-    /// </summary>
-    [DataField("activated"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
-    public bool Activated;
-
     #region Brain
     /// <summary>
     /// A whitelist for which entities count as valid brains
@@ -68,7 +62,7 @@ public sealed partial class BorgChassisComponent : Component
     /// <summary>
     /// The currently selected module
     /// </summary>
-    [DataField("selectedModule")]
+    [DataField("selectedModule"), AutoNetworkedField]
     public EntityUid? SelectedModule;
 
     #region Visuals
index 2983c0d642f22f11cd88ddf514b6153f6be4a792..48d235783681310f08f8c8376ac3f200e23a5dcb 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Shared.Access.Components;
 using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Item.ItemToggle;
 using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Systems;
 using Content.Shared.Popups;
@@ -18,6 +19,7 @@ public abstract partial class SharedBorgSystem : EntitySystem
 {
     [Dependency] protected readonly SharedContainerSystem Container = default!;
     [Dependency] protected readonly ItemSlotsSystem ItemSlots = default!;
+    [Dependency] protected readonly ItemToggleSystem Toggle = default!;
     [Dependency] protected readonly SharedPopupSystem Popup = default!;
 
     /// <inheritdoc/>
@@ -96,7 +98,7 @@ public abstract partial class SharedBorgSystem : EntitySystem
 
     private void OnRefreshMovementSpeedModifiers(EntityUid uid, BorgChassisComponent component, RefreshMovementSpeedModifiersEvent args)
     {
-        if (component.Activated)
+        if (Toggle.IsActivated(uid))
             return;
 
         if (!TryComp<MovementSpeedModifierComponent>(uid, out var movement))
index 1283b6699bf57799e8e53fad1cf15549b067089b..f28e62e7dd1ca67b16557c7121cc045e934208f9 100644 (file)
@@ -4,9 +4,12 @@ using Robust.Shared.Serialization;
 namespace Content.Shared.Toggleable;
 
 /// <summary>
-///     Generic action-event for toggle-able components.
+/// Generic action-event for toggle-able components.
 /// </summary>
-public sealed partial class ToggleActionEvent : InstantActionEvent { }
+/// <remarks>
+/// If you are using <c>ItemToggleComponent</c> subscribe to <c>ItemToggledEvent</c> instead.
+/// </remarks>
+public sealed partial class ToggleActionEvent : InstantActionEvent;
 
 /// <summary>
 ///     Generic enum keys for toggle-visualizer appearance data & sprite layers.
index 56ce81413fb5c90bc689e0bff5694305d9a24e54..201eb19a88ba051072c4d63a97f7047536ad2b73 100644 (file)
@@ -24,7 +24,7 @@ public abstract partial class SharedToolSystem : EntitySystem
     [Dependency] private   readonly SharedAudioSystem _audioSystem = default!;
     [Dependency] private   readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] protected readonly SharedInteractionSystem InteractionSystem = default!;
-    [Dependency] protected readonly SharedItemToggleSystem ItemToggle = default!;
+    [Dependency] protected readonly ItemToggleSystem ItemToggle = default!;
     [Dependency] private   readonly SharedMapSystem _maps = default!;
     [Dependency] private   readonly SharedPopupSystem _popup = default!;
     [Dependency] protected readonly SharedSolutionContainerSystem SolutionContainerSystem = default!;
index b8a815c7a8105d21852c163d987f909a0f9ee928..e494253c8321b0c0607e87af563d793b61f55c86 100644 (file)
@@ -1,3 +1,5 @@
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.PowerCell;
 using Robust.Shared.Containers;
 
@@ -5,6 +7,7 @@ namespace Content.Shared.UserInterface;
 
 public sealed partial class ActivatableUISystem
 {
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
     [Dependency] private readonly SharedPowerCellSystem _cell = default!;
 
     private void InitializePower()
@@ -12,27 +15,22 @@ public sealed partial class ActivatableUISystem
         SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ActivatableUIOpenAttemptEvent>(OnBatteryOpenAttempt);
         SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIOpenedEvent>(OnBatteryOpened);
         SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIClosedEvent>(OnBatteryClosed);
-
-        SubscribeLocalEvent<PowerCellDrawComponent, EntRemovedFromContainerMessage>(OnPowerCellRemoved);
+        SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ItemToggledEvent>(OnToggled);
     }
 
-    private void OnPowerCellRemoved(EntityUid uid, PowerCellDrawComponent component, EntRemovedFromContainerMessage args)
+    private void OnToggled(Entity<ActivatableUIRequiresPowerCellComponent> ent, ref ItemToggledEvent args)
     {
-        _cell.SetPowerCellDrawEnabled(uid, false);
-
-        if (!HasComp<ActivatableUIRequiresPowerCellComponent>(uid) ||
-            !TryComp(uid, out ActivatableUIComponent? activatable))
-        {
+        // only close ui when losing power
+        if (!TryComp<ActivatableUIComponent>(ent, out var activatable) || args.Activated)
             return;
-        }
 
         if (activatable.Key == null)
         {
-            Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}");
+            Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}");
             return;
         }
 
-        _uiSystem.CloseUi(uid, activatable.Key);
+        _uiSystem.CloseUi(ent.Owner, activatable.Key);
     }
 
     private void OnBatteryOpened(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIOpenedEvent args)
@@ -42,7 +40,7 @@ public sealed partial class ActivatableUISystem
         if (!args.UiKey.Equals(activatable.Key))
             return;
 
-        _cell.SetPowerCellDrawEnabled(uid, true);
+        _toggle.TryActivate(uid);
     }
 
     private void OnBatteryClosed(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIClosedEvent args)
@@ -54,7 +52,7 @@ public sealed partial class ActivatableUISystem
 
         // Stop drawing power if this was the last person with the UI open.
         if (!_uiSystem.IsUiOpen(uid, activatable.Key))
-            _cell.SetPowerCellDrawEnabled(uid, false);
+            _toggle.TryDeactivate(uid);
     }
 
     /// <summary>
index 8e7b8975d9d1b128ac21a7213c351ee55863e880..8418c1f3efbeffcaee2701f5548366f5a0f43d29 100644 (file)
@@ -5,16 +5,11 @@ namespace Content.Shared.Weapons.Reflect;
 
 /// <summary>
 /// Entities with this component have a chance to reflect projectiles and hitscan shots
+/// Uses <c>ItemToggleComponent</c> to control reflection.
 /// </summary>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class ReflectComponent : Component
 {
-    /// <summary>
-    /// Can only reflect when enabled
-    /// </summary>
-    [DataField("enabled"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
-    public bool Enabled = true;
-
     /// <summary>
     /// What we reflect.
     /// </summary>
index 7a2e733bf7c771307d25bedfb3e6ec85004d8763..881b547f27f82eb22564c7104fd9265ca9f51085 100644 (file)
@@ -7,6 +7,7 @@ using Content.Shared.Database;
 using Content.Shared.Hands;
 using Content.Shared.Inventory;
 using Content.Shared.Inventory.Events;
+using Content.Shared.Item.ItemToggle;
 using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.Popups;
 using Content.Shared.Projectiles;
@@ -27,10 +28,11 @@ namespace Content.Shared.Weapons.Reflect;
 /// </summary>
 public sealed class ReflectSystem : EntitySystem
 {
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
     [Dependency] private readonly INetManager _netManager = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
-    [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly SharedPhysicsSystem _physics = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -93,7 +95,7 @@ public sealed class ReflectSystem : EntitySystem
     private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null)
     {
         if (!Resolve(reflector, ref reflect, false) ||
-            !reflect.Enabled ||
+            !_toggle.IsActivated(reflector) ||
             !TryComp<ReflectiveComponent>(projectile, out var reflective) ||
             (reflect.Reflects & reflective.Reflective) == 0x0 ||
             !_random.Prob(reflect.ReflectProb) ||
@@ -162,7 +164,7 @@ public sealed class ReflectSystem : EntitySystem
         [NotNullWhen(true)] out Vector2? newDirection)
     {
         if (!TryComp<ReflectComponent>(reflector, out var reflect) ||
-            !reflect.Enabled ||
+            !_toggle.IsActivated(reflector) ||
             !_random.Prob(reflect.ReflectProb))
         {
             newDirection = null;
@@ -214,8 +216,8 @@ public sealed class ReflectSystem : EntitySystem
 
     private void OnToggleReflect(EntityUid uid, ReflectComponent comp, ref ItemToggledEvent args)
     {
-        comp.Enabled = args.Activated;
-        Dirty(uid, comp);
+        if (args.User is {} user)
+            RefreshReflectUser(user);
     }
 
     /// <summary>
@@ -225,7 +227,7 @@ public sealed class ReflectSystem : EntitySystem
     {
         foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.All & ~SlotFlags.POCKET))
         {
-            if (!HasComp<ReflectComponent>(ent))
+            if (!HasComp<ReflectComponent>(ent) || !_toggle.IsActivated(ent))
                 continue;
 
             EnsureComp<ReflectUserComponent>(user);
index adaf563692d92f4814df6e3ca773b3fd4596c1a3..47cb8a83f445eb17aad935c919791e2926926f3a 100644 (file)
@@ -2,7 +2,7 @@
 - type: entity
   id: ActionToggleNinjaGloves
   name: Toggle ninja gloves
-  description: Toggles all glove actions on left click. Includes your doorjack, draining power, stunning enemies, downloading research and calling in a threat.
+  description: Toggles all glove actions on left click. Includes your doorjack, draining power, stunning enemies and hacking certain computers.
   components:
   - type: InstantAction
     priority: -13
@@ -21,7 +21,7 @@
       state: icon
     itemIconStyle: NoItem
     priority: -10
-    event: !type:CreateThrowingStarEvent {}
+    event: !type:CreateItemEvent {}
 
 - type: entity
   id: ActionRecallKatana
@@ -59,7 +59,7 @@
     # have to plan (un)cloaking ahead of time
     useDelay: 5
     priority: -9
-    event: !type:ToggleStealthEvent
+    event: !type:ToggleActionEvent
 
 # katana
 - type: entity
       sprite: Objects/Magic/magicactions.rsi
       state: blink
     itemIconStyle: NoItem
+    sound:
+      path: /Audio/Magic/blink.ogg
+      params:
+        volume: 5
     priority: -12
     event: !type:DashEvent
     checkCanAccess: false
index 8b73eee0d249c9c3450ae74a37a952726a88d6de..f1d99884658a487fd2aaeab968769ae29b5bb1e3 100644 (file)
   - type: FingerprintMask
 
 - type: entity
-  parent: ClothingHandsBase
+  parent: [ClothingHandsBase, BaseToggleClothing]
   id: ClothingHandsGlovesSpaceNinja
   name: space ninja gloves
   description: These black nano-enhanced gloves insulate from electricity and provide fire resistance.
   - type: Thieving
     stripTimeReduction: 1
     stealthy: true
+  - type: ToggleClothing
+    action: ActionToggleNinjaGloves
   - type: NinjaGloves
+    abilities:
+    - components:
+      - type: BatteryDrainer
+      - type: StunProvider
+        noPowerPopup: ninja-no-power
+        whitelist:
+          components:
+          - Stamina
+      - type: EmagProvider
+        whitelist:
+          components:
+          - Airlock
+    - objective: StealResearchObjective
+      components:
+      - type: ResearchStealer
+    - objective: TerrorObjective
+      components:
+      - type: CommsHacker
+        threats: NinjaThreats
+    - objective: MassArrestObjective
+      components:
+      - type: CriminalRecordsHacker
 
 - type: entity
   parent: ClothingHandsGlovesColorBlack
index c6a556b2d324f6b559b18e1c6f4799a9213ff1c5..c32f485f9cadad97e3ed5aee63faaf42fb42f119 100644 (file)
   - type: AddAccentClothing
     accent: OwOAccent
 
+- type: entity
+  parent: [ClothingHeadHatCatEars, BaseToggleClothing]
+  id: ClothingHeadHatCatEarsValid
+  suffix: Valid, DO NOT MAP
+  components:
+  - type: ToggleClothing
+    action: ActionBecomeValid
+    disableOnUnequip: true
+  - type: ComponentToggler
+    parent: true
+    components:
+    - type: KillSign
+  - type: Tag
+    tags: [] # ignore "WhitelistChameleon" tag
+  - type: Sprite
+    sprite: Clothing/Head/Hats/catears.rsi
+  - type: Clothing
+    sprite: Clothing/Head/Hats/catears.rsi
+  - type: AddAccentClothing
+    accent: OwOAccent
+
+- type: entity
+  noSpawn: true
+  id: ActionBecomeValid
+  name: Become Valid
+  description: "*notices your killsign* owo whats this"
+  components:
+  - type: InstantAction
+    event: !type:ToggleActionEvent
+
 - type: entity
   parent: ClothingHeadBase
   id: ClothingHeadHatDogEars
index 2053ced0f631e43169d104428853110032c4f39f..bacdd0046f884a0dae44d4801ffc82e1b1871ddf 100644 (file)
     slots: WITHOUT_POCKET
 
 - type: entity
-  parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing]
+  parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing, BaseToggleClothing]
   id: ClothingOuterSuitSpaceNinja
   name: space ninja suit
   description: This black technologically advanced, cybernetically-enhanced suit provides many abilities like invisibility or teleportation.
     sprite: Clothing/OuterClothing/Suits/spaceninja.rsi
   - type: Clothing
     sprite: Clothing/OuterClothing/Suits/spaceninja.rsi
-  - type: StealthClothing
-    visibility: 1.1
-    toggleAction: ActionTogglePhaseCloak
+  # hardsuit stuff
   - type: PressureProtection
     highPressureMultiplier: 0.6
     lowPressureMultiplier: 1000
         Slash: 0.8
         Piercing: 0.8
         Heat: 0.8
+  # phase cloak
+  - type: ToggleClothing
+    action: ActionTogglePhaseCloak
+  - type: ComponentToggler
+    parent: true
+    components:
+    - type: Stealth
+      minVisibility: 0.1
+      lastVisibility: 0.1
+  - type: PowerCellDraw
+    drawRate: 1.8 # 200 seconds on the default cell
+  # throwing star ability
+  - type: ItemCreator
+    action: ActionCreateThrowingStar
+    charge: 14.4
+    spawnedPrototype: ThrowingStarNinja
+    noPowerPopup: ninja-no-power
+  # core ninja suit stuff
   - type: NinjaSuit
+  - type: UseDelay
+    delay: 5 # disable time
   - type: PowerCellSlot
     cellSlotId: cell_slot
     # throwing in a recharger would bypass glove charging mechanic
index d934a8b97e1364a100563a175d50518c8752e12d..19fa86a631227636e807dc6f2160bafbe548817d 100644 (file)
@@ -1,33 +1,39 @@
 - type: entity
-  parent: ClothingShoesBase
+  parent: [ClothingShoesBase, BaseToggleClothing]
   id: ClothingShoesBootsMag
   name: magboots
   description: Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle.
   components:
-    - type: Sprite
-      sprite: Clothing/Shoes/Boots/magboots.rsi
-      layers:
-      - state: icon
-        map: [ "enum.ToggleVisuals.Layer" ]
-    - type: Clothing
-      sprite: Clothing/Shoes/Boots/magboots.rsi
-    - type: Magboots
-    - type: ClothingSpeedModifier
-      walkModifier: 0.85
-      sprintModifier: 0.8
-      enabled: false
-    - type: Appearance
-    - type: GenericVisualizer
-      visuals:
-        enum.ToggleVisuals.Toggled:
-          enum.ToggleVisuals.Layer:
-            True: {state: icon-on}
-            False: {state: icon}
-    - type: StaticPrice
-      price: 200
-    - type: Tag
-      tags:
-      - WhitelistChameleon
+  - type: Sprite
+    sprite: Clothing/Shoes/Boots/magboots.rsi
+    layers:
+    - state: icon
+      map: [ "enum.ToggleVisuals.Layer" ]
+  - type: Clothing
+    sprite: Clothing/Shoes/Boots/magboots.rsi
+  - type: ToggleClothing
+    action: ActionToggleMagboots
+  - type: ToggleVerb
+    text: toggle-magboots-verb-get-data-text
+  - type: ComponentToggler
+    components:
+    - type: NoSlip
+  - type: Magboots
+  - type: ClothingSpeedModifier
+    walkModifier: 0.85
+    sprintModifier: 0.8
+  - type: Appearance
+  - type: GenericVisualizer
+    visuals:
+      enum.ToggleVisuals.Toggled:
+        enum.ToggleVisuals.Layer:
+          True: {state: icon-on}
+          False: {state: icon}
+  - type: StaticPrice
+    price: 200
+  - type: Tag
+    tags:
+    - WhitelistChameleon
 
 - type: entity
   parent: ClothingShoesBootsMag
     state: icon
   - type: Clothing
     sprite: Clothing/Shoes/Boots/magboots-advanced.rsi
-  - type: Magboots
-    toggleAction: ActionToggleMagbootsAdvanced
   - type: ClothingSpeedModifier
     walkModifier: 1
     sprintModifier: 1
-    enabled: false
-  - type: NoSlip
   - type: Tag
     tags:
     - WhitelistChameleon
@@ -64,8 +66,6 @@
     sprite: Clothing/Shoes/Boots/magboots-science.rsi
   - type: Clothing
     sprite: Clothing/Shoes/Boots/magboots-science.rsi
-  - type: Magboots
-    toggleAction: ActionToggleMagbootsSci
 
 - type: entity
   parent: ClothingShoesBootsMag
@@ -76,7 +76,6 @@
   - type: ClothingSpeedModifier
     walkModifier: 1.10 #PVS isn't too much of an issue when you are blind...
     sprintModifier: 1.10
-    enabled: false
   - type: StaticPrice
     price: 3000
 
     state: icon
   - type: Clothing
     sprite: Clothing/Shoes/Boots/magboots-syndicate.rsi
-  - type: Magboots
-    toggleAction: ActionToggleMagbootsSyndie
   - type: ClothingSpeedModifier
     walkModifier: 0.95
     sprintModifier: 0.9
-    enabled: false
   - type: GasTank
     outputPressure: 42.6
     air:
       volume: 0.75
       temperature: 293.15
       moles:
-        - 0.153853429 # oxygen
-        - 0.153853429 # nitrogen
+      - 0.153853429 # oxygen
+      - 0.153853429 # nitrogen
   - type: Item
     sprite: null
     size: Normal
 
 - type: entity
-  id: ActionBaseToggleMagboots
+  id: ActionToggleMagboots
   name: Toggle Magboots
   description: Toggles the magboots on and off.
   components:
   - type: InstantAction
-    itemIconStyle: NoItem
-    event: !type:ToggleMagbootsEvent
-
-- type: entity
-  id: ActionToggleMagboots
-  parent: ActionBaseToggleMagboots
-  components:
-  - type: InstantAction
-    icon: { sprite: Clothing/Shoes/Boots/magboots.rsi, state: icon }
-    iconOn: { sprite : Clothing/Shoes/Boots/magboots.rsi, state: icon-on }
-
-- type: entity
-  id: ActionToggleMagbootsAdvanced
-  parent: ActionBaseToggleMagboots
-  components:
-  - type: InstantAction
-    icon: { sprite: Clothing/Shoes/Boots/magboots-advanced.rsi, state: icon }
-    iconOn: Clothing/Shoes/Boots/magboots-advanced.rsi/icon-on.png
-
-- type: entity
-  id: ActionToggleMagbootsSci
-  parent: ActionBaseToggleMagboots
-  components:
-  - type: InstantAction
-    icon: { sprite: Clothing/Shoes/Boots/magboots-science.rsi, state: icon }
-    iconOn: Clothing/Shoes/Boots/magboots-science.rsi/icon-on.png
-
-- type: entity
-  id: ActionToggleMagbootsSyndie
-  parent: ActionBaseToggleMagboots
-  components:
-  - type: InstantAction
-    icon: { sprite: Clothing/Shoes/Boots/magboots-syndicate.rsi, state: icon }
-    iconOn: Clothing/Shoes/Boots/magboots-syndicate.rsi/icon-on.png
+    itemIconStyle: BigItem
+    event: !type:ToggleActionEvent
index 22cd13af60051664336dda442112eabe8e39ea49..fae8717223806be2bcbd0c454865629e605bd518 100644 (file)
@@ -88,7 +88,7 @@
   - type: NoSlip
 
 - type: entity
-  parent: [ClothingShoesBase, PowerCellSlotSmallItem]
+  parent: [ClothingShoesBase, PowerCellSlotSmallItem, BaseToggleClothing]
   id: ClothingShoesBootsSpeed
   name: speed boots
   description: High-tech boots woven with quantum fibers, able to convert electricity into pure speed!
       map: [ "enum.ToggleVisuals.Layer" ]
   - type: Clothing
     sprite: Clothing/Shoes/Boots/speedboots.rsi
-  - type: ToggleClothingSpeed
-    toggleAction: ActionToggleSpeedBoots
+  - type: ToggleClothing
+    action: ActionToggleSpeedBoots
   - type: ClothingSpeedModifier
     walkModifier: 1.5
     sprintModifier: 1.5
-    enabled: false
   - type: Appearance
   - type: GenericVisualizer
     visuals:
   description: Toggles the speed boots on and off.
   components:
   - type: InstantAction
-    itemIconStyle: NoItem
-    event: !type:ToggleClothingSpeedEvent
-    icon: { sprite: Clothing/Shoes/Boots/speedboots.rsi, state: icon }
-    iconOn: { sprite: Clothing/Shoes/Boots/speedboots.rsi, state: icon-on }
+    itemIconStyle: BigItem
+    event: !type:ToggleActionEvent
 
 - type: entity
   parent: ClothingShoesBase
index a96ca2d23c8c649faf79910c7db62a61cc5bb353..02a2ddce411676a7be09af7b6eca6ed7849394e2 100644 (file)
   - type: GroupExamine
   - type: Armor
     modifiers: {}
+
+# for clothing that can be toggled, like magboots
+- type: entity
+  abstract: true
+  id: BaseToggleClothing
+  components:
+  - type: ItemToggle
+    onUse: false # can't really wear it like that
+  - type: ToggleClothing
index b6f9287cf7ff56ae00bf83c54f6bdc079d425d6a..b8cf755d0ff9895c46d537e00f66c445efc2b2f3 100644 (file)
       state: alive
 
 - type: entity
+  noSpawn: true
+  parent: BaseAntagSpawner
   id: SpawnPointGhostSpaceNinja
-  name: ghost role spawn point
-  suffix: space ninja
-  parent: MarkerBase
   components:
   - type: GhostRole
     name: ghost-role-information-space-ninja-name
     rules: ghost-role-information-antagonist-rules
     raffle:
       settings: default
-  - type: GhostRoleMobSpawner
-    prototype: MobHumanSpaceNinja
   - type: Sprite
     sprite: Markers/jobs.rsi
     layers:
index 3b8e5bde6ae818f75592a9c17552956c09939601..6656a7aadbf6aa410756d027cccc7bfbc31218ff 100644 (file)
   - type: PowerCellSlot
     cellSlotId: cell_slot
     fitsInCharger: true
+  - type: ItemToggle
+    onUse: false # no item-borg toggling sorry
+  - type: AccessToggle
+  # TODO: refactor movement to just be based on toggle like speedboots but for the boots themselves
+  # TODO: or just have sentient speedboots be fast idk
   - type: PowerCellDraw
     drawRate: 0.6
   - type: ItemSlots
index 629ba91518aecd7b3403fe61620e54157f1d98c4..0b26668e10385ad1d074e5a7da69935f08705b16 100644 (file)
     - type: NpcFactionMember
       factions:
       - Syndicate
-
-# Space Ninja
-- type: entity
-  noSpawn: true
-  name: Space Ninja
-  parent: MobHuman
-  id: MobHumanSpaceNinja
-  components:
-  - type: RandomHumanoidAppearance
-    randomizeName: false
-  - type: Loadout
-    prototypes: [SpaceNinjaGear]
-  - type: NpcFactionMember
-    factions:
-    - Syndicate
-  - type: SpaceNinja
-  - type: GenericAntag
-    rule: Ninja
-  - type: AutoImplant
-    implants:
-    - DeathAcidifierImplant
-  - type: RandomMetadata
-    nameSegments:
-    - names_ninja_title
-    - names_ninja
index d956f1871d78dec2859ee366bfde85d28b6c0577..8702bbe0218c3a78e292671b46472192e1216a04 100644 (file)
     - Pacified
     - StaminaModifier
     - Flashed
-  - type: Reflect
-    enabled: false
-    reflectProb: 0
   - type: Body
     prototype: Human
     requiredLegs: 2
diff --git a/Resources/Prototypes/Entities/Objects/Devices/base_handheld.yml b/Resources/Prototypes/Entities/Objects/Devices/base_handheld.yml
new file mode 100644 (file)
index 0000000..259323f
--- /dev/null
@@ -0,0 +1,11 @@
+- type: entity
+  abstract: true
+  parent: [BaseItem, PowerCellSlotSmallItem]
+  id: BaseHandheldComputer
+  components:
+  - type: ActivatableUIRequiresPowerCell
+  - type: ItemToggle
+    onUse: false # above component does the toggling
+  - type: PowerCellDraw
+    drawRate: 0
+    useRate: 20
index 84f0f4ae94c8e8cdec41c3b84364bfe4ddd0f0c0..bf0b7190b5b3add66dd97ddd6e6ab659affd5f73 100644 (file)
   id: BaseMedicalPDA
   abstract: true
   components:
+  - type: ItemToggle
+    toggleLight: false
+    onUse: false
   - type: HealthAnalyzer
     scanDelay: 1
     scanningEndSound:
index 0d2f890a1d3e77c8e23d264bb10cd8d30ce76851..54fc4a70c5a555c2c3a559888f07f8442d4f6a9f 100644 (file)
@@ -1,9 +1,9 @@
 - type: entity
+  parent: BaseItem
   id: BaseHandheldStationMap
   name: station map
   description: Displays a readout of the current station.
   abstract: true
-  parent: BaseItem
   components:
     - type: StationMap
     - type: Sprite
 - type: entity
   id: HandheldStationMap
   parent:
-    - BaseHandheldStationMap
-    - PowerCellSlotSmallItem
+  - BaseHandheldStationMap
+  - BaseHandheldComputer
   suffix: Handheld, Powered
-  components:
-    - type: PowerCellDraw
-      drawRate: 0
-      useRate: 20
-    - type: ActivatableUIRequiresPowerCell
 
 - type: entity
   id: HandheldStationMapEmpty
index dc24ac485a11aebba3712f01f5934fd9ccfc81e4..8182accfb6f9df6622b434ca87b26e712d0a21c8 100644 (file)
         path: /Audio/Weapons/ebladehum.ogg
     - type: ItemToggleSize
       activatedSize: Huge
-    - type: ItemToggleDisarmMalus
-      activatedDisarmMalus: 0.6
+    - type: ComponentToggler
+      components:
+      - type: DisarmMalus
+        malus: 0.6
     - type: Sprite
       sprite: Objects/Weapons/Melee/e_shield.rsi
       layers:
       energy: 2
       color: blue
     - type: Reflect
-      enabled: false
       reflectProb: 0.95
       reflects:
         - Energy
         path: /Audio/Weapons/telescopicoff.ogg
         params:
           volume: -5
-    - type: ItemToggleDisarmMalus
-      activatedDisarmMalus: 0.6
+    - type: ComponentToggler
+      components:
+      - type: DisarmMalus
+        malus: 0.6
     - type: ItemToggleSize
       activatedSize: Huge
     - type: Sprite
index 4a61074a8d5728f4be202f80554150c5021a5be6..2d06ed0f1dc715d3b749d7a032f68af6a99ed794 100644 (file)
       size: Large
     - type: Speech
       speechVerb: Robotic
+    - type: ItemToggle
+      soundActivate:
+        path: /Audio/Items/Defib/defib_safety_on.ogg
+      soundDeactivate:
+        path: /Audio/Items/Defib/defib_safety_off.ogg
     - type: Defibrillator
       zapHeal:
         types:
index 19a0b36ee9f9797c21aa0db109d88b9ab10bbf11..c9ab24274daf6bcbd230f8b5fb09989603b4bd1c 100644 (file)
@@ -1,9 +1,7 @@
 - type: entity
   name: handheld crew monitor
   suffix: DO NOT MAP
-  parent:
-  - BaseItem
-  - PowerCellSlotSmallItem
+  parent: BaseHandheldComputer
   # CMO-only bud, don't add more.
   id: HandheldCrewMonitor
   description: A hand-held crew monitor displaying the status of suit sensors.
@@ -11,10 +9,6 @@
   - type: Sprite
     sprite: Objects/Specific/Medical/handheldcrewmonitor.rsi
     state: scanner
-  - type: PowerCellDraw
-    drawRate: 0
-    useRate: 20
-  - type: ActivatableUIRequiresPowerCell
   - type: ActivatableUI
     key: enum.CrewMonitoringUIKey.Key
   - type: UserInterface
index c01aaa84a9d76b33734364ee0fa7a78f5d7a825c..d8547d92948c280652899a48c5b563bb26735d83 100644 (file)
@@ -21,6 +21,8 @@
     interfaces:
       enum.HealthAnalyzerUiKey.Key:
         type: HealthAnalyzerBoundUserInterface
+  - type: ItemToggle
+    onUse: false
   - type: HealthAnalyzer
     scanningEndSound:
       path: "/Audio/Items/Medical/healthscanner.ogg"
index bf0a3be4e51d63a2c092c505df7c075a940fed4a..99f874406b421a94c643cabe860278a2916736f6 100644 (file)
       - state: screen
         shader: unshaded
         visible: false
-        map: ["enum.PowerDeviceVisualLayers.Powered"]
+        map: ["enum.ToggleVisuals.Layer"]
   - type: Appearance
   - type: GenericVisualizer
     visuals:
-      enum.ProximityBeeperVisuals.Enabled:
-        enum.PowerDeviceVisualLayers.Powered:
+      enum.ToggleVisuals.Toggled:
+        enum.ToggleVisuals.Layer:
           True: { visible: true }
           False: { visible: false }
+  - type: ItemToggle
   - type: ProximityBeeper
   - type: ProximityDetector
-    enabled: false
     range: 20
     criteria:
       components:
       - Anomaly
   - type: Beeper
-    enabled: false
     minBeepInterval: 0.15
     maxBeepInterval: 1.00
     beepSound:
index 5938193181e4f5f78bc1930763a60c19bcaa4072..78abedc28abd02b4589849a7f8cb570cc878f4a4 100644 (file)
@@ -1,6 +1,6 @@
 - type: entity
   name: handheld mass scanner
-  parent: [ BaseItem, PowerCellSlotSmallItem]
+  parent: BaseHandheldComputer
   id: HandHeldMassScanner
   description: A hand-held mass scanner.
   components:
@@ -27,7 +27,6 @@
   - type: PowerCellDraw
     drawRate: 3
     useRate: 100
-  - type: ActivatableUIRequiresPowerCell
   - type: ActivatableUI
     key: enum.RadarConsoleUiKey.Key
     inHandsOnly: true
index 9db30edb52ed57cf929a7b0205220c8ed104660f..cd188969b5325b0c1d6d07f0a498c404ea609fd6 100644 (file)
   - type: ItemToggleSize
     activatedSize: Large
   - type: ItemToggleHot
-  - type: ItemToggleDisarmMalus
-    activatedDisarmMalus: 0.6
+  - type: ComponentToggler
+    components:
+    - type: DisarmMalus
+      malus: 0.6
   - type: ToggleableLightVisuals
     spriteLayer: flame
     inhandVisuals:
index 0d10411cfbbf5ed32ec05585512907a8eae785f1..19a0cf30e860f1cd8df2c8c15cafd836ea5fbb8e 100644 (file)
     - type: TetherGun
       frequency: 5
       dampingRatio: 4
+    - type: ItemToggle
+      onUse: false
     - type: PowerCellDraw
     - type: Sprite
       sprite: Objects/Weapons/Guns/Launchers/tether_gun.rsi
         path: /Audio/Weapons/soup.ogg
         params:
           volume: 2
+    - type: ItemToggle
+      onUse: false
     - type: PowerCellDraw
     - type: Sprite
       sprite: Objects/Weapons/Guns/Launchers/force_gun.rsi
index fbf8b1003c355476e04abf013fb307b4005359eb..ce3545920ac20b14f93585f1e2de89e336211dd6 100644 (file)
   - type: ItemToggleActiveSound
     activeSound:
       path: /Audio/Weapons/ebladehum.ogg
-  - type: ItemToggleSharp
+  - type: ComponentToggler
+    components:
+    - type: Sharp
+    - type: DisarmMalus
+      malus: 0.6
   - type: ItemToggleHot
-  - type: ItemToggleDisarmMalus
-    activatedDisarmMalus: 0.6
   - type: ItemToggleSize
     activatedSize: Huge
   - type: ItemToggleMeleeWeapon
       right:
       - state: inhand-right-blade
         shader: unshaded
-  - type: DisarmMalus
-    malus: 0
   - type: Reflect
-    enabled: false
   - type: IgnitionSource
     temperature: 700
 
@@ -88,7 +87,6 @@
   suffix: E-Dagger
   description: 'A dark ink pen.'
   components:
-  - type: EnergySword
   - type: ItemToggle
     soundActivate:
       path: /Audio/Weapons/ebladeon.ogg
       path: /Audio/Weapons/ebladehum.ogg
       params:
         volume: -6
-  - type: ItemToggleDisarmMalus
-    activatedDisarmMalus: 0.4
+  - type: ComponentToggler
+    components:
+    - type: Sharp
+    - type: DisarmMalus
+      malus: 0.4
   - type: Sprite
     sprite: Objects/Weapons/Melee/e_dagger.rsi
     layers:
   id: EnergyCutlass
   description: An exotic energy weapon.
   components:
-  - type: EnergySword
   - type: ItemToggleMeleeWeapon
     activatedDamage:
         types:
             Slash: 10
             Heat: 12
     deactivatedSecret: true
-  - type: ItemToggleDisarmMalus
-    activatedDisarmMalus: 0.6
   - type: Sprite
     sprite: Objects/Weapons/Melee/e_cutlass.rsi
     layers:
   id: EnergySwordDouble
   description: Syndicate Command Interns thought that having one blade on the energy sword was not enough. This can be stored in pockets.
   components:
-  - type: EnergySword
   - type: ItemToggle
+    onUse: false # wielding events control it instead
     soundActivate:
       path: /Audio/Weapons/ebladeon.ogg
       params:
       path: /Audio/Weapons/ebladehum.ogg
       params:
         volume: 3
-  - type: ItemToggleDisarmMalus
-    activatedDisarmMalus: 0.7
+  - type: ComponentToggler
+    components:
+    - type: Sharp
+    - type: DisarmMalus
+      malus: 0.7
   - type: Wieldable
+    wieldSound: null # esword light sound instead
   - type: MeleeWeapon
     wideAnimationRotation: -135
     attackRate: 1.5
index d0d85beb6f591d4bc715b7b5326fb381f8217f62..0bab2c828dec974e5e601a0731817b28fd1f514c 100644 (file)
@@ -30,7 +30,6 @@
     soundHit:
         path: /Audio/Weapons/bladeslice.ogg
   - type: Reflect
-    enabled: true
     reflectProb: .1
     spread: 90
   - type: Item
       soundHit:
         path: /Audio/Effects/explosion_small1.ogg
     - type: Reflect
-      enabled: true
       reflectProb: .25
       spread: 90
     - type: Item
index 4cb76ea4b921ff6a6d22c0ee20bb60c02fcbba8a..0485b5a517836bde73f9552389d308c5ed27038a 100644 (file)
@@ -85,6 +85,7 @@
         whitelist:
           components:
             - FitsInDispenser
+  - type: ItemToggle
   - type: HealthAnalyzer
     scanDelay: 0
   - type: UserInterface
index 39e29ad1158cc2f399bb4e0bd13c5ba1541a38d2..0b183039a997b4e0a52a767999cc31f16b84fe54 100644 (file)
     earliestStart: 30
     reoccurrenceDelay: 20
     minimumPlayers: 30
-  - type: NinjaSpawnRule
+  - type: SpaceSpawnRule
+  - type: AntagLoadProfileRule
+  - type: AntagObjectives
+    objectives:
+    - StealResearchObjective
+    - DoorjackObjective
+    - SpiderChargeObjective
+    - TerrorObjective
+    - MassArrestObjective
+    - NinjaSurviveObjective
+  - type: AntagSelection
+    agentName: ninja-round-end-agent-name
+    definitions:
+    - spawnerPrototype: SpawnPointGhostSpaceNinja
+      min: 1
+      max: 1
+      pickPlayer: false
+      startingGear: SpaceNinjaGear
+      briefing:
+        text: ninja-role-greeting
+        color: Green
+        sound: /Audio/Misc/ninja_greeting.ogg
+      components:
+      - type: SpaceNinja
+      - type: NpcFactionMember
+        factions:
+        - Syndicate
+      - type: AutoImplant
+        implants:
+        - DeathAcidifierImplant
+      - type: RandomMetadata
+        nameSegments:
+        - names_ninja_title
+        - names_ninja
+      mindComponents:
+      - type: NinjaRole
+        prototype: SpaceNinja
 
 - type: entity
   parent: BaseGameRule
index 5a38c22215458bbe6d25105db05d329e807c21fd..43cdca3bc33b4c6181cf2411a2fe81372bdee546 100644 (file)
@@ -1,21 +1,3 @@
-# doesnt spawn a ninja or anything, just stores configuration for it
-# see NinjaSpawn event for spawning
-- type: entity
-  id: Ninja
-  parent: BaseGameRule
-  components:
-  - type: GenericAntagRule
-    agentName: ninja-round-end-agent-name
-    objectives:
-    - StealResearchObjective
-    - DoorjackObjective
-    - SpiderChargeObjective
-    - TerrorObjective
-    - MassArrestObjective
-    - NinjaSurviveObjective
-  - type: NinjaRule
-    threats: NinjaThreats
-
 - type: entity
   parent: BaseGameRule
   id: Thief