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