diff --git a/Content.Client/Items/Systems/ItemToggleSystem.cs b/Content.Client/Items/Systems/ItemToggleSystem.cs
deleted file mode 100644
index 46d6f1b464..0000000000
--- a/Content.Client/Items/Systems/ItemToggleSystem.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.Item.ItemToggle;
-
-namespace Content.Shared.Item;
-
-///
-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
index 0000000000..9ab62cc12d
--- /dev/null
+++ b/Content.Client/Ninja/Systems/ItemCreatorSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class ItemCreatorSystem : SharedItemCreatorSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs b/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
index 7758c3d7e2..5b07b1588f 100644
--- a/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
@@ -2,9 +2,4 @@ using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
-///
-/// Does nothing special, only exists to provide a client implementation.
-///
-public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
-{
-}
+public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaSuitSystem.cs b/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
index fde1801b37..852ea8af46 100644
--- a/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
@@ -1,24 +1,5 @@
-using Content.Shared.Clothing.EntitySystems;
-using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
-///
-/// Disables cloak prediction since client has no knowledge of battery power.
-/// Cloak will still be enabled after server tells it.
-///
-public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
-{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnAttemptStealth);
- }
-
- private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
- {
- args.Cancel();
- }
-}
+public sealed class NinjaSuitSystem : SharedNinjaSuitSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaSystem.cs b/Content.Client/Ninja/Systems/NinjaSystem.cs
index aa2fa2047f..958dc6a5d9 100644
--- a/Content.Client/Ninja/Systems/NinjaSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaSystem.cs
@@ -2,11 +2,4 @@ using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
-///
-/// Currently does nothing special clientside.
-/// All functionality is in shared and server.
-/// Only exists to prevent crashing.
-///
-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
index 0000000000..b107fd3867
--- /dev/null
+++ b/Content.Client/Ninja/Systems/SpiderChargeSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem;
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
index a09126a7f7..33e4da3fa3 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
@@ -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));
}
});
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
index 457d3e3192..b3d684e01a 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
@@ -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();
InteractSys = SEntMan.System();
ToolSys = SEntMan.System();
- ItemToggleSys = SEntMan.System();
+ ItemToggleSys = SEntMan.System();
DoAfterSys = SEntMan.System();
Transform = SEntMan.System();
MapSystem = SEntMan.System();
diff --git a/Content.Server/Charges/Components/AutoRechargeComponent.cs b/Content.Server/Charges/Components/AutoRechargeComponent.cs
index 9dcf555ea9..165b181dcb 100644
--- a/Content.Server/Charges/Components/AutoRechargeComponent.cs
+++ b/Content.Server/Charges/Components/AutoRechargeComponent.cs
@@ -7,6 +7,7 @@ namespace Content.Server.Charges.Components;
/// Something with limited charges that can be recharged automatically.
/// Requires LimitedChargesComponent to function.
///
+// TODO: no reason this cant be predicted and server system deleted
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(ChargesSystem))]
public sealed partial class AutoRechargeComponent : Component
diff --git a/Content.Server/Charges/Systems/ChargesSystem.cs b/Content.Server/Charges/Systems/ChargesSystem.cs
index 03e192e680..974928ee4b 100644
--- a/Content.Server/Charges/Systems/ChargesSystem.cs
+++ b/Content.Server/Charges/Systems/ChargesSystem.cs
@@ -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(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(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
index fa352eb320..0000000000
--- a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs
+++ /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;
-
-///
-/// Stores some configuration used by the ninja system.
-/// Objectives and roundend summary are handled by .
-///
-[RegisterComponent, Access(typeof(SpaceNinjaSystem))]
-public sealed partial class NinjaRuleComponent : Component
-{
- ///
- /// List of threats that can be called in. Copied onto when gloves are enabled.
- ///
- [DataField(required: true)]
- public ProtoId Threats = string.Empty;
-
- ///
- /// Sound played when making the player a ninja via antag control or ghost role
- ///
- [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
index 30fa84ed90..0000000000
--- a/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace Content.Server.Item;
-
-///
-/// Handles whether this item applies a disarm malus when active.
-///
-[RegisterComponent]
-public sealed partial class ItemToggleDisarmMalusComponent : Component
-{
- ///
- /// 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.
- ///
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public float? ActivatedDisarmMalus = null;
-
- ///
- /// 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.
- ///
- [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
index 227491b16c..0000000000
--- a/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Item;
-
-///
-/// Handles whether this item is sharp when toggled on.
-///
-[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
index f98415eb08..0000000000
--- a/Content.Server/Item/ItemToggle/ItemToggleSystem.cs
+++ /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(ToggleSharp);
- SubscribeLocalEvent(ToggleMalus);
- }
-
- private void ToggleSharp(Entity ent, ref ItemToggledEvent args)
- {
- // TODO generalize this into a "ToggleComponentComponent", though probably with a better name
- if (args.Activated)
- EnsureComp(ent);
- else
- RemCompDeferred(ent);
- }
-
- private void ToggleMalus(Entity ent, ref ItemToggledEvent args)
- {
- if (!TryComp(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;
- }
-}
diff --git a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs
index 6380b71c8a..de40e98e18 100644
--- a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs
+++ b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs
@@ -6,6 +6,9 @@ namespace Content.Server.Medical.Components;
///
/// After scanning, retrieves the target Uid to use with its related UI.
///
+///
+/// Requires ItemToggleComponent.
+///
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(HealthAnalyzerSystem), typeof(CryoPodSystem))]
public sealed partial class HealthAnalyzerComponent : Component
diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs
index 4373532f01..1896f51edd 100644
--- a/Content.Server/Medical/DefibrillatorSystem.cs
+++ b/Content.Server/Medical/DefibrillatorSystem.cs
@@ -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
///
public override void Initialize()
{
- SubscribeLocalEvent(OnUseInHand);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnAfterInteract);
SubscribeLocalEvent(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));
diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs
index c72cd2ddf6..1d6e564a32 100644
--- a/Content.Server/Medical/HealthAnalyzerSystem.cs
+++ b/Content.Server/Medical/HealthAnalyzerSystem.cs
@@ -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(OnAfterInteract);
SubscribeLocalEvent(OnDoAfter);
SubscribeLocalEvent(OnInsertedIntoContainer);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
+ SubscribeLocalEvent(OnToggled);
SubscribeLocalEvent(OnDropped);
}
@@ -111,16 +114,16 @@ public sealed class HealthAnalyzerSystem : EntitySystem
private void OnInsertedIntoContainer(Entity uid, ref EntGotInsertedIntoContainerMessage args)
{
if (uid.Comp.ScannedEntity is { } patient)
- StopAnalyzingEntity(uid, patient);
+ _toggle.TryDeactivate(uid.Owner);
}
///
- /// Disable continuous updates once battery is dead
+ /// Disable continuous updates once turned off
///
- private void OnPowerCellSlotEmpty(Entity uid, ref PowerCellSlotEmptyEvent args)
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
{
- if (uid.Comp.ScannedEntity is { } patient)
- StopAnalyzingEntity(uid, patient);
+ if (!args.Activated && ent.Comp.ScannedEntity is { } patient)
+ StopAnalyzingEntity(ent, patient);
}
///
@@ -129,7 +132,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
private void OnDropped(Entity 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);
}
diff --git a/Content.Server/Ninja/Events/BatteryChangedEvent.cs b/Content.Server/Ninja/Events/BatteryChangedEvent.cs
index 45bfedfee7..1848e88186 100644
--- a/Content.Server/Ninja/Events/BatteryChangedEvent.cs
+++ b/Content.Server/Ninja/Events/BatteryChangedEvent.cs
@@ -1,7 +1,7 @@
namespace Content.Server.Ninja.Events;
///
-/// Raised on the ninja when the suit has its powercell changed.
+/// Raised on the ninja and suit when the suit has its powercell changed.
///
[ByRefEvent]
public record struct NinjaBatteryChangedEvent(EntityUid Battery, EntityUid BatteryHolder);
diff --git a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
index 59dec556fa..4baf0913ce 100644
--- a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
+++ b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
@@ -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.
///
- private void OnBeforeInteractHand(EntityUid uid, BatteryDrainerComponent comp, BeforeInteractHandEvent args)
+ private void OnBeforeInteractHand(Entity ent, ref BeforeInteractHandEvent args)
{
+ var (uid, comp) = ent;
var target = args.Target;
- if (args.Handled || comp.BatteryUid == null || !HasComp(target))
+ if (args.Handled || comp.BatteryUid is not {} battery || !HasComp(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 ent, ref NinjaBatteryChangedEvent args)
{
- SetBattery(uid, args.Battery, comp);
+ SetBattery((ent, ent.Comp), args.Battery);
}
///
- protected override void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent args)
+ protected override void OnDoAfterAttempt(Entity ent, ref DoAfterAttemptEvent 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();
}
///
- protected override bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
+ protected override bool TryDrainPower(Entity ent, EntityUid target)
{
+ var (uid, comp) = ent;
if (comp.BatteryUid == null || !TryComp(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
index 0000000000..d7a7be995d
--- /dev/null
+++ b/Content.Server/Ninja/Systems/ItemCreatorSystem.cs
@@ -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(OnCreateItem);
+ SubscribeLocalEvent(OnBatteryChanged);
+ }
+
+ private void OnCreateItem(Entity 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 ent, ref NinjaBatteryChangedEvent args)
+ {
+ if (ent.Comp.Battery == args.Battery)
+ return;
+
+ ent.Comp.Battery = args.Battery;
+ Dirty(ent, ent.Comp);
+ }
+}
diff --git a/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs b/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
index ac76ae6b77..3aaf7c5d58 100644
--- a/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
+++ b/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
@@ -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;
///
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 ent, Entity user)
{
- base.Initialize();
+ base.EnableGloves(ent, user);
- SubscribeLocalEvent(OnToggleAction);
- }
-
- ///
- /// Toggle gloves, if the user is a ninja wearing a ninja suit.
- ///
- private void OnToggleAction(EntityUid uid, NinjaGlovesComponent comp, ToggleActionEvent args)
- {
- if (args.Handled)
- return;
-
- args.Handled = true;
-
- var user = args.Performer;
- // need to wear suit to enable gloves
- if (!TryComp(user, out var ninja)
- || ninja.Suit == null
- || !HasComp(ninja.Suit.Value))
- {
- Popup.PopupEntity(Loc.GetString("ninja-gloves-not-wearing-suit"), user, user);
- 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)
- {
- EnableGloves(uid, comp, user, ninja);
- }
- 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)
+ if (user.Comp.Suit is not {} suit)
return;
- comp.User = user;
- Dirty(uid, comp);
- _ninja.AssignGloves(user, uid, ninja);
+ if (!_mind.TryGetMind(user, out var mindId, out var mind))
+ return;
- var drainer = EnsureComp(user);
- var stun = EnsureComp(user);
- _stunProvider.SetNoPowerPopup(user, "ninja-no-power", stun);
+ foreach (var ability in ent.Comp.Abilities)
+ {
+ // 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);
+ }
+
+ // 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(user);
- _emagProvider.SetWhitelist(user, comp.DoorjackWhitelist, emag);
-
- EnsureComp(user);
- // prevent calling in multiple threats by toggling gloves after
- if (!_codeCondition.IsCompleted(user, ninja.TerrorObjective))
- {
- var hacker = EnsureComp(user);
- var rule = _ninja.NinjaRule(user);
- if (rule != null)
- _commsHacker.SetThreats(user, rule.Threats, hacker);
- }
- if (!_codeCondition.IsCompleted(user, ninja.MassArrestObjective))
- {
- EnsureComp(user);
- }
}
}
diff --git a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs
index 04095b549c..63054eaad5 100644
--- a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs
+++ b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs
@@ -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(OnSuitInsertAttempt);
SubscribeLocalEvent(OnEmpAttempt);
- SubscribeLocalEvent(OnAttemptStealth);
- SubscribeLocalEvent(OnCreateThrowingStar);
SubscribeLocalEvent(OnRecallKatana);
SubscribeLocalEvent(OnEmp);
}
- protected override void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
+ protected override void NinjaEquipped(Entity ent, Entity 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(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(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 ent, Entity 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 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(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 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);
}
diff --git a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
index 0c1e88653f..28ab633227 100644
--- a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
+++ b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
@@ -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
-
///
/// Main ninja system that handles ninja setup, provides helper methods for the rest of the code to use.
///
@@ -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(OnNinjaCreated);
SubscribeLocalEvent(OnDoorjack);
SubscribeLocalEvent(OnResearchStolen);
SubscribeLocalEvent(OnThreatCalledIn);
@@ -62,7 +53,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
var query = EntityQueryEnumerator();
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;
}
- ///
- /// Returns a ninja's gamerule config data.
- /// If the gamerule was not started then it will be started automatically.
- ///
- 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(comp.RuleEntity);
- }
-
// TODO: can probably copy paste borg code here
///
/// Update the alert for the ninja's suit power indicator.
///
- public void SetSuitPowerAlert(EntityUid uid, SpaceNinjaComponent? comp = null)
+ public void SetSuitPowerAlert(Entity 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);
}
- ///
- /// Set up everything for ninja to work and send the greeting message/sound.
- /// Objectives are added by .
- ///
- 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
- ///
- /// Handle constant power drains from passive usage and cloak.
- ///
- 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);
- }
- }
-
///
/// Increment greentext when emagging a door.
///
diff --git a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
index 64c958d6f1..c262651f27 100644
--- a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
+++ b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
@@ -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;
///
/// Prevents planting a spider charge outside of its location and handles greentext.
///
-public sealed class SpiderChargeSystem : EntitySystem
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
{
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly PopupSystem _popup = default!;
diff --git a/Content.Server/Ninja/Systems/StunProviderSystem.cs b/Content.Server/Ninja/Systems/StunProviderSystem.cs
index 1768606ad2..822486cff5 100644
--- a/Content.Server/Ninja/Systems/StunProviderSystem.cs
+++ b/Content.Server/Ninja/Systems/StunProviderSystem.cs
@@ -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
///
/// Stun clicked mobs on the whitelist, if there is enough power.
///
- private void OnBeforeInteractHand(EntityUid uid, StunProviderComponent comp, BeforeInteractHandEvent args)
+ private void OnBeforeInteractHand(Entity 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(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 ent, ref NinjaBatteryChangedEvent args)
{
- SetBattery(uid, args.Battery, comp);
+ SetBattery((ent, ent.Comp), args.Battery);
}
}
diff --git a/Content.Server/Objectives/Systems/CodeConditionSystem.cs b/Content.Server/Objectives/Systems/CodeConditionSystem.cs
index 7ba312f4bb..fbc58dafe8 100644
--- a/Content.Server/Objectives/Systems/CodeConditionSystem.cs
+++ b/Content.Server/Objectives/Systems/CodeConditionSystem.cs
@@ -35,20 +35,6 @@ public sealed class CodeConditionSystem : EntitySystem
return ent.Comp.Completed;
}
- ///
- /// Returns true if a mob's objective with a certain prototype is completed.
- ///
- public bool IsCompleted(Entity 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);
- }
-
///
/// Sets an objective's completed field.
///
diff --git a/Content.Server/PowerCell/PowerCellSystem.Draw.cs b/Content.Server/PowerCell/PowerCellSystem.Draw.cs
index 4155a4f6be..9ebd677f47 100644
--- a/Content.Server/PowerCell/PowerCellSystem.Draw.cs
+++ b/Content.Server/PowerCell/PowerCellSystem.Draw.cs
@@ -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();
+ var query = EntityQueryEnumerator();
- 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;
+ var canUse = component.UseRate <= 0f || args.Charge > component.UseRate;
- if (component.UseRate > 0f)
- {
- canUse = args.Charge > component.UseRate;
- }
- else
- {
- canUse = true;
- }
-
- 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;
diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs
index f45a01b2e1..0d2d9012bc 100644
--- a/Content.Server/PowerCell/PowerCellSystem.cs
+++ b/Content.Server/PowerCell/PowerCellSystem.cs
@@ -39,8 +39,8 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
SubscribeLocalEvent(OnDrawChargeChanged);
SubscribeLocalEvent(OnDrawCellChanged);
- // funny
SubscribeLocalEvent(OnCellSlotExamined);
+ // funny
SubscribeLocalEvent(OnSlotMicrowaved);
}
diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
index 746d75f0d8..f289752b7c 100644
--- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
+++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
@@ -29,7 +29,7 @@ public sealed partial class BorgSystem
if (!TryComp(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);
}
///
@@ -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)
diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs
index c97ca9cbc0..1c40e9489e 100644
--- a/Content.Server/Silicons/Borgs/BorgSystem.cs
+++ b/Content.Server/Silicons/Borgs/BorgSystem.cs
@@ -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(OnPowerCellChanged);
SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnGetDeadIC);
+ SubscribeLocalEvent(OnToggled);
SubscribeLocalEvent(OnBrainMindAdded);
SubscribeLocalEvent(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(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 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);
}
- ///
- /// Activates the borg, enabling all of its modules.
- ///
- 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);
- }
-
- ///
- /// Deactivates the borg, disabling all of its modules and decreasing its speed.
- ///
- public void DisableBorgAbilities(EntityUid uid, BorgChassisComponent component)
- {
- if (!component.Activated)
- return;
-
- component.Activated = false;
- DisableAllModules(uid, component);
- Dirty(uid, component);
- _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
- }
-
///
/// Activates a borg when a player occupies it
///
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);
}
///
@@ -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);
}
///
diff --git a/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs
deleted file mode 100644
index d758247eca..0000000000
--- a/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Server.StationEvents.Events;
-
-namespace Content.Server.StationEvents.Components;
-
-///
-/// Configuration component for the Space Ninja antag.
-///
-[RegisterComponent, Access(typeof(NinjaSpawnRule))]
-public sealed partial class NinjaSpawnRuleComponent : Component
-{
- ///
- /// Distance that the ninja spawns from the station's half AABB radius
- ///
- [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
index 0000000000..a0168077fd
--- /dev/null
+++ b/Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs
@@ -0,0 +1,25 @@
+using Content.Server.StationEvents.Events;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.StationEvents.Components;
+
+///
+/// Component for spawning antags in space around a station.
+/// Requires AntagSelectionComponent.
+///
+[RegisterComponent, Access(typeof(SpaceSpawnRule))]
+public sealed partial class SpaceSpawnRuleComponent : Component
+{
+ ///
+ /// Distance that the entity spawns from the station's half AABB radius
+ ///
+ [DataField]
+ public float SpawnDistance = 20f;
+
+ ///
+ /// Location that was picked.
+ ///
+ [DataField]
+ public MapCoordinates? Coords;
+}
diff --git a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs b/Content.Server/StationEvents/Events/SpaceSpawnRule.cs
similarity index 53%
rename from Content.Server/StationEvents/Events/NinjaSpawnRule.cs
rename to Content.Server/StationEvents/Events/SpaceSpawnRule.cs
index 9cbc193ce6..6fccaaa5cf 100644
--- a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs
+++ b/Content.Server/StationEvents/Events/SpaceSpawnRule.cs
@@ -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;
///
-/// Event for spawning a Space Ninja mid-game.
+/// Station event component for spawning this rules antags in space around a station.
///
-public sealed class NinjaSpawnRule : StationEventSystem
+public sealed class SpaceSpawnRule : StationEventSystem
{
[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(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(station.Value);
@@ -28,22 +38,28 @@ public sealed class NinjaSpawnRule : StationEventSystem
var gridUid = StationSystem.GetLargestGrid(stationData);
if (gridUid == null || !TryComp(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 ent, ref AntagSelectLocationEvent args)
+ {
+ if (ent.Comp.Coords is {} coords)
+ args.Coordinates.Add(coords);
}
}
diff --git a/Content.Server/Stunnable/Systems/StunbatonSystem.cs b/Content.Server/Stunnable/Systems/StunbatonSystem.cs
index c1782efaba..97dd2c7e73 100644
--- a/Content.Server/Stunnable/Systems/StunbatonSystem.cs
+++ b/Content.Server/Stunnable/Systems/StunbatonSystem.cs
@@ -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()
{
diff --git a/Content.Server/Weapons/Misc/TetherGunSystem.cs b/Content.Server/Weapons/Misc/TetherGunSystem.cs
index f6aafe376d..2bf53d46f4 100644
--- a/Content.Server/Weapons/Misc/TetherGunSystem.cs
+++ b/Content.Server/Weapons/Misc/TetherGunSystem.cs
@@ -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);
}
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
index c6f56a2750..a585a9ef45 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
@@ -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();
- while (query.MoveNext(out _, out var magboot, out var magXform))
+ //assume that there's more magboots than artifacts
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out _, out var magboot, out var magXform, out var toggle))
{
- if (!magboot.On)
+ if (!toggle.Activated)
continue;
var artiQuery = EntityQueryEnumerator();
diff --git a/Content.Shared/Access/Components/AccessToggleComponent.cs b/Content.Shared/Access/Components/AccessToggleComponent.cs
new file mode 100644
index 0000000000..60a606ac7e
--- /dev/null
+++ b/Content.Shared/Access/Components/AccessToggleComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Access.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Access.Components;
+
+///
+/// Toggles an access provider with ItemToggle.
+/// Requires .
+///
+[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
index 0000000000..564aca0681
--- /dev/null
+++ b/Content.Shared/Access/Systems/AccessToggleSystem.cs
@@ -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(OnToggled);
+ }
+
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
+ {
+ _access.SetAccessEnabled(ent, args.Activated);
+ }
+}
diff --git a/Content.Shared/Beeper/Components/BeeperComponent.cs b/Content.Shared/Beeper/Components/BeeperComponent.cs
index 54d242709c..f6efbb10f3 100644
--- a/Content.Shared/Beeper/Components/BeeperComponent.cs
+++ b/Content.Shared/Beeper/Components/BeeperComponent.cs
@@ -10,15 +10,12 @@ namespace Content.Shared.Beeper.Components;
/// This is used for an item that beeps based on
/// proximity to a specified component.
///
+///
+/// Requires ItemToggleComponent to control it.
+///
[RegisterComponent, NetworkedComponent, Access(typeof(BeeperSystem)), AutoGenerateComponentState]
public sealed partial class BeeperComponent : Component
{
- ///
- /// Whether or not it's on.
- ///
- [DataField, AutoNetworkedField]
- public bool Enabled = true;
-
///
/// How much to scale the interval by (< 0 = min, > 1 = max)
///
@@ -56,7 +53,7 @@ public sealed partial class BeeperComponent : Component
/// Is the beep muted
///
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public bool IsMuted = false;
+ public bool IsMuted;
///
/// The sound played when the locator beeps.
diff --git a/Content.Shared/Beeper/Systems/BeeperSystem.cs b/Content.Shared/Beeper/Systems/BeeperSystem.cs
index c51eef4da9..a52e19f755 100644
--- a/Content.Shared/Beeper/Systems/BeeperSystem.cs
+++ b/Content.Shared/Beeper/Systems/BeeperSystem.cs
@@ -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();
- while (query.MoveNext(out var uid, out var beeper))
+ var query = EntityQueryEnumerator();
+ 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;
}
}
diff --git a/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
index bd857d4c29..ed3c6366c1 100644
--- a/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
+++ b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
@@ -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;
///
-/// This handles logic for implementing proximity beeper as a handheld tool />
+/// This handles controlling a beeper from proximity detector events.
///
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!;
///
public override void Initialize()
{
- SubscribeLocalEvent(OnUseInHand);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnNewProximityTarget);
SubscribeLocalEvent(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);
+
+ _beeper.SetIntervalScaling(owner, args.Distance / args.Detector.Range, beeper);
+ _beeper.SetMute(owner, false, 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);
- }
-
- 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);
- }
-
-
- ///
- /// Disables the proximity beeper
- ///
- 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);
- }
-
- ///
- /// toggles the proximity beeper
- ///
- public bool TryToggle(EntityUid owner, ProximityBeeperComponent? proxBeeper = null, BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null,
- PowerCellDrawComponent? draw = null, EntityUid? user = null)
- {
- 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);
}
}
diff --git a/Content.Shared/Charges/Systems/SharedChargesSystem.cs b/Content.Shared/Charges/Systems/SharedChargesSystem.cs
index 5de1383cde..7f95ef184e 100644
--- a/Content.Shared/Charges/Systems/SharedChargesSystem.cs
+++ b/Content.Shared/Charges/Systems/SharedChargesSystem.cs
@@ -5,10 +5,14 @@ namespace Content.Shared.Charges.Systems;
public abstract class SharedChargesSystem : EntitySystem
{
+ protected EntityQuery Query;
+
public override void Initialize()
{
base.Initialize();
+ Query = GetEntityQuery();
+
SubscribeLocalEvent(OnExamine);
}
@@ -30,9 +34,9 @@ public abstract class SharedChargesSystem : EntitySystem
///
/// Tries to add a number of charges. If it over or underflows it will be clamped, wasting the extra charges.
///
- 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
///
/// Uses a single charge. Must check IsEmpty beforehand to prevent using with 0 charge.
///
- public virtual void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+ public void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
{
- if (Resolve(uid, ref comp, false))
- AddCharges(uid, -1, comp);
+ AddCharges(uid, -1, comp);
+ }
+
+ ///
+ /// Checks IsEmpty and uses a charge if it isn't empty.
+ ///
+ public bool TryUseCharge(Entity ent)
+ {
+ if (!Query.Resolve(ent, ref ent.Comp, false))
+ return true;
+
+ if (IsEmpty(ent, ent.Comp))
+ return false;
+
+ UseCharge(ent, ent.Comp);
+ return true;
}
///
@@ -80,7 +98,6 @@ public abstract class SharedChargesSystem : EntitySystem
///
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);
}
}
diff --git a/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs b/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
index c3c4baf19d..866ce38a57 100644
--- a/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
+++ b/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
@@ -3,20 +3,18 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Clothing;
+///
+/// Modifies speed when worn and activated.
+/// Supports ItemToggleComponent.
+///
[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;
-
- ///
- /// Is this clothing item currently 'actively' slowing you down?
- /// e.g. magboots can be turned on and off.
- ///
- [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;
}
}
diff --git a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
index 4198c5c165..c1efe0b3dd 100644
--- a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
+++ b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
@@ -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(OnHandleState);
SubscribeLocalEvent>(OnRefreshMoveSpeed);
SubscribeLocalEvent>(OnClothingVerbExamine);
-
- SubscribeLocalEvent>(AddToggleVerb);
- SubscribeLocalEvent(OnGetActions);
- SubscribeLocalEvent(OnToggleSpeed);
- SubscribeLocalEvent(OnMapInit);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
+ SubscribeLocalEvent(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 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 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 uid, ref MapInitEvent args)
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
{
- _actions.AddAction(uid, ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
- }
+ // make sentient boots slow or fast too
+ _movementSpeed.RefreshMovementSpeedModifiers(ent);
- private void OnToggleSpeed(Entity uid, ref ToggleClothingSpeedEvent args)
- {
- if (args.Handled)
- return;
-
- args.Handled = true;
- SetSpeedToggleEnabled(uid, !uid.Comp.Enabled, args.Performer);
- }
-
- private void SetSpeedToggleEnabled(Entity uid, bool value, EntityUid? user)
- {
- if (uid.Comp.Enabled == value)
- return;
-
- TryComp(uid, out var draw);
- if (value && !_powerCell.HasDrawCharge(uid, draw, user: user))
- return;
-
- 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 uid, ref GetVerbsEvent 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 uid, ref GetItemActionsEvent args)
- {
- args.AddAction(ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
- }
-
- private void OnPowerCellSlotEmpty(Entity 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
index fedf48b36e..0000000000
--- a/Content.Shared/Clothing/Components/StealthClothingComponent.cs
+++ /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;
-
-///
-/// Adds StealthComponent to the user when enabled, either by an action or the system's SetEnabled method.
-///
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(StealthClothingSystem))]
-public sealed partial class StealthClothingComponent : Component
-{
- ///
- /// Whether stealth effect is enabled.
- ///
- [DataField("enabled"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public bool Enabled;
-
- ///
- /// Number added to MinVisibility when stealthed, to make the user not fully invisible.
- ///
- [DataField("visibility"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public float Visibility;
-
- [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string ToggleAction = "ActionTogglePhaseCloak";
-
- ///
- /// The action for enabling and disabling stealth.
- ///
- [DataField, AutoNetworkedField] public EntityUid? ToggleActionEntity;
-}
-
-///
-/// When stealth is enabled, disables it.
-/// When it is disabled, raises before enabling.
-/// Put any checks in a handler for that event to cancel it.
-///
-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
index 0000000000..c77aa03475
--- /dev/null
+++ b/Content.Shared/Clothing/Components/ToggleClothingComponent.cs
@@ -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;
+
+///
+/// Clothing that can be enabled and disabled with an action.
+/// Requires .
+///
+///
+/// Not to be confused with for hardsuit helmets and such.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(ToggleClothingSystem))]
+public sealed partial class ToggleClothingComponent : Component
+{
+ ///
+ /// The action to add when equipped, even if not worn.
+ /// This must raise to then get handled.
+ ///
+ [DataField(required: true)]
+ public EntProtoId Action = string.Empty;
+
+ [DataField, AutoNetworkedField]
+ public EntityUid? ActionEntity;
+
+ ///
+ /// If true, automatically disable the clothing after unequipping it.
+ ///
+ [DataField]
+ public bool DisableOnUnequip;
+}
+
+///
+/// Raised on the clothing when being equipped to see if it should add the action.
+///
+[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
index 90b2d7322e..0000000000
--- a/Content.Shared/Clothing/Components/ToggleClothingSpeedComponent.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Content.Shared.Actions;
-using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Clothing.Components;
-
-///
-/// This is used for a clothing item that gives a speed modification that is toggleable.
-///
-[RegisterComponent, NetworkedComponent, Access(typeof(ClothingSpeedModifierSystem)), AutoGenerateComponentState]
-public sealed partial class ToggleClothingSpeedComponent : Component
-{
- ///
- /// The action for toggling the clothing.
- ///
- [DataField]
- public EntProtoId ToggleAction = "ActionToggleSpeedBoots";
-
- ///
- /// The action entity
- ///
- [DataField, AutoNetworkedField]
- public EntityUid? ToggleActionEntity;
-
- ///
- /// The state of the toggle.
- ///
- [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
index e96d9f866a..0000000000
--- a/Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs
+++ /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;
-
-///
-/// Handles the toggle action and disables stealth when clothing is unequipped.
-///
-public sealed class StealthClothingSystem : EntitySystem
-{
- [Dependency] private readonly SharedStealthSystem _stealth = default!;
- [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnGetItemActions);
- SubscribeLocalEvent(OnToggleStealth);
- SubscribeLocalEvent(OnHandleState);
- SubscribeLocalEvent(OnUnequipped);
- SubscribeLocalEvent(OnMapInit);
- }
-
- private void OnMapInit(EntityUid uid, StealthClothingComponent component, MapInitEvent args)
- {
- _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
- Dirty(uid, component);
- }
-
- ///
- /// Sets the clothing's stealth effect for the user.
- ///
- /// True if it was changed, false otherwise
- 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(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;
- }
-
- ///
- /// Raise then add the toggle action if it was not cancelled.
- ///
- 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);
- }
-
- ///
- /// Raises if enabling.
- ///
- 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);
- }
-
- ///
- /// Calls when server sends new state.
- ///
- 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);
- }
-
- ///
- /// Force unstealths the user, doesnt remove StealthComponent since other things might use it
- ///
- private void OnUnequipped(EntityUid uid, StealthClothingComponent comp, GotUnequippedEvent args)
- {
- SetEnabled(uid, args.Equipee, false, comp);
- }
-}
-
-///
-/// Raised on the stealth clothing when attempting to add an action.
-///
-public sealed class AddStealthActionEvent : CancellableEntityEventArgs
-{
- ///
- /// User that equipped the stealth clothing.
- ///
- public EntityUid User;
-
- public AddStealthActionEvent(EntityUid user)
- {
- User = user;
- }
-}
-
-///
-/// Raised on the stealth clothing when the user is attemping to enable it.
-///
-public sealed class AttemptStealthEvent : CancellableEntityEventArgs
-{
- ///
- /// User that is attempting to enable the stealth clothing.
- ///
- 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
index 0000000000..9889376c9d
--- /dev/null
+++ b/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs
@@ -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;
+
+///
+/// Handles adding and using a toggle action for .
+///
+public sealed class ToggleClothingSystem : EntitySystem
+{
+ [Dependency] private readonly SharedActionsSystem _actions = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnGetActions);
+ SubscribeLocalEvent(OnToggleAction);
+ SubscribeLocalEvent(OnUnequipped);
+ }
+
+ private void OnMapInit(Entity 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 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 ent, ref ToggleActionEvent args)
+ {
+ args.Handled = _toggle.Toggle(ent.Owner, args.Performer);
+ }
+
+ private void OnUnequipped(Entity ent, ref ClothingGotUnequippedEvent args)
+ {
+ if (ent.Comp.DisableOnUnequip)
+ _toggle.TryDeactivate(ent.Owner, args.Wearer);
+ }
+}
diff --git a/Content.Shared/Clothing/MagbootsComponent.cs b/Content.Shared/Clothing/MagbootsComponent.cs
index b3fb607a38..4bef74fd33 100644
--- a/Content.Shared/Clothing/MagbootsComponent.cs
+++ b/Content.Shared/Clothing/MagbootsComponent.cs
@@ -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 MagbootsAlert = "Magboots";
@@ -26,4 +16,10 @@ public sealed partial class MagbootsComponent : Component
///
[DataField]
public bool RequiresGrid = true;
+
+ ///
+ /// Slot the clothing has to be worn in to work.
+ ///
+ [DataField]
+ public string Slot = "shoes";
}
diff --git a/Content.Shared/Clothing/MagbootsSystem.cs b/Content.Shared/Clothing/MagbootsSystem.cs
new file mode 100644
index 0000000000..88d987aae1
--- /dev/null
+++ b/Content.Shared/Clothing/MagbootsSystem.cs
@@ -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(OnToggled);
+ SubscribeLocalEvent(OnGotEquipped);
+ SubscribeLocalEvent(OnGotUnequipped);
+ SubscribeLocalEvent(OnIsWeightless);
+ SubscribeLocalEvent>(OnIsWeightless);
+ }
+
+ private void OnToggled(Entity 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 ent, ref ClothingGotUnequippedEvent args)
+ {
+ UpdateMagbootEffects(args.Wearer, ent, false);
+ }
+
+ private void OnGotEquipped(Entity ent, ref ClothingGotEquippedEvent args)
+ {
+ UpdateMagbootEffects(args.Wearer, ent, _toggle.IsActivated(ent.Owner));
+ }
+
+ public void UpdateMagbootEffects(EntityUid user, Entity ent, bool state)
+ {
+ // TODO: public api for this and add access
+ if (TryComp(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 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 ent, ref InventoryRelayedEvent args)
+ {
+ OnIsWeightless(ent, ref args.Args);
+ }
+}
diff --git a/Content.Shared/Clothing/SharedMagbootsSystem.cs b/Content.Shared/Clothing/SharedMagbootsSystem.cs
deleted file mode 100644
index 6814593615..0000000000
--- a/Content.Shared/Clothing/SharedMagbootsSystem.cs
+++ /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>(AddToggleVerb);
- SubscribeLocalEvent>(OnSlipAttempt);
- SubscribeLocalEvent(OnGetActions);
- SubscribeLocalEvent(OnToggleMagboots);
- SubscribeLocalEvent(OnMapInit);
-
- SubscribeLocalEvent(OnGotEquipped);
- SubscribeLocalEvent(OnGotUnequipped);
-
- SubscribeLocalEvent>(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(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 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 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 ent, ref InventoryRelayedEvent 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
index 0000000000..760cefe27d
--- /dev/null
+++ b/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs
@@ -0,0 +1,26 @@
+using Content.Shared.Item.ItemToggle.Components;
+
+namespace Content.Shared.Item.ItemToggle;
+
+///
+/// Handles component manipulation.
+///
+public sealed class ComponentTogglerSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnToggled);
+ }
+
+ private void OnToggled(Entity 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
index 0000000000..20ef0a0231
--- /dev/null
+++ b/Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs
@@ -0,0 +1,32 @@
+using Content.Shared.Item.ItemToggle;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Item.ItemToggle.Components;
+
+///
+/// Adds or removes components when toggled.
+/// Requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(ComponentTogglerSystem))]
+public sealed partial class ComponentTogglerComponent : Component
+{
+ ///
+ /// The components to add when activated.
+ ///
+ [DataField(required: true)]
+ public ComponentRegistry Components = new();
+
+ ///
+ /// The components to remove when deactivated.
+ /// If this is null is reused.
+ ///
+ [DataField]
+ public ComponentRegistry? RemoveComponents;
+
+ ///
+ /// If true, adds components on the entity's parent instead of the entity itself.
+ ///
+ [DataField]
+ public bool Parent;
+}
diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleActiveSoundComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleActiveSoundComponent.cs
index 6d53471357..cdac49ae6d 100644
--- a/Content.Shared/Item/ItemToggle/Components/ItemToggleActiveSoundComponent.cs
+++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleActiveSoundComponent.cs
@@ -12,12 +12,12 @@ public sealed partial class ItemToggleActiveSoundComponent : Component
///
/// The continuous noise this item makes when it's activated (like an e-sword's hum).
///
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField(required: true), AutoNetworkedField]
public SoundSpecifier? ActiveSound;
///
/// Used when the item emits sound while active.
///
- [ViewVariables(VVAccess.ReadWrite), DataField]
+ [DataField]
public EntityUid? PlayingStream;
}
diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
index 620ddfd194..46249fdd0d 100644
--- a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
+++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
@@ -8,7 +8,7 @@ namespace Content.Shared.Item.ItemToggle.Components;
///
///
/// If you need extended functionality (e.g. requiring power) then add a new component and use events:
-/// ItemToggleActivateAttemptEvent, ItemToggleDeactivateAttemptEvent or ItemToggleForceToggleEvent.
+/// ItemToggleActivateAttemptEvent, ItemToggleDeactivateAttemptEvent, ItemToggledEvent.
///
[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;
+ ///
+ /// If this is set to false then the item can't be toggled by pressing Z.
+ /// Use another system to do it then.
+ ///
+ [DataField]
+ public bool OnUse = true;
+
///
/// Whether the item's toggle can be predicted by the client.
///
diff --git a/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs b/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs
new file mode 100644
index 0000000000..b673c55e0f
--- /dev/null
+++ b/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs
@@ -0,0 +1,18 @@
+using Content.Shared.Item.ItemToggle;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Item.ItemToggle.Components;
+
+///
+/// Adds a verb for toggling something, requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(ToggleVerbSystem))]
+public sealed partial class ToggleVerbComponent : Component
+{
+ ///
+ /// Text the verb will have.
+ /// Gets passed "entity" as the entity's identity string.
+ ///
+ [DataField(required: true)]
+ public LocId Text = string.Empty;
+}
diff --git a/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
similarity index 54%
rename from Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs
rename to Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
index 523f67bac3..6b969d1d62 100644
--- a/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs
+++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
@@ -15,12 +15,12 @@ namespace Content.Shared.Item.ItemToggle;
///
/// If you need extended functionality (e.g. requiring power) then add a new component and use events.
///
-public abstract class SharedItemToggleSystem : EntitySystem
+public sealed class ItemToggleSystem : EntitySystem
{
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedPointLightSystem _light = 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 SharedPopupSystem _popup = default!;
public override void Initialize()
@@ -28,8 +28,9 @@ public abstract class SharedItemToggleSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent(OnStartup);
- SubscribeLocalEvent(TurnOffonUnwielded);
- SubscribeLocalEvent(TurnOnonWielded);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(TurnOffOnUnwielded);
+ SubscribeLocalEvent(TurnOnOnWielded);
SubscribeLocalEvent(OnUseInHand);
SubscribeLocalEvent(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 ent, ref MapInitEvent args)
{
- if (args.Handled)
+ 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 ent, ref UseInHandEvent args)
+ {
+ 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);
}
///
/// Used when an item is attempted to be toggled.
+ /// Sets its state to the opposite of what it is.
///
- public void Toggle(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
+ /// Same as
+ public bool Toggle(Entity 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);
+ }
+
+ ///
+ /// Tries to set the activated bool from a value.
+ ///
+ /// false if the attempt fails for any reason
+ public bool TrySetActive(Entity 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);
}
///
/// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation.
///
- public bool TryActivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
+ public bool TryActivate(Entity 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
///
/// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation.
///
- public bool TryDeactivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
+ public bool TryDeactivate(Entity 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 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);
}
///
/// Used to make the actual changes to the item's components on deactivation.
///
- private void Deactivate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null)
+ private void Deactivate(Entity 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 ent)
@@ -204,55 +214,56 @@ public abstract class SharedItemToggleSystem : EntitySystem
///
/// 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.
///
- private void TurnOffonUnwielded(EntityUid uid, ItemToggleComponent itemToggle, ItemUnwieldedEvent args)
+ private void TurnOffOnUnwielded(Entity ent, ref ItemUnwieldedEvent args)
{
- if (itemToggle.Activated)
- TryDeactivate(uid, args.User, itemToggle: itemToggle);
+ TryDeactivate((ent, ent.Comp), args.User);
}
///
/// Wieldable items will automatically turn on when wielded.
///
- private void TurnOnonWielded(EntityUid uid, ItemToggleComponent itemToggle, ref ItemWieldedEvent args)
+ private void TurnOnOnWielded(Entity 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 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;
}
///
/// Used to make the item hot when activated.
///
- private void OnIsHotEvent(EntityUid uid, ItemToggleHotComponent itemToggleHot, IsHotEvent args)
+ private void OnIsHotEvent(Entity ent, ref IsHotEvent args)
{
- args.IsHot |= IsActivated(uid);
+ args.IsHot |= IsActivated(ent.Owner);
}
///
/// Used to update the looping active sound linked to the entity.
///
- private void UpdateActiveSound(EntityUid uid, ItemToggleActiveSoundComponent activeSound, ref ItemToggledEvent args)
+ private void UpdateActiveSound(Entity 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
index 0000000000..858cd9bc11
--- /dev/null
+++ b/Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs
@@ -0,0 +1,34 @@
+using Content.Shared.IdentityManagement;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Verbs;
+
+namespace Content.Shared.Item.ItemToggle;
+
+///
+/// Adds a verb for toggling something with .
+///
+public sealed class ToggleVerbSystem : EntitySystem
+{
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent>(OnGetVerbs);
+ }
+
+ private void OnGetVerbs(Entity ent, ref GetVerbsEvent 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)
+ });
+ }
+}
diff --git a/Content.Shared/Medical/DefibrillatorComponent.cs b/Content.Shared/Medical/DefibrillatorComponent.cs
index 61a02187d0..e4cd8077d2 100644
--- a/Content.Shared/Medical/DefibrillatorComponent.cs
+++ b/Content.Shared/Medical/DefibrillatorComponent.cs
@@ -10,16 +10,11 @@ namespace Content.Shared.Medical;
///
/// This is used for defibrillators; a machine that shocks a dead
/// person back into the world of the living.
+/// Uses ItemToggleComponent
///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause]
public sealed partial class DefibrillatorComponent : Component
{
- ///
- /// Whether or not it's turned on and able to be used.
- ///
- [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
- public bool Enabled;
-
///
/// The time at which the zap cooldown will be completed
///
@@ -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");
- ///
- /// The sound when the defib is powered on.
- ///
- [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");
diff --git a/Content.Shared/Ninja/Components/BatteryDrainerComponent.cs b/Content.Shared/Ninja/Components/BatteryDrainerComponent.cs
index 55bcdd0f0a..9c39c4724c 100644
--- a/Content.Shared/Ninja/Components/BatteryDrainerComponent.cs
+++ b/Content.Shared/Ninja/Components/BatteryDrainerComponent.cs
@@ -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.
///
-[RegisterComponent, Access(typeof(SharedBatteryDrainerSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedBatteryDrainerSystem))]
public sealed partial class BatteryDrainerComponent : Component
{
///
/// The powercell entity to drain power into.
/// Determines whether draining is possible.
///
- [DataField("batteryUid"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField, AutoNetworkedField]
public EntityUid? BatteryUid;
///
/// 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.
///
- [DataField("drainEfficiency"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float DrainEfficiency = 0.001f;
///
/// Time that the do after takes to drain charge from a battery, in seconds
///
- [DataField("drainTime"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float DrainTime = 1f;
///
/// Sound played after the doafter ends.
///
- [DataField("sparkSound")]
+ [DataField]
public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks");
}
diff --git a/Content.Shared/Ninja/Components/BombingTargetComponent.cs b/Content.Shared/Ninja/Components/BombingTargetComponent.cs
index bf0eaec84b..c429eb6880 100644
--- a/Content.Shared/Ninja/Components/BombingTargetComponent.cs
+++ b/Content.Shared/Ninja/Components/BombingTargetComponent.cs
@@ -4,6 +4,4 @@ namespace Content.Shared.Ninja.Components;
/// Makes this warp point a valid bombing target for ninja's spider charge.
///
[RegisterComponent]
-public sealed partial class BombingTargetComponent : Component
-{
-}
+public sealed partial class BombingTargetComponent : Component;
diff --git a/Content.Shared/Ninja/Components/DashAbilityComponent.cs b/Content.Shared/Ninja/Components/DashAbilityComponent.cs
index ba4060c703..464f48f187 100644
--- a/Content.Shared/Ninja/Components/DashAbilityComponent.cs
+++ b/Content.Shared/Ninja/Components/DashAbilityComponent.cs
@@ -8,6 +8,7 @@ namespace Content.Shared.Ninja.Components;
///
/// Adds an action to dash, teleport to clicked position, when this item is held.
+/// Cancel to prevent using it.
///
[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.
///
[DataField]
- public EntProtoId DashAction = "ActionEnergyKatanaDash";
+ public EntProtoId DashAction = "ActionEnergyKatanaDash";
[DataField, AutoNetworkedField]
public EntityUid? DashActionEntity;
-
- ///
- /// Sound played when using dash action.
- ///
- [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;
diff --git a/Content.Shared/Ninja/Components/EmagProviderComponent.cs b/Content.Shared/Ninja/Components/EmagProviderComponent.cs
index db7678f61d..ae3e85cbe4 100644
--- a/Content.Shared/Ninja/Components/EmagProviderComponent.cs
+++ b/Content.Shared/Ninja/Components/EmagProviderComponent.cs
@@ -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.
///
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
-[Access(typeof(EmagProviderSystem))]
+[RegisterComponent, NetworkedComponent, Access(typeof(EmagProviderSystem))]
public sealed partial class EmagProviderComponent : Component
{
///
/// The tag that marks an entity as immune to emagging.
///
- [DataField("emagImmuneTag", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string EmagImmuneTag = "EmagImmune";
+ [DataField]
+ public ProtoId EmagImmuneTag = "EmagImmune";
///
/// Whitelist that entities must be on to work.
///
- [DataField("whitelist"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public EntityWhitelist? Whitelist = null;
+ [DataField]
+ public EntityWhitelist? Whitelist;
}
diff --git a/Content.Shared/Ninja/Components/EnergyKatanaComponent.cs b/Content.Shared/Ninja/Components/EnergyKatanaComponent.cs
index 33b8fc7893..84c58bb648 100644
--- a/Content.Shared/Ninja/Components/EnergyKatanaComponent.cs
+++ b/Content.Shared/Ninja/Components/EnergyKatanaComponent.cs
@@ -7,6 +7,4 @@ namespace Content.Shared.Ninja.Components;
/// Requires a ninja with a suit for abilities to work.
///
[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
index 0000000000..d9f66d21a3
--- /dev/null
+++ b/Content.Shared/Ninja/Components/ItemCreatorComponent.cs
@@ -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;
+
+///
+/// Uses battery charge to spawn an item and place it in the user's hands.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedItemCreatorSystem))]
+public sealed partial class ItemCreatorComponent : Component
+{
+ ///
+ /// The battery entity to use charge from
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityUid? Battery;
+
+ ///
+ /// The action id for creating an item.
+ ///
+ [DataField(required: true)]
+ public EntProtoId Action = string.Empty;
+
+ [DataField, AutoNetworkedField]
+ public EntityUid? ActionEntity;
+
+ ///
+ /// Battery charge used to create an item.
+ ///
+ [DataField(required: true)]
+ public float Charge = 14.4f;
+
+ ///
+ /// Item to create with the action
+ ///
+ [DataField(required: true)]
+ public EntProtoId SpawnedPrototype = string.Empty;
+
+ ///
+ /// Popup shown to the user when there isn't enough power to create an item.
+ ///
+ [DataField(required: true)]
+ public LocId NoPowerPopup = string.Empty;
+}
+
+///
+/// Action event to use an .
+///
+public sealed partial class CreateItemEvent : InstantActionEvent;
diff --git a/Content.Shared/Ninja/Components/NinjaGlovesComponent.cs b/Content.Shared/Ninja/Components/NinjaGlovesComponent.cs
index 7b57926330..3b9e2a5e35 100644
--- a/Content.Shared/Ninja/Components/NinjaGlovesComponent.cs
+++ b/Content.Shared/Ninja/Components/NinjaGlovesComponent.cs
@@ -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;
///
/// Component for toggling glove powers.
-/// Powers being enabled is controlled by User not being null.
///
+///
+/// Requires ItemToggleComponent.
+///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedNinjaGlovesSystem))]
public sealed partial class NinjaGlovesComponent : Component
@@ -22,24 +19,33 @@ public sealed partial class NinjaGlovesComponent : Component
///
/// Entity of the ninja using these gloves, usually means enabled
///
- [DataField("user"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? User;
///
- /// The action id for toggling ninja gloves abilities
+ /// Abilities to give to the user when enabled.
///
- [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string ToggleAction = "ActionToggleNinjaGloves";
+ [DataField(required: true)]
+ public List Abilities = new();
+}
- [DataField, AutoNetworkedField]
- public EntityUid? ToggleActionEntity;
+///
+/// An ability that adds components to the user when the gloves are enabled.
+///
+[DataRecord]
+public record struct NinjaGloveAbility()
+{
+ ///
+ /// 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 CodeConditionComponent to be checked.
+ ///
+ [DataField]
+ public EntProtoId? Objective;
///
- /// The whitelist used for the emag provider to emag airlocks only (not regular doors).
+ /// Components to add and remove.
///
- [DataField("doorjackWhitelist")]
- public EntityWhitelist DoorjackWhitelist = new()
- {
- Components = new[] {"Airlock"}
- };
+ [DataField(required: true)]
+ public ComponentRegistry Components = new();
}
diff --git a/Content.Shared/Ninja/Components/NinjaSuitComponent.cs b/Content.Shared/Ninja/Components/NinjaSuitComponent.cs
index 7e7b1ffcd3..8b477b2aa5 100644
--- a/Content.Shared/Ninja/Components/NinjaSuitComponent.cs
+++ b/Content.Shared/Ninja/Components/NinjaSuitComponent.cs
@@ -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.
///
-[RegisterComponent, NetworkedComponent, Access(typeof(SharedNinjaSuitSystem)), AutoGenerateComponentState]
-[AutoGenerateComponentPause]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedNinjaSuitSystem))]
public sealed partial class NinjaSuitComponent : Component
{
- ///
- /// Battery charge used passively, in watts. Will last 1000 seconds on a small-capacity power cell.
- ///
- [DataField("passiveWattage")]
- public float PassiveWattage = 0.36f;
-
- ///
- /// Battery charge used while cloaked, stacks with passive. Will last 200 seconds while cloaked on a small-capacity power cell.
- ///
- [DataField("cloakWattage")]
- public float CloakWattage = 1.44f;
-
///
/// Sound played when a ninja is hit while cloaked.
///
- [DataField("revealSound")]
+ [DataField]
public SoundSpecifier RevealSound = new SoundPathSpecifier("/Audio/Effects/chime.ogg");
///
- /// How long to disable all abilities when revealed.
- /// Normally, ninjas are revealed when attacking or getting damaged.
+ /// ID of the use delay to disable all ninja abilities.
///
- [DataField("disableTime")]
- public TimeSpan DisableTime = TimeSpan.FromSeconds(5);
-
- ///
- /// Time at which we will be able to use our abilities again
- ///
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoPausedField]
- public TimeSpan DisableCooldown;
-
- ///
- /// The action id for creating throwing stars.
- ///
- [DataField("createThrowingStarAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string CreateThrowingStarAction = "ActionCreateThrowingStar";
-
- [DataField, AutoNetworkedField]
- public EntityUid? CreateThrowingStarActionEntity;
-
- ///
- /// Battery charge used to create a throwing star. Can do it 25 times on a small-capacity power cell.
- ///
- [DataField("throwingStarCharge")]
- public float ThrowingStarCharge = 14.4f;
-
- ///
- /// Throwing star item to create with the action
- ///
- [DataField("throwingStarPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string ThrowingStarPrototype = "ThrowingStarNinja";
+ [DataField]
+ public string DisableDelayId = "suit_powers";
///
/// The action id for recalling a bound energy katana
///
- [DataField("recallKatanaAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- 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.
///
- [DataField("recallCharge")]
+ [DataField]
public float RecallCharge = 3.6f;
///
/// The action id for creating an EMP burst
///
- [DataField("empAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string EmpAction = "ActionNinjaEmp";
+ [DataField]
+ public EntProtoId EmpAction = "ActionNinjaEmp";
[DataField, AutoNetworkedField]
public EntityUid? EmpActionEntity;
@@ -99,36 +55,29 @@ public sealed partial class NinjaSuitComponent : Component
///
/// Battery charge used to create an EMP burst. Can do it 2 times on a small-capacity power cell.
///
- [DataField("empCharge")]
+ [DataField]
public float EmpCharge = 180f;
+ // TODO: EmpOnTrigger bruh
///
/// Range of the EMP in tiles.
///
- [DataField("empRange")]
+ [DataField]
public float EmpRange = 6f;
///
/// Power consumed from batteries by the EMP
///
- [DataField("empConsumption")]
+ [DataField]
public float EmpConsumption = 100000f;
///
/// How long the EMP effects last for, in seconds
///
- [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;
diff --git a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs
index 91c816df5c..a19537be1c 100644
--- a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs
+++ b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs
@@ -7,34 +7,28 @@ namespace Content.Shared.Ninja.Components;
///
/// 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.
///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedSpaceNinjaSystem))]
public sealed partial class SpaceNinjaComponent : Component
{
- ///
- /// The ninja game rule that spawned this ninja.
- ///
- [DataField("rule")]
- public EntityUid? Rule;
-
///
/// Currently worn suit
///
- [DataField("suit"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? Suit;
///
- /// Currently worn gloves
+ /// Currently worn gloves, if enabled.
///
- [DataField("gloves"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? Gloves;
///
/// Bound katana, set once picked up and never removed
///
- [DataField("katana"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? Katana;
///
@@ -55,6 +49,9 @@ public sealed partial class SpaceNinjaComponent : Component
[DataField]
public EntProtoId SpiderChargeObjective = "SpiderChargeObjective";
+ ///
+ /// Alert to show for suit power.
+ ///
[DataField]
public ProtoId SuitPowerAlert = "SuitPower";
}
diff --git a/Content.Shared/Ninja/Components/SpiderChargeComponent.cs b/Content.Shared/Ninja/Components/SpiderChargeComponent.cs
index dacf47bb23..3ba4494cca 100644
--- a/Content.Shared/Ninja/Components/SpiderChargeComponent.cs
+++ b/Content.Shared/Ninja/Components/SpiderChargeComponent.cs
@@ -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.
///
-[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;
}
diff --git a/Content.Shared/Ninja/Components/StunProviderComponent.cs b/Content.Shared/Ninja/Components/StunProviderComponent.cs
index 37a27074a4..2da094291d 100644
--- a/Content.Shared/Ninja/Components/StunProviderComponent.cs
+++ b/Content.Shared/Ninja/Components/StunProviderComponent.cs
@@ -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.
///
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStunProviderSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedStunProviderSystem))]
public sealed partial class StunProviderComponent : Component
{
///
/// The powercell entity to take power from.
/// Determines whether stunning is possible.
///
- [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? BatteryUid;
///
/// Sound played when stunning someone.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public SoundSpecifier Sound = new SoundCollectionSpecifier("sparks");
///
/// Joules required in the battery to stun someone. Defaults to 10 uses on a small battery.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float StunCharge = 36f;
///
/// Damage dealt when stunning someone
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public DamageSpecifier StunDamage = new()
{
DamageDict = new()
@@ -48,34 +48,30 @@ public sealed partial class StunProviderComponent : Component
///
/// Time that someone is stunned for, stacks if done multiple times.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public TimeSpan StunTime = TimeSpan.FromSeconds(5);
///
/// How long stunning is disabled after stunning something.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public TimeSpan Cooldown = TimeSpan.FromSeconds(2);
+ ///
+ /// ID of the cooldown use delay.
+ ///
+ [DataField]
+ public string DelayId = "stun_cooldown";
+
///
/// Locale string to popup when there is no power
///
- [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
- public string NoPowerPopup = string.Empty;
+ [DataField(required: true)]
+ public LocId NoPowerPopup = string.Empty;
///
/// Whitelist for what counts as a mob.
///
- [DataField]
- public EntityWhitelist Whitelist = new()
- {
- Components = new[] {"Stamina"}
- };
-
- ///
- /// When someone can next be stunned.
- /// Essentially a UseDelay unique to this component.
- ///
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan NextStun = TimeSpan.Zero;
+ [DataField(required: true)]
+ public EntityWhitelist Whitelist = new();
}
diff --git a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs
index 4853968b61..1385219e47 100644
--- a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs
+++ b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs
@@ -16,6 +16,7 @@ namespace Content.Shared.Ninja.Systems;
///
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(OnGetItemActions);
+ SubscribeLocalEvent(OnGetActions);
SubscribeLocalEvent(OnDash);
SubscribeLocalEvent(OnMapInit);
}
- private void OnMapInit(EntityUid uid, DashAbilityComponent component, MapInitEvent args)
+ private void OnMapInit(Entity 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 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);
}
///
/// Handle charges and teleport to a visible location.
///
- private void OnDash(EntityUid uid, DashAbilityComponent comp, DashEvent args)
+ private void OnDash(Entity 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(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;
+ }
+
+ var xform = Transform(user);
+ _transform.SetCoordinates(user, xform, args.Target);
+ _transform.AttachToGridOrMap(user, xform);
+ args.Handled = true;
+ }
+
+ public bool CheckDash(EntityUid uid, EntityUid user)
+ {
+ var ev = new CheckDashEvent(user);
+ RaiseLocalEvent(uid, ref ev);
+ return !ev.Cancelled;
}
}
///
-/// Raised on the item before adding the dash action
+/// Raised on the item before adding the dash action and when using the action.
///
-public sealed class AddDashActionEvent : CancellableEntityEventArgs
-{
- public EntityUid User;
-
- public AddDashActionEvent(EntityUid user)
- {
- User = user;
- }
-}
-
-///
-/// Raised on the item before dashing is done.
-///
-public sealed class DashAttemptEvent : CancellableEntityEventArgs
-{
- public EntityUid User;
-
- public DashAttemptEvent(EntityUid user)
- {
- User = user;
- }
-}
+[ByRefEvent]
+public record struct CheckDashEvent(EntityUid User, bool Cancelled = false);
diff --git a/Content.Shared/Ninja/Systems/EmagProviderSystem.cs b/Content.Shared/Ninja/Systems/EmagProviderSystem.cs
index 6838e7982c..ae0bacaf5f 100644
--- a/Content.Shared/Ninja/Systems/EmagProviderSystem.cs
+++ b/Content.Shared/Ninja/Systems/EmagProviderSystem.cs
@@ -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
///
/// Emag clicked entities that are on the whitelist.
///
- private void OnBeforeInteractHand(EntityUid uid, EmagProviderComponent comp, BeforeInteractHandEvent args)
+ private void OnBeforeInteractHand(Entity 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;
}
-
- ///
- /// Set the whitelist for emagging something outside of yaml.
- ///
- public void SetWhitelist(EntityUid uid, EntityWhitelist? whitelist, EmagProviderComponent? comp = null)
- {
- if (!Resolve(uid, ref comp))
- return;
-
- comp.Whitelist = whitelist;
- Dirty(uid, comp);
- }
}
///
diff --git a/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs b/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs
index d427ffa39b..281b97a648 100644
--- a/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs
+++ b/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs
@@ -15,33 +15,20 @@ public sealed class EnergyKatanaSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent(OnEquipped);
- SubscribeLocalEvent(OnAddDashAction);
- SubscribeLocalEvent(OnDashAttempt);
+ SubscribeLocalEvent(OnCheckDash);
}
///
/// When equipped by a ninja, try to bind it.
///
- private void OnEquipped(EntityUid uid, EnergyKatanaComponent comp, GotEquippedEvent args)
+ private void OnEquipped(Entity ent, ref GotEquippedEvent args)
{
- // check if user isnt a ninja or already has a katana bound
- var user = args.Equipee;
- if (!TryComp(user, out var ninja) || ninja.Katana != null)
- return;
-
- // bind it since its unbound
- _ninja.BindKatana(user, uid, ninja);
+ _ninja.BindKatana(args.Equipee, ent);
}
- private void OnAddDashAction(EntityUid uid, EnergyKatanaComponent comp, AddDashActionEvent args)
+ private void OnCheckDash(Entity ent, ref CheckDashEvent args)
{
- if (!HasComp(args.User))
- args.Cancel();
- }
-
- private void OnDashAttempt(EntityUid uid, EnergyKatanaComponent comp, DashAttemptEvent args)
- {
- if (!TryComp(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
index 0000000000..56112e9a69
--- /dev/null
+++ b/Content.Shared/Ninja/Systems/ItemCreatorSystem.cs
@@ -0,0 +1,56 @@
+using Content.Shared.Actions;
+using Content.Shared.Ninja.Components;
+
+namespace Content.Shared.Ninja.Systems;
+
+///
+/// Handles predicting that the action exists, creating items is done serverside.
+///
+public abstract class SharedItemCreatorSystem : EntitySystem
+{
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnGetActions);
+ }
+
+ private void OnMapInit(Entity 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 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;
+ }
+}
+
+///
+/// Raised on the item creator before adding the action.
+///
+[ByRefEvent]
+public record struct CheckItemCreatorEvent(EntityUid User, bool Cancelled = false);
+
+///
+/// Raised on the item creator before creating an item.
+///
+[ByRefEvent]
+public record struct CreateItemAttemptEvent(EntityUid User, bool Cancelled = false);
diff --git a/Content.Shared/Ninja/Systems/SharedBatteryDrainerSystem.cs b/Content.Shared/Ninja/Systems/SharedBatteryDrainerSystem.cs
index ac11063eb7..0abcca7d1b 100644
--- a/Content.Shared/Ninja/Systems/SharedBatteryDrainerSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedBatteryDrainerSystem.cs
@@ -18,34 +18,32 @@ public abstract class SharedBatteryDrainerSystem : EntitySystem
}
///
- /// 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.
///
- protected virtual void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent args)
+ protected virtual void OnDoAfterAttempt(Entity ent, ref DoAfterAttemptEvent args)
{
- if (comp.BatteryUid == null)
- {
+ if (ent.Comp.BatteryUid == null)
args.Cancel();
- }
}
///
/// Drain power from a power source (on server) and repeat if it succeeded.
/// Client will predict always succeeding since power is serverside.
///
- private void OnDoAfter(EntityUid uid, BatteryDrainerComponent comp, DrainDoAfterEvent args)
+ private void OnDoAfter(Entity 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);
}
///
/// 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.
///
- protected virtual bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
+ protected virtual bool TryDrainPower(Entity ent, EntityUid target)
{
return true;
}
@@ -53,12 +51,13 @@ public abstract class SharedBatteryDrainerSystem : EntitySystem
///
/// Sets the battery field on the drainer.
///
- public void SetBattery(EntityUid uid, EntityUid? battery, BatteryDrainerComponent? comp = null)
+ public void SetBattery(Entity 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 .
///
[Serializable, NetSerializable]
-public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent { }
+public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent;
diff --git a/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs b/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs
index f61d0c6a90..8b892190b7 100644
--- a/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs
@@ -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(OnGetItemActions);
+ SubscribeLocalEvent(OnToggleCheck);
+ SubscribeLocalEvent(OnActivateAttempt);
+ SubscribeLocalEvent(OnToggled);
SubscribeLocalEvent(OnExamined);
- SubscribeLocalEvent(OnUnequipped);
- SubscribeLocalEvent(OnMapInit);
- }
-
- private void OnMapInit(EntityUid uid, NinjaGlovesComponent component, MapInitEvent args)
- {
- _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
- Dirty(uid, component);
}
///
/// Disable glove abilities and show the popup if they were enabled previously.
///
- public void DisableGloves(EntityUid uid, NinjaGlovesComponent? comp = null)
+ private void DisableGloves(Entity 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(user);
- RemComp(user);
- RemComp(user);
- RemComp(user);
- RemComp(user);
- RemComp(user);
+ foreach (var ability in comp.Abilities)
+ {
+ EntityManager.RemoveComponents(user, ability.Components);
+ }
}
///
- /// Adds the toggle action when equipped.
+ /// Adds the toggle action when equipped by a ninja only.
///
- private void OnGetItemActions(EntityUid uid, NinjaGlovesComponent comp, GetItemActionsEvent args)
+ private void OnToggleCheck(Entity ent, ref ToggleClothingCheckEvent args)
{
- if (HasComp(args.User))
- args.AddAction(ref comp.ToggleActionEntity, comp.ToggleAction);
+ if (!_ninja.IsNinja(args.User))
+ args.Cancelled = true;
}
///
/// Show if the gloves are enabled when examining.
///
- private void OnExamined(EntityUid uid, NinjaGlovesComponent comp, ExaminedEvent args)
+ private void OnExamined(Entity 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}"));
}
- ///
- /// Disable gloves when unequipped and clean up ninja's gloves reference
- ///
- private void OnUnequipped(EntityUid uid, NinjaGlovesComponent comp, GotUnequippedEvent args)
+ private void OnActivateAttempt(Entity 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(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 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 ent, Entity 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
///
@@ -112,6 +130,6 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem
&& !_combatMode.IsInCombatMode(uid)
&& TryComp(uid, out var hands)
&& hands.ActiveHandEntity == null
- && Interaction.InRangeUnobstructed(uid, target);
+ && _interaction.InRangeUnobstructed(uid, target);
}
}
diff --git a/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs b/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs
index fed41eaed8..3800d15b26 100644
--- a/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs
@@ -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;
///
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(OnMapInit);
-
- SubscribeLocalEvent(OnEquipped);
+ SubscribeLocalEvent(OnEquipped);
SubscribeLocalEvent(OnGetItemActions);
- SubscribeLocalEvent(OnAddStealthAction);
+ SubscribeLocalEvent(OnCloakCheck);
+ SubscribeLocalEvent(OnStarCheck);
+ SubscribeLocalEvent(OnCreateStarAttempt);
+ SubscribeLocalEvent(OnActivateAttempt);
SubscribeLocalEvent(OnUnequipped);
}
- private void OnMapInit(EntityUid uid, NinjaSuitComponent component, MapInitEvent args)
+ private void OnEquipped(Entity 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));
}
- ///
- /// Call the shared and serverside code for when a ninja equips the suit.
- ///
- private void OnEquipped(EntityUid uid, NinjaSuitComponent comp, GotEquippedEvent args)
+ protected virtual void NinjaEquipped(Entity ent, Entity user)
{
- var user = args.Equipee;
- if (!TryComp(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 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);
}
///
/// Add all the actions when a suit is equipped by a ninja.
///
- private void OnGetItemActions(EntityUid uid, NinjaSuitComponent comp, GetItemActionsEvent args)
+ private void OnGetItemActions(Entity ent, ref GetItemActionsEvent args)
{
- if (!HasComp(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);
}
///
- /// Only add stealth clothing's toggle action when equipped by a ninja.
+ /// Only add toggle cloak action when equipped by a ninja.
///
- private void OnAddStealthAction(EntityUid uid, NinjaSuitComponent comp, AddStealthActionEvent args)
+ private void OnCloakCheck(Entity ent, ref ToggleClothingCheckEvent args)
{
- if (!HasComp(args.User))
- args.Cancel();
+ if (!_ninja.IsNinja(args.User))
+ args.Cancelled = true;
+ }
+
+ private void OnStarCheck(Entity ent, ref CheckItemCreatorEvent args)
+ {
+ if (!_ninja.IsNinja(args.User))
+ args.Cancelled = true;
+ }
+
+ private void OnCreateStarAttempt(Entity ent, ref CreateItemAttemptEvent args)
+ {
+ if (CheckDisabled(ent, args.User))
+ args.Cancelled = true;
}
///
/// Call the shared and serverside code for when anyone unequips a suit.
///
- private void OnUnequipped(EntityUid uid, NinjaSuitComponent comp, GotUnequippedEvent args)
+ private void OnUnequipped(Entity ent, ref GotUnequippedEvent args)
{
- UserUnequippedSuit(uid, comp, args.Equipee);
- }
-
- ///
- /// 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.
- ///
- protected virtual void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
- {
- // 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));
}
///
/// Force uncloaks the user and disables suit abilities.
///
- public void RevealNinja(EntityUid uid, EntityUid user, bool disable = true, NinjaSuitComponent? comp = null, StealthClothingComponent? stealthClothing = null)
+ public void RevealNinja(Entity 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);
}
- // TODO: modify PowerCellDrain
- ///
- /// Returns the power used by a suit
- ///
- public float SuitWattage(EntityUid uid, NinjaSuitComponent? suit = null)
+ private void OnActivateAttempt(Entity ent, ref ItemToggleActivateAttemptEvent args)
{
- if (!Resolve(uid, ref suit))
- return 0f;
+ if (!_ninja.IsNinja(args.User))
+ {
+ args.Cancelled = true;
+ return;
+ }
- float wattage = suit.PassiveWattage;
- if (TryComp(uid, out var stealthClothing) && stealthClothing.Enabled)
- wattage += suit.CloakWattage;
- return wattage;
+ if (IsDisabled((ent, ent.Comp, null)))
+ {
+ args.Cancelled = true;
+ args.Popup = Loc.GetString("ninja-suit-cooldown");
+ }
+ }
+
+ ///
+ /// Returns true if the suit is currently disabled
+ ///
+ public bool IsDisabled(Entity 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 ent, EntityUid user)
+ {
+ if (IsDisabled((ent, ent.Comp, null)))
+ {
+ Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
+ return true;
+ }
+
+ return false;
}
///
/// 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.
///
- protected virtual void UserUnequippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user)
+ protected virtual void UserUnequippedSuit(Entity ent, Entity user)
{
- if (!TryComp(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(ninja.Gloves.Value, out var gloves))
- _gloves.DisableGloves(ninja.Gloves.Value, gloves);
+ if (user.Comp.Gloves is {} uid)
+ _toggle.TryDeactivate(uid, user: user);
}
}
diff --git a/Content.Shared/Ninja/Systems/SharedSpaceNinjaSystem.cs b/Content.Shared/Ninja/Systems/SharedSpaceNinjaSystem.cs
index 522f29fe42..d738f2dd8a 100644
--- a/Content.Shared/Ninja/Systems/SharedSpaceNinjaSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedSpaceNinjaSystem.cs
@@ -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 NinjaQuery;
+
public override void Initialize()
{
base.Initialize();
+ NinjaQuery = GetEntityQuery();
+
SubscribeLocalEvent(OnNinjaAttacked);
SubscribeLocalEvent(OnNinjaAttack);
SubscribeLocalEvent(OnShotAttempted);
}
+ public bool IsNinja([NotNullWhen(true)] EntityUid? uid)
+ {
+ return NinjaQuery.HasComp(uid);
+ }
+
///
/// Set the ninja's worn suit entity
///
- public void AssignSuit(EntityUid uid, EntityUid? suit, SpaceNinjaComponent? comp = null)
+ public void AssignSuit(Entity 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);
}
///
/// Set the ninja's worn gloves entity
///
- public void AssignGloves(EntityUid uid, EntityUid? gloves, SpaceNinjaComponent? comp = null)
+ public void AssignGloves(Entity 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);
}
///
/// 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.
///
- public void BindKatana(EntityUid uid, EntityUid? katana, SpaceNinjaComponent? comp = null)
+ public void BindKatana(Entity 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);
}
///
@@ -71,32 +82,32 @@ public abstract class SharedSpaceNinjaSystem : EntitySystem
///
/// Handle revealing ninja if cloaked when attacked.
///
- private void OnNinjaAttacked(EntityUid uid, SpaceNinjaComponent comp, AttackedEvent args)
+ private void OnNinjaAttacked(Entity ent, ref AttackedEvent args)
{
- if (comp.Suit != null && TryComp(comp.Suit, out var stealthClothing) && stealthClothing.Enabled)
- {
- Suit.RevealNinja(comp.Suit.Value, uid, true, null, stealthClothing);
- }
+ TryRevealNinja(ent, disable: true);
}
///
/// Handle revealing ninja if cloaked when attacking.
/// Only reveals, there is no cooldown.
///
- private void OnNinjaAttack(EntityUid uid, SpaceNinjaComponent comp, ref MeleeAttackEvent args)
+ private void OnNinjaAttack(Entity ent, ref MeleeAttackEvent args)
{
- if (comp.Suit != null && TryComp(comp.Suit, out var stealthClothing) && stealthClothing.Enabled)
- {
- Suit.RevealNinja(comp.Suit.Value, uid, false, null, stealthClothing);
- }
+ TryRevealNinja(ent, disable: false);
+ }
+
+ private void TryRevealNinja(Entity ent, bool disable)
+ {
+ if (ent.Comp.Suit is {} uid && TryComp(ent.Comp.Suit, out var suit))
+ Suit.RevealNinja((uid, suit), ent, disable: disable);
}
///
/// Require ninja to fight with HONOR, no guns!
///
- private void OnShotAttempted(EntityUid uid, SpaceNinjaComponent comp, ref ShotAttemptedEvent args)
+ private void OnShotAttempted(Entity 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
index 0000000000..f4b158aced
--- /dev/null
+++ b/Content.Shared/Ninja/Systems/SharedSpiderChargeSystem.cs
@@ -0,0 +1,6 @@
+namespace Content.Shared.Ninja.Systems;
+
+///
+/// Sticking triggering and exploding are all in server so this is just for access.
+///
+public abstract class SharedSpiderChargeSystem : EntitySystem;
diff --git a/Content.Shared/Ninja/Systems/SharedStunProviderSystem.cs b/Content.Shared/Ninja/Systems/SharedStunProviderSystem.cs
index 61b6e4313e..061c019c9b 100644
--- a/Content.Shared/Ninja/Systems/SharedStunProviderSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedStunProviderSystem.cs
@@ -11,22 +11,12 @@ public abstract class SharedStunProviderSystem : EntitySystem
///
/// Set the battery field on the stun provider.
///
- public void SetBattery(EntityUid uid, EntityUid? battery, StunProviderComponent? comp = null)
+ public void SetBattery(Entity ent, EntityUid? battery)
{
- if (!Resolve(uid, ref comp))
+ if (!Resolve(ent, ref ent.Comp) || ent.Comp.BatteryUid == battery)
return;
- comp.BatteryUid = battery;
- }
-
- ///
- /// Set the no power popup field on the stun provider.
- ///
- 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);
}
}
diff --git a/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs b/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs
index 07032a00ce..8d2c4dcfeb 100644
--- a/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs
+++ b/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs
@@ -92,7 +92,7 @@ public abstract class SharedObjectivesSystem : EntitySystem
}
///
- /// Get the title, description, icon and progress of an objective using .
+ /// Get the title, description, icon and progress of an objective using .
/// If any of them are null it is logged and null is returned.
///
/// ID of the condition entity
@@ -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(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);
+ }
+
+ ///
+ /// Gets the progress of an objective using .
+ /// Returning null is a programmer error.
+ ///
+ public float? GetProgress(EntityUid uid, Entity 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;
+ }
+
+ ///
+ /// Returns true if an objective is completed.
+ ///
+ public bool IsCompleted(EntityUid uid, Entity mind)
+ {
+ return (GetProgress(uid, mind) ?? 0f) >= 0.999f;
}
///
diff --git a/Content.Shared/Pinpointer/SharedProximityBeeper.cs b/Content.Shared/Pinpointer/SharedProximityBeeper.cs
deleted file mode 100644
index 5163112683..0000000000
--- a/Content.Shared/Pinpointer/SharedProximityBeeper.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Pinpointer;
-
-[Serializable, NetSerializable]
-public enum ProximityBeeperVisuals : byte
-{
- Enabled
-}
diff --git a/Content.Shared/PowerCell/PowerCellDrawComponent.cs b/Content.Shared/PowerCell/PowerCellDrawComponent.cs
index 708a86a8ea..94de7c7787 100644
--- a/Content.Shared/PowerCell/PowerCellDrawComponent.cs
+++ b/Content.Shared/PowerCell/PowerCellDrawComponent.cs
@@ -6,6 +6,10 @@ namespace Content.Shared.PowerCell;
///
/// Indicates that the entity's ActivatableUI requires power or else it closes.
///
+///
+/// With ActivatableUI it will activate and deactivate when the ui is opened and closed, drawing power inbetween.
+/// Requires to work.
+///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class PowerCellDrawComponent : Component
{
@@ -26,10 +30,12 @@ public sealed partial class PowerCellDrawComponent : Component
#endregion
///
- /// 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.
///
- [ViewVariables(VVAccess.ReadWrite), DataField("enabled")]
- public bool Drawing;
+ [DataField, AutoNetworkedField]
+ public bool Enabled = true;
///
/// 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;
+
+ ///
+ /// How long to wait between power drawing.
+ ///
+ [DataField]
+ public TimeSpan Delay = TimeSpan.FromSeconds(1);
}
diff --git a/Content.Shared/PowerCell/SharedPowerCellSystem.cs b/Content.Shared/PowerCell/SharedPowerCellSystem.cs
index 508bfc85f0..2b2a836633 100644
--- a/Content.Shared/PowerCell/SharedPowerCellSystem.cs
+++ b/Content.Shared/PowerCell/SharedPowerCellSystem.cs
@@ -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(OnRejuvenate);
SubscribeLocalEvent(OnCellInserted);
SubscribeLocalEvent(OnCellRemoved);
SubscribeLocalEvent(OnCellInsertAttempt);
+
+ SubscribeLocalEvent(OnActivateAttempt);
+ SubscribeLocalEvent(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 ent, ref ItemToggleActivateAttemptEvent args)
{
- if (!Resolve(uid, ref component, false) || enabled == component.Drawing)
+ if (!HasDrawCharge(ent, ent.Comp, user: args.User)
+ || !HasActivatableCharge(ent, ent.Comp, user: args.User))
+ args.Cancelled = true;
+ }
+
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
+ {
+ ent.Comp.NextUpdateTime = Timing.CurTime;
+ }
+
+ public void SetDrawEnabled(Entity ent, bool enabled)
+ {
+ 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);
}
///
diff --git a/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs b/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs
index 09cb7f06d5..7e2bb4dfe6 100644
--- a/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs
+++ b/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs
@@ -10,12 +10,6 @@ namespace Content.Shared.ProximityDetection.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState ,Access(typeof(ProximityDetectionSystem))]
public sealed partial class ProximityDetectorComponent : Component
{
- ///
- /// Whether or not it's on.
- ///
- [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
- public bool Enabled = true;
-
///
/// 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;
-
///
/// The farthest distance to search for targets
///
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public FixedPoint2 Range = 10f;
+ // TODO: use timespans not this
public float AccumulatedFrameTime;
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
diff --git a/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs b/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs
index db25e8bc51..df302f9477 100644
--- a/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs
+++ b/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs
@@ -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(OnPaused);
- SubscribeLocalEvent(OnUnpaused);
- SubscribeLocalEvent(OnCompInit);
+ base.Initialize();
+ SubscribeLocalEvent(OnCompInit);
+ SubscribeLocalEvent(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();
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 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 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();
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);
diff --git a/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs b/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
index e1776873da..de0fe0bce3 100644
--- a/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
+++ b/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
@@ -15,12 +15,6 @@ namespace Content.Shared.Silicons.Borgs.Components;
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem)), AutoGenerateComponentState]
public sealed partial class BorgChassisComponent : Component
{
- ///
- /// Whether or not the borg is activated, meaning it has access to modules and a heightened movement speed
- ///
- [DataField("activated"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public bool Activated;
-
#region Brain
///
/// A whitelist for which entities count as valid brains
@@ -68,7 +62,7 @@ public sealed partial class BorgChassisComponent : Component
///
/// The currently selected module
///
- [DataField("selectedModule")]
+ [DataField("selectedModule"), AutoNetworkedField]
public EntityUid? SelectedModule;
#region Visuals
diff --git a/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs b/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
index 2983c0d642..48d2357836 100644
--- a/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
+++ b/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
@@ -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!;
///
@@ -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(uid, out var movement))
diff --git a/Content.Shared/Toggleable/ToggleActionEvent.cs b/Content.Shared/Toggleable/ToggleActionEvent.cs
index 1283b6699b..f28e62e7dd 100644
--- a/Content.Shared/Toggleable/ToggleActionEvent.cs
+++ b/Content.Shared/Toggleable/ToggleActionEvent.cs
@@ -4,9 +4,12 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Toggleable;
///
-/// Generic action-event for toggle-able components.
+/// Generic action-event for toggle-able components.
///
-public sealed partial class ToggleActionEvent : InstantActionEvent { }
+///
+/// If you are using ItemToggleComponent subscribe to ItemToggledEvent instead.
+///
+public sealed partial class ToggleActionEvent : InstantActionEvent;
///
/// Generic enum keys for toggle-visualizer appearance data & sprite layers.
diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs
index 56ce81413f..201eb19a88 100644
--- a/Content.Shared/Tools/Systems/SharedToolSystem.cs
+++ b/Content.Shared/Tools/Systems/SharedToolSystem.cs
@@ -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!;
diff --git a/Content.Shared/UserInterface/ActivatableUISystem.Power.cs b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs
index b8a815c7a8..e494253c83 100644
--- a/Content.Shared/UserInterface/ActivatableUISystem.Power.cs
+++ b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs
@@ -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(OnBatteryOpenAttempt);
SubscribeLocalEvent(OnBatteryOpened);
SubscribeLocalEvent(OnBatteryClosed);
-
- SubscribeLocalEvent(OnPowerCellRemoved);
+ SubscribeLocalEvent(OnToggled);
}
- private void OnPowerCellRemoved(EntityUid uid, PowerCellDrawComponent component, EntRemovedFromContainerMessage args)
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
{
- _cell.SetPowerCellDrawEnabled(uid, false);
-
- if (!HasComp(uid) ||
- !TryComp(uid, out ActivatableUIComponent? activatable))
- {
+ // only close ui when losing power
+ if (!TryComp(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);
}
///
diff --git a/Content.Shared/Weapons/Reflect/ReflectComponent.cs b/Content.Shared/Weapons/Reflect/ReflectComponent.cs
index 8e7b8975d9..8418c1f3ef 100644
--- a/Content.Shared/Weapons/Reflect/ReflectComponent.cs
+++ b/Content.Shared/Weapons/Reflect/ReflectComponent.cs
@@ -5,16 +5,11 @@ namespace Content.Shared.Weapons.Reflect;
///
/// Entities with this component have a chance to reflect projectiles and hitscan shots
+/// Uses ItemToggleComponent to control reflection.
///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ReflectComponent : Component
{
- ///
- /// Can only reflect when enabled
- ///
- [DataField("enabled"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public bool Enabled = true;
-
///
/// What we reflect.
///
diff --git a/Content.Shared/Weapons/Reflect/ReflectSystem.cs b/Content.Shared/Weapons/Reflect/ReflectSystem.cs
index 7a2e733bf7..881b547f27 100644
--- a/Content.Shared/Weapons/Reflect/ReflectSystem.cs
+++ b/Content.Shared/Weapons/Reflect/ReflectSystem.cs
@@ -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;
///
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(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(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);
}
///
@@ -225,7 +227,7 @@ public sealed class ReflectSystem : EntitySystem
{
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.All & ~SlotFlags.POCKET))
{
- if (!HasComp(ent))
+ if (!HasComp(ent) || !_toggle.IsActivated(ent))
continue;
EnsureComp(user);
diff --git a/Resources/Prototypes/Actions/ninja.yml b/Resources/Prototypes/Actions/ninja.yml
index adaf563692..47cb8a83f4 100644
--- a/Resources/Prototypes/Actions/ninja.yml
+++ b/Resources/Prototypes/Actions/ninja.yml
@@ -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
@@ -72,6 +72,10 @@
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
diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml
index 8b73eee0d2..f1d9988465 100644
--- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml
+++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml
@@ -206,7 +206,7 @@
- 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.
@@ -234,7 +234,31 @@
- 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
diff --git a/Resources/Prototypes/Entities/Clothing/Head/misc.yml b/Resources/Prototypes/Entities/Clothing/Head/misc.yml
index c6a556b2d3..c32f485f9c 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/misc.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/misc.yml
@@ -183,6 +183,36 @@
- 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
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
index 2053ced0f6..bacdd0046f 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
@@ -127,7 +127,7 @@
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.
@@ -136,9 +136,7 @@
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
@@ -151,7 +149,27 @@
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
diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
index d934a8b97e..19fa86a631 100644
--- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
+++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
@@ -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
@@ -40,13 +46,9 @@
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
@@ -91,12 +90,9 @@
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:
@@ -104,49 +100,17 @@
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
diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
index 22cd13af60..fae8717223 100644
--- a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
+++ b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
@@ -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!
@@ -100,12 +100,11 @@
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:
@@ -130,10 +129,8 @@
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
diff --git a/Resources/Prototypes/Entities/Clothing/base_clothing.yml b/Resources/Prototypes/Entities/Clothing/base_clothing.yml
index a96ca2d23c..02a2ddce41 100644
--- a/Resources/Prototypes/Entities/Clothing/base_clothing.yml
+++ b/Resources/Prototypes/Entities/Clothing/base_clothing.yml
@@ -57,3 +57,12 @@
- 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
diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml
index b6f9287cf7..b8cf755d0f 100644
--- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml
+++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml
@@ -151,10 +151,9 @@
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
@@ -162,8 +161,6 @@
rules: ghost-role-information-antagonist-rules
raffle:
settings: default
- - type: GhostRoleMobSpawner
- prototype: MobHumanSpaceNinja
- type: Sprite
sprite: Markers/jobs.rsi
layers:
diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
index 3b8e5bde6a..6656a7aadb 100644
--- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
+++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
@@ -112,6 +112,11 @@
- 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
diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml
index 629ba91518..0b26668e10 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/human.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml
@@ -68,28 +68,3 @@
- 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
diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml
index d956f1871d..8702bbe021 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml
@@ -142,9 +142,6 @@
- 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
index 0000000000..259323fede
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Devices/base_handheld.yml
@@ -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
diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
index 84f0f4ae94..bf0b7190b5 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
@@ -109,6 +109,9 @@
id: BaseMedicalPDA
abstract: true
components:
+ - type: ItemToggle
+ toggleLight: false
+ onUse: false
- type: HealthAnalyzer
scanDelay: 1
scanningEndSound:
diff --git a/Resources/Prototypes/Entities/Objects/Devices/station_map.yml b/Resources/Prototypes/Entities/Objects/Devices/station_map.yml
index 0d2f890a1d..54fc4a70c5 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/station_map.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/station_map.yml
@@ -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
@@ -34,14 +34,9 @@
- 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
diff --git a/Resources/Prototypes/Entities/Objects/Shields/shields.yml b/Resources/Prototypes/Entities/Objects/Shields/shields.yml
index dc24ac485a..8182accfb6 100644
--- a/Resources/Prototypes/Entities/Objects/Shields/shields.yml
+++ b/Resources/Prototypes/Entities/Objects/Shields/shields.yml
@@ -382,8 +382,10 @@
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:
@@ -415,7 +417,6 @@
energy: 2
color: blue
- type: Reflect
- enabled: false
reflectProb: 0.95
reflects:
- Energy
@@ -492,8 +493,10 @@
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
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml
index 4a61074a8d..2d06ed0f1d 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml
@@ -31,6 +31,11 @@
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:
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
index 19a0b36ee9..c9ab24274d 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
@@ -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
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
index c01aaa84a9..d8547d9294 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
@@ -21,6 +21,8 @@
interfaces:
enum.HealthAnalyzerUiKey.Key:
type: HealthAnalyzerBoundUserInterface
+ - type: ItemToggle
+ onUse: false
- type: HealthAnalyzer
scanningEndSound:
path: "/Audio/Items/Medical/healthscanner.ogg"
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
index bf0a3be4e5..99f874406b 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
@@ -36,23 +36,22 @@
- 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:
diff --git a/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml b/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml
index 5938193181..78abedc28a 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml
@@ -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
diff --git a/Resources/Prototypes/Entities/Objects/Tools/welders.yml b/Resources/Prototypes/Entities/Objects/Tools/welders.yml
index 9db30edb52..cd188969b5 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/welders.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/welders.yml
@@ -55,8 +55,10 @@
- type: ItemToggleSize
activatedSize: Large
- type: ItemToggleHot
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.6
+ - type: ComponentToggler
+ components:
+ - type: DisarmMalus
+ malus: 0.6
- type: ToggleableLightVisuals
spriteLayer: flame
inhandVisuals:
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
index 0d10411cfb..19a0cf30e8 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
@@ -166,6 +166,8 @@
- type: TetherGun
frequency: 5
dampingRatio: 4
+ - type: ItemToggle
+ onUse: false
- type: PowerCellDraw
- type: Sprite
sprite: Objects/Weapons/Guns/Launchers/tether_gun.rsi
@@ -211,6 +213,8 @@
path: /Audio/Weapons/soup.ogg
params:
volume: 2
+ - type: ItemToggle
+ onUse: false
- type: PowerCellDraw
- type: Sprite
sprite: Objects/Weapons/Guns/Launchers/force_gun.rsi
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml
index fbf8b1003c..ce3545920a 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml
@@ -13,10 +13,12 @@
- 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
@@ -74,10 +76,7 @@
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
@@ -114,8 +112,11 @@
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:
@@ -183,15 +184,12 @@
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:
@@ -220,8 +218,8 @@
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:
@@ -246,9 +244,13 @@
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
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
index d0d85beb6f..0bab2c828d 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
@@ -30,7 +30,6 @@
soundHit:
path: /Audio/Weapons/bladeslice.ogg
- type: Reflect
- enabled: true
reflectProb: .1
spread: 90
- type: Item
@@ -175,7 +174,6 @@
soundHit:
path: /Audio/Effects/explosion_small1.ogg
- type: Reflect
- enabled: true
reflectProb: .25
spread: 90
- type: Item
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
index 4cb76ea4b9..0485b5a517 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
@@ -85,6 +85,7 @@
whitelist:
components:
- FitsInDispenser
+ - type: ItemToggle
- type: HealthAnalyzer
scanDelay: 0
- type: UserInterface
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index 39e29ad115..0b183039a9 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -149,7 +149,43 @@
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
diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml
index 5a38c22215..43cdca3bc3 100644
--- a/Resources/Prototypes/GameRules/midround.yml
+++ b/Resources/Prototypes/GameRules/midround.yml
@@ -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