]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Power cell slot QOL (#15373)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Sun, 23 Apr 2023 02:25:12 +0000 (12:25 +1000)
committerGitHub <noreply@github.com>
Sun, 23 Apr 2023 02:25:12 +0000 (12:25 +1000)
17 files changed:
Content.Server/Eye/Blinding/ActivatableUIRequiresVisionSystem.cs
Content.Server/Medical/HealthAnalyzerSystem.cs
Content.Server/Pinpointer/StationMapSystem.cs
Content.Server/Power/Components/BatteryComponent.cs
Content.Server/PowerCell/PowerCellDrawComponent.cs [new file with mode: 0644]
Content.Server/PowerCell/PowerCellSlotEmptyEvent.cs [new file with mode: 0644]
Content.Server/PowerCell/PowerCellSystem.cs
Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs [new file with mode: 0644]
Content.Server/UserInterface/ActivatableUISystem.Power.cs [new file with mode: 0644]
Content.Server/UserInterface/ActivatableUISystem.cs
Resources/Locale/en-US/power-cell/components/power-cell-component.ftl
Resources/Locale/en-US/stunnable/components/stunbaton-component.ftl
Resources/Prototypes/Entities/Objects/Devices/station_map.yml
Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
Resources/Prototypes/Entities/Objects/base_item.yml
Resources/Prototypes/Entities/Structures/Wallmounts/station_map.yml

index a1401f17456a34652a90c0bf55ecb0322bd5cef4..4b5d3d3eef89b58678f5c0440e827715f0252eb7 100644 (file)
@@ -4,57 +4,55 @@ using Content.Server.Popups;
 using Robust.Shared.Player;
 using Robust.Server.GameObjects;
 
-namespace Content.Server.Eye.Blinding
+namespace Content.Server.Eye.Blinding;
+
+public sealed class ActivatableUIRequiresVisionSystem : EntitySystem
 {
-    public sealed class ActivatableUIRequiresVisionSystem : EntitySystem
+    [Dependency] private readonly PopupSystem _popupSystem = default!;
+    [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
+
+    public override void Initialize()
     {
-        [Dependency] private readonly ActivatableUISystem _activatableUISystem = default!;
-        [Dependency] private readonly PopupSystem _popupSystem = default!;
-        [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
+        base.Initialize();
+        SubscribeLocalEvent<ActivatableUIRequiresVisionComponent, ActivatableUIOpenAttemptEvent>(OnOpenAttempt);
+        SubscribeLocalEvent<BlindableComponent, BlindnessChangedEvent>(OnBlindnessChanged);
+    }
 
-        public override void Initialize()
-        {
-            base.Initialize();
-            SubscribeLocalEvent<ActivatableUIRequiresVisionComponent, ActivatableUIOpenAttemptEvent>(OnOpenAttempt);
-            SubscribeLocalEvent<BlindableComponent, BlindnessChangedEvent>(OnBlindnessChanged);
-        }
+    private void OnOpenAttempt(EntityUid uid, ActivatableUIRequiresVisionComponent component, ActivatableUIOpenAttemptEvent args)
+    {
+        if (args.Cancelled)
+            return;
 
-        private void OnOpenAttempt(EntityUid uid, ActivatableUIRequiresVisionComponent component, ActivatableUIOpenAttemptEvent args)
+        if (TryComp<BlindableComponent>(args.User, out var blindable) && blindable.Sources > 0)
         {
-            if (args.Cancelled)
-                return;
-
-            if (TryComp<BlindableComponent>(args.User, out var blindable) && blindable.Sources > 0)
-            {
-                _popupSystem.PopupCursor(Loc.GetString("blindness-fail-attempt"), args.User, Shared.Popups.PopupType.MediumCaution);
-                args.Cancel();
-            }
+            _popupSystem.PopupCursor(Loc.GetString("blindness-fail-attempt"), args.User, Shared.Popups.PopupType.MediumCaution);
+            args.Cancel();
         }
+    }
 
-        private void OnBlindnessChanged(EntityUid uid, BlindableComponent component, BlindnessChangedEvent args)
-        {
-            if (!args.Blind)
-                return;
+    private void OnBlindnessChanged(EntityUid uid, BlindableComponent component, BlindnessChangedEvent args)
+    {
+        if (!args.Blind)
+            return;
 
-            if (!TryComp<ActorComponent>(uid, out var actor))
-                return;
+        if (!TryComp<ActorComponent>(uid, out var actor))
+            return;
 
-            var uiList = _userInterfaceSystem.GetAllUIsForSession(actor.PlayerSession);
-            if (uiList == null)
-                return;
+        var uiList = _userInterfaceSystem.GetAllUIsForSession(actor.PlayerSession);
+        if (uiList == null)
+            return;
 
-            Queue<BoundUserInterface> closeList = new(); // foreach collection modified moment
+        Queue<BoundUserInterface> closeList = new(); // foreach collection modified moment
 
-            foreach (var ui in uiList)
-            {
-                if (HasComp<ActivatableUIRequiresVisionComponent>(ui.Owner))
-                    closeList.Enqueue(ui);
-            }
+        foreach (var ui in uiList)
+        {
+            if (HasComp<ActivatableUIRequiresVisionComponent>(ui.Owner))
+                closeList.Enqueue(ui);
+        }
 
-            foreach (var ui in closeList)
-            {
-                _userInterfaceSystem.CloseUi(ui, actor.PlayerSession);
-            }
+        foreach (var ui in closeList)
+        {
+            _userInterfaceSystem.CloseUi(ui, actor.PlayerSession);
         }
     }
 }
index 197656ab702923a55c245fcfa2bcb6f4a89299c8..85ace0f1d6826c2d516ea9c747276044367100ef 100644 (file)
@@ -1,6 +1,8 @@
 using Content.Server.Disease;
 using Content.Server.Medical.Components;
 using Content.Server.Popups;
+using Content.Server.PowerCell;
+using Content.Server.UserInterface;
 using Content.Shared.Damage;
 using Content.Shared.DoAfter;
 using Content.Shared.IdentityManagement;
@@ -13,28 +15,23 @@ namespace Content.Server.Medical
 {
     public sealed class HealthAnalyzerSystem : EntitySystem
     {
-        [Dependency] private readonly SharedAudioSystem _audio = default!;
         [Dependency] private readonly DiseaseSystem _disease = default!;
-        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
+        [Dependency] private readonly PowerCellSystem _cell = default!;
+        [Dependency] private readonly SharedAudioSystem _audio = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
 
         public override void Initialize()
         {
             base.Initialize();
-            SubscribeLocalEvent<HealthAnalyzerComponent, ActivateInWorldEvent>(HandleActivateInWorld);
             SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
             SubscribeLocalEvent<HealthAnalyzerComponent, HealthAnalyzerDoAfterEvent>(OnDoAfter);
         }
 
-        private void HandleActivateInWorld(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, ActivateInWorldEvent args)
-        {
-            OpenUserInterface(args.User, healthAnalyzer);
-        }
-
         private void OnAfterInteract(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, AfterInteractEvent args)
         {
-            if (args.Target == null || !args.CanReach || !HasComp<MobStateComponent>(args.Target))
+            if (args.Target == null || !args.CanReach || !HasComp<MobStateComponent>(args.Target) || !_cell.HasActivatableCharge(uid, user: args.User))
                 return;
 
             _audio.PlayPvs(healthAnalyzer.ScanningBeginSound, uid);
@@ -49,7 +46,7 @@ namespace Content.Server.Medical
 
         private void OnDoAfter(EntityUid uid, HealthAnalyzerComponent component, DoAfterEvent args)
         {
-            if (args.Handled || args.Cancelled || args.Args.Target == null)
+            if (args.Handled || args.Cancelled || args.Args.Target == null || !_cell.TryUseActivatableCharge(uid, user: args.User))
                 return;
 
             _audio.PlayPvs(component.ScanningEndSound, args.Args.User);
@@ -58,6 +55,9 @@ namespace Content.Server.Medical
             // Below is for the traitor item
             // Piggybacking off another component's doafter is complete CBT so I gave up
             // and put it on the same component
+            /*
+             * this code is cursed wuuuuuuut
+             */
             if (string.IsNullOrEmpty(component.Disease))
             {
                 args.Handled = true;
@@ -71,8 +71,6 @@ namespace Content.Server.Medical
                 _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-self", ("disease", component.Disease)),
                     args.Args.User, args.Args.User);
             }
-
-
             else
             {
                 _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", Identity.Entity(args.Args.Target.Value, EntityManager)),
index 3b5eea07cf7c6a96178e698edb66d06bbc03b0da..c0dbbc641fe904b3e10e7f79a469f3afe0e8fa99 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Shared.Interaction.Events;
 using Content.Shared.Pinpointer;
 using Robust.Server.GameObjects;
 
index 8071321745a73d6b27d44fc0c023c1980192315b..f9cd0201cae0e34dd0f7a446d66d4f2878e7ad8e 100644 (file)
@@ -62,5 +62,5 @@ namespace Content.Server.Power.Components
     ///     Raised when a battery's charge or capacity changes (capacity affects relative charge percentage).
     /// </summary>
     [ByRefEvent]
-    public record struct ChargeChangedEvent(float Charge, float MaxCharge);
+    public readonly record struct ChargeChangedEvent(float Charge, float MaxCharge);
 }
diff --git a/Content.Server/PowerCell/PowerCellDrawComponent.cs b/Content.Server/PowerCell/PowerCellDrawComponent.cs
new file mode 100644 (file)
index 0000000..7697fa2
--- /dev/null
@@ -0,0 +1,25 @@
+namespace Content.Server.PowerCell;
+
+/// <summary>
+/// Indicates that the entity's ActivatableUI requires power or else it closes.
+/// </summary>
+[RegisterComponent]
+public sealed class PowerCellDrawComponent : Component
+{
+    [ViewVariables(VVAccess.ReadWrite), DataField("enabled")]
+    public bool Enabled = false;
+
+    /// <summary>
+    /// How much the entity draws while the UI is open.
+    /// Set to 0 if you just wish to check for power upon opening the UI.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("drawRate")]
+    public float DrawRate = 1f;
+
+    /// <summary>
+    /// How much power is used whenever the entity is "used".
+    /// This is used to ensure the UI won't open again without a minimum use power.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("useRate")]
+    public float UseRate = 0f;
+}
diff --git a/Content.Server/PowerCell/PowerCellSlotEmptyEvent.cs b/Content.Server/PowerCell/PowerCellSlotEmptyEvent.cs
new file mode 100644 (file)
index 0000000..b2930ee
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Content.Server.PowerCell;
+
+/// <summary>
+/// Raised directed on an entity when its active power cell has no more charge to supply.
+/// </summary>
+[ByRefEvent]
+public readonly record struct PowerCellSlotEmptyEvent;
index f7882dd6442d5378ccd4cc097e7ee38c5bff9bfc..b65a949da5a6c7120df7085e4f8b364d16b32c9a 100644 (file)
@@ -10,19 +10,25 @@ using Content.Shared.Rounding;
 using Robust.Shared.Containers;
 using System.Diagnostics.CodeAnalysis;
 using Content.Server.Kitchen.Components;
+using Content.Server.UserInterface;
 using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Popups;
 using Content.Shared.Rejuvenate;
+using Content.Shared.UserInterface;
+using Robust.Server.GameObjects;
 
 namespace Content.Server.PowerCell;
 
 public sealed class PowerCellSystem : SharedPowerCellSystem
 {
+    [Dependency] private readonly ActivatableUISystem _activatable = default!;
     [Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
     [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
     [Dependency] private readonly IAdminLogManager _adminLogger = default!;
     [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
     [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
     [Dependency] private readonly SharedAppearanceSystem _sharedAppearanceSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
 
     public override void Initialize()
     {
@@ -39,6 +45,28 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
         SubscribeLocalEvent<BatteryComponent, BeingMicrowavedEvent>(OnMicrowaved);
     }
 
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+        var query = EntityQueryEnumerator<PowerCellDrawComponent, PowerCellSlotComponent>();
+
+        while (query.MoveNext(out var uid, out var comp, out var slot))
+        {
+            if (!comp.Enabled)
+                continue;
+
+            if (!TryGetBatteryFromSlot(uid, out var battery, slot))
+                continue;
+
+            if (battery.TryUseCharge(comp.DrawRate * frameTime))
+                continue;
+
+            comp.Enabled = false;
+            var ev = new PowerCellSlotEmptyEvent();
+            RaiseLocalEvent(uid, ref ev);
+        }
+    }
+
     private void OnRejuvenate(EntityUid uid, PowerCellComponent component, RejuvenateEvent args)
     {
         component.IsRigged = false;
@@ -102,6 +130,89 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
         QueueDel(uid);
     }
 
+    #region Activatable
+
+    /// <summary>
+    /// Returns whether the entity has a slotted battery and <see cref="PowerCellDrawComponent.UseRate"/> charge.
+    /// </summary>
+    /// <param name="user">Popup to this user with the relevant detail if specified.</param>
+    public bool HasActivatableCharge(EntityUid uid, PowerCellDrawComponent? battery = null, PowerCellSlotComponent? cell = null, EntityUid? user = null)
+    {
+        if (!Resolve(uid, ref battery, ref cell, false))
+            return false;
+
+        return HasCharge(uid, battery.UseRate, cell, user);
+    }
+
+    /// <summary>
+    /// Tries to use the <see cref="PowerCellDrawComponent.UseRate"/> for this entity.
+    /// </summary>
+    /// <param name="user">Popup to this user with the relevant detail if specified.</param>
+    public bool TryUseActivatableCharge(EntityUid uid, PowerCellDrawComponent? battery = null, PowerCellSlotComponent? cell = null, EntityUid? user = null)
+    {
+        if (!Resolve(uid, ref battery, ref cell, false))
+            return false;
+
+        if (TryUseCharge(uid, battery.UseRate, cell, user))
+        {
+            _activatable.CheckUsage(uid);
+            return true;
+        }
+
+        return false;
+    }
+
+    #endregion
+
+    /// <summary>
+    /// Returns whether the entity has a slotted battery and charge for the requested action.
+    /// </summary>
+    /// <param name="user">Popup to this user with the relevant detail if specified.</param>
+    public bool HasCharge(EntityUid uid, float charge, PowerCellSlotComponent? component = null, EntityUid? user = null)
+    {
+        if (!TryGetBatteryFromSlot(uid, out var battery, component))
+        {
+            if (user != null)
+                _popup.PopupEntity(Loc.GetString("power-cell-no-battery"), uid, user.Value);
+
+            return false;
+        }
+
+        if (battery.CurrentCharge < charge)
+        {
+            if (user != null)
+                _popup.PopupEntity(Loc.GetString("power-cell-insufficient"), uid, user.Value);
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Tries to use charge from a slotted battery.
+    /// </summary>
+    public bool TryUseCharge(EntityUid uid, float charge, PowerCellSlotComponent? component = null, EntityUid? user = null)
+    {
+        if (!TryGetBatteryFromSlot(uid, out var battery, component))
+        {
+            if (user != null)
+                _popup.PopupEntity(Loc.GetString("power-cell-no-battery"), uid, user.Value);
+
+            return false;
+        }
+
+        if (!battery.TryUseCharge(charge))
+        {
+            if (user != null)
+                _popup.PopupEntity(Loc.GetString("power-cell-insufficient"), uid, user.Value);
+
+            return false;
+        }
+
+        return true;
+    }
+
     public bool TryGetBatteryFromSlot(EntityUid uid, [NotNullWhen(true)] out BatteryComponent? battery, PowerCellSlotComponent? component = null)
     {
         if (!Resolve(uid, ref component, false))
diff --git a/Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs b/Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs
new file mode 100644 (file)
index 0000000..14429dd
--- /dev/null
@@ -0,0 +1,13 @@
+using Content.Server.PowerCell;
+using Content.Shared.UserInterface;
+
+namespace Content.Server.UserInterface;
+
+/// <summary>
+/// Specifies that the attached entity requires <see cref="PowerCellDrawComponent"/> power.
+/// </summary>
+[RegisterComponent]
+public sealed class ActivatableUIRequiresPowerCellComponent : Component
+{
+
+}
diff --git a/Content.Server/UserInterface/ActivatableUISystem.Power.cs b/Content.Server/UserInterface/ActivatableUISystem.Power.cs
new file mode 100644 (file)
index 0000000..7b36116
--- /dev/null
@@ -0,0 +1,77 @@
+using Content.Server.PowerCell;
+using Content.Shared.PowerCell;
+using Robust.Shared.Containers;
+
+namespace Content.Server.UserInterface;
+
+public sealed partial class ActivatableUISystem
+{
+    [Dependency] private readonly PowerCellSystem _cell = default!;
+
+    private void InitializePower()
+    {
+        SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ActivatableUIOpenAttemptEvent>(OnBatteryOpenAttempt);
+        SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIOpenedEvent>(OnBatteryOpened);
+        SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIClosedEvent>(OnBatteryClosed);
+
+        SubscribeLocalEvent<PowerCellDrawComponent, EntRemovedFromContainerMessage>(OnPowerCellRemoved);
+    }
+
+    private void OnPowerCellRemoved(EntityUid uid, PowerCellDrawComponent component, EntRemovedFromContainerMessage args)
+    {
+        if (TryComp<PowerCellDrawComponent>(uid, out var draw))
+        {
+            draw.Enabled = false;
+        }
+
+        if (HasComp<ActivatableUIRequiresPowerCellComponent>(uid) &&
+            TryComp<ActivatableUIComponent>(uid, out var activatable) &&
+            activatable.Key != null)
+        {
+            _uiSystem.TryCloseAll(uid, activatable.Key);
+        }
+    }
+
+    private void OnBatteryOpened(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIOpenedEvent args)
+    {
+        if (!TryComp<PowerCellDrawComponent>(uid, out var draw))
+            return;
+
+        draw.Enabled = true;
+    }
+
+    private void OnBatteryClosed(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIClosedEvent args)
+    {
+        if (!TryComp<PowerCellDrawComponent>(uid, out var draw))
+            return;
+
+        draw.Enabled = false;
+    }
+
+    /// <summary>
+    /// Call if you want to check if the UI should close due to a recent battery usage.
+    /// </summary>
+    public void CheckUsage(EntityUid uid, ActivatableUIComponent? active = null, ActivatableUIRequiresPowerCellComponent? component = null, PowerCellDrawComponent? draw = null)
+    {
+        if (!Resolve(uid, ref component, ref draw, ref active, false) || active.Key == null)
+            return;
+
+        if (_cell.HasCharge(uid, draw.UseRate))
+            return;
+
+        _uiSystem.TryCloseAll(uid, active.Key);
+    }
+
+    private void OnBatteryOpenAttempt(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, ActivatableUIOpenAttemptEvent args)
+    {
+        if (!TryComp<PowerCellDrawComponent>(uid, out var draw))
+            return;
+
+        // Check if we have the appropriate drawrate / userate to even open it.
+        if (args.Cancelled || !_cell.HasCharge(uid, MathF.Max(draw.DrawRate, draw.UseRate), user: args.User))
+        {
+            args.Cancel();
+            return;
+        }
+    }
+}
index e9bdce6c2c61239a790e4ab5b5bd4662f0b0e638..b947aac89f174acf0516007c24cf0f84f33b1d6a 100644 (file)
@@ -6,226 +6,225 @@ using Content.Shared.Hands.Components;
 using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Verbs;
-using JetBrains.Annotations;
 using Robust.Server.GameObjects;
 using Robust.Server.Player;
 
-namespace Content.Server.UserInterface
+namespace Content.Server.UserInterface;
+
+public sealed partial class ActivatableUISystem : EntitySystem
 {
-    [UsedImplicitly]
-    internal sealed class ActivatableUISystem : EntitySystem
-    {
-        [Dependency] private readonly IAdminManager _adminManager = default!;
-        [Dependency] private readonly ActionBlockerSystem _blockerSystem = default!;
-        [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+    [Dependency] private readonly IAdminManager _adminManager = default!;
+    [Dependency] private readonly ActionBlockerSystem _blockerSystem = default!;
+    [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
 
-        public override void Initialize()
-        {
-            base.Initialize();
+    public override void Initialize()
+    {
+        base.Initialize();
 
-            SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
-            SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
-            SubscribeLocalEvent<ActivatableUIComponent, HandDeselectedEvent>(OnHandDeselected);
-            SubscribeLocalEvent<ActivatableUIComponent, GotUnequippedHandEvent>((uid, aui, _) => CloseAll(uid, aui));
-            // *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it
-            SubscribeLocalEvent<ActivatableUIComponent, EntParentChangedMessage>(OnParentChanged);
-            SubscribeLocalEvent<ActivatableUIComponent, BoundUIClosedEvent>(OnUIClose);
-            SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
+        SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
+        SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
+        SubscribeLocalEvent<ActivatableUIComponent, HandDeselectedEvent>(OnHandDeselected);
+        SubscribeLocalEvent<ActivatableUIComponent, GotUnequippedHandEvent>((uid, aui, _) => CloseAll(uid, aui));
+        // *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it
+        SubscribeLocalEvent<ActivatableUIComponent, EntParentChangedMessage>(OnParentChanged);
+        SubscribeLocalEvent<ActivatableUIComponent, BoundUIClosedEvent>(OnUIClose);
+        SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
 
-            SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(AddOpenUiVerb);
+        SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(AddOpenUiVerb);
 
-            SubscribeLocalEvent<ServerUserInterfaceComponent, OpenUiActionEvent>(OnActionPerform);
-        }
+        SubscribeLocalEvent<ServerUserInterfaceComponent, OpenUiActionEvent>(OnActionPerform);
 
-        private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
-        {
-            if (!TryComp(ev.Target, out ActivatableUIComponent? comp))
-                return;
+        InitializePower();
+    }
 
-            if (!comp.RequireHands)
-                return;
+    private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
+    {
+        if (!TryComp(ev.Target, out ActivatableUIComponent? comp))
+            return;
 
-            if (!TryComp(ev.Sender.AttachedEntity, out HandsComponent? hands) || hands.Hands.Count == 0)
-                ev.Cancel();
-        }
+        if (!comp.RequireHands)
+            return;
 
-        private void OnActionPerform(EntityUid uid, ServerUserInterfaceComponent component, OpenUiActionEvent args)
-        {
-            if (args.Handled || args.Key == null)
-                return;
+        if (!TryComp(ev.Sender.AttachedEntity, out HandsComponent? hands) || hands.Hands.Count == 0)
+            ev.Cancel();
+    }
 
-            if (!TryComp(args.Performer, out ActorComponent? actor))
-                return;
+    private void OnActionPerform(EntityUid uid, ServerUserInterfaceComponent component, OpenUiActionEvent args)
+    {
+        if (args.Handled || args.Key == null)
+            return;
 
-            args.Handled = _uiSystem.TryToggleUi(uid, args.Key, actor.PlayerSession);
-        }
+        if (!TryComp(args.Performer, out ActorComponent? actor))
+            return;
 
-        private void AddOpenUiVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<ActivationVerb> args)
-        {
-            if (!args.CanAccess)
-                return;
+        args.Handled = _uiSystem.TryToggleUi(uid, args.Key, actor.PlayerSession);
+    }
 
-            if (component.RequireHands && args.Hands == null)
-                return;
+    private void AddOpenUiVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<ActivationVerb> args)
+    {
+        if (!args.CanAccess)
+            return;
 
-            if (component.InHandsOnly && args.Using != uid)
-                return;
+        if (component.RequireHands && args.Hands == null)
+            return;
 
-            if (!args.CanInteract && (!component.AllowSpectator || !HasComp<GhostComponent>(args.User)))
-                return;
+        if (component.InHandsOnly && args.Using != uid)
+            return;
 
-            ActivationVerb verb = new();
-            verb.Act = () => InteractUI(args.User, component);
-            verb.Text = Loc.GetString(component.VerbText);
-            // TODO VERBS add "open UI" icon?
-            args.Verbs.Add(verb);
-        }
+        if (!args.CanInteract && (!component.AllowSpectator || !HasComp<GhostComponent>(args.User)))
+            return;
 
-        private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
-        {
-            if (args.Handled) return;
-            if (component.InHandsOnly) return;
-            args.Handled = InteractUI(args.User, component);
-        }
-
-        private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args)
-        {
-            if (args.Handled) return;
-            args.Handled = InteractUI(args.User, component);
-        }
+        ActivationVerb verb = new();
+        verb.Act = () => InteractUI(args.User, component);
+        verb.Text = Loc.GetString(component.VerbText);
+        // TODO VERBS add "open UI" icon?
+        args.Verbs.Add(verb);
+    }
 
-        private void OnParentChanged(EntityUid uid, ActivatableUIComponent aui, ref EntParentChangedMessage args)
-        {
-            CloseAll(uid, aui);
-        }
+    private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
+    {
+        if (args.Handled) return;
+        if (component.InHandsOnly) return;
+        args.Handled = InteractUI(args.User, component);
+    }
 
-        private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args)
-        {
-            if (args.Session != component.CurrentSingleUser) return;
-            if (args.UiKey != component.Key) return;
-            SetCurrentSingleUser(uid, null, component);
-        }
+    private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args)
+    {
+        if (args.Handled) return;
+        args.Handled = InteractUI(args.User, component);
+    }
 
-        private bool InteractUI(EntityUid user, ActivatableUIComponent aui)
-        {
-            if (!_blockerSystem.CanInteract(user, aui.Owner) && (!aui.AllowSpectator || !HasComp<GhostComponent>(user)))
-                return false;
+    private void OnParentChanged(EntityUid uid, ActivatableUIComponent aui, ref EntParentChangedMessage args)
+    {
+        CloseAll(uid, aui);
+    }
 
-            if (aui.RequireHands && !HasComp<HandsComponent>(user))
-                return false;
+    private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args)
+    {
+        if (args.Session != component.CurrentSingleUser) return;
+        if (args.UiKey != component.Key) return;
+        SetCurrentSingleUser(uid, null, component);
+    }
 
-            if (!EntityManager.TryGetComponent(user, out ActorComponent? actor)) return false;
+    private bool InteractUI(EntityUid user, ActivatableUIComponent aui)
+    {
+        if (!_blockerSystem.CanInteract(user, aui.Owner) && (!aui.AllowSpectator || !HasComp<GhostComponent>(user)))
+            return false;
 
-            if (aui.AdminOnly && !_adminManager.IsAdmin(actor.PlayerSession)) return false;
+        if (aui.RequireHands && !HasComp<HandsComponent>(user))
+            return false;
 
-            var ui = aui.UserInterface;
-            if (ui == null) return false;
+        if (!EntityManager.TryGetComponent(user, out ActorComponent? actor)) return false;
 
-            if (aui.SingleUser && (aui.CurrentSingleUser != null) && (actor.PlayerSession != aui.CurrentSingleUser))
-            {
-                // If we get here, supposedly, the object is in use.
-                // Check with BUI that it's ACTUALLY in use just in case.
-                // Since this could brick the object if it goes wrong.
-                if (ui.SubscribedSessions.Count != 0) return false;
-            }
+        if (aui.AdminOnly && !_adminManager.IsAdmin(actor.PlayerSession)) return false;
 
-            // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this.
-            // This is so that stuff can require further conditions (like power).
-            var oae = new ActivatableUIOpenAttemptEvent(user);
-            var uae = new UserOpenActivatableUIAttemptEvent(user, aui.Owner);
-            RaiseLocalEvent(user, uae, false);
-            RaiseLocalEvent((aui).Owner, oae, false);
-            if (oae.Cancelled || uae.Cancelled) return false;
+        var ui = aui.UserInterface;
+        if (ui == null) return false;
 
-            // Give the UI an opportunity to prepare itself if it needs to do anything
-            // before opening
-            var bae = new BeforeActivatableUIOpenEvent(user);
-            RaiseLocalEvent((aui).Owner, bae, false);
+        if (aui.SingleUser && (aui.CurrentSingleUser != null) && (actor.PlayerSession != aui.CurrentSingleUser))
+        {
+            // If we get here, supposedly, the object is in use.
+            // Check with BUI that it's ACTUALLY in use just in case.
+            // Since this could brick the object if it goes wrong.
+            if (ui.SubscribedSessions.Count != 0) return false;
+        }
 
-            SetCurrentSingleUser((aui).Owner, actor.PlayerSession, aui);
-            ui.Toggle(actor.PlayerSession);
+        // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this.
+        // This is so that stuff can require further conditions (like power).
+        var oae = new ActivatableUIOpenAttemptEvent(user);
+        var uae = new UserOpenActivatableUIAttemptEvent(user, aui.Owner);
+        RaiseLocalEvent(user, uae, false);
+        RaiseLocalEvent((aui).Owner, oae, false);
+        if (oae.Cancelled || uae.Cancelled) return false;
 
-            //Let the component know a user opened it so it can do whatever it needs to do
-            var aae = new AfterActivatableUIOpenEvent(user, actor.PlayerSession);
-            RaiseLocalEvent((aui).Owner, aae, false);
+        // Give the UI an opportunity to prepare itself if it needs to do anything
+        // before opening
+        var bae = new BeforeActivatableUIOpenEvent(user);
+        RaiseLocalEvent((aui).Owner, bae, false);
 
-            return true;
-        }
+        SetCurrentSingleUser((aui).Owner, actor.PlayerSession, aui);
+        ui.Toggle(actor.PlayerSession);
 
-        public void SetCurrentSingleUser(EntityUid uid, IPlayerSession? v, ActivatableUIComponent? aui = null)
-        {
-            if (!Resolve(uid, ref aui))
-                return;
-            if (!aui.SingleUser)
-                return;
+        //Let the component know a user opened it so it can do whatever it needs to do
+        var aae = new AfterActivatableUIOpenEvent(user, actor.PlayerSession);
+        RaiseLocalEvent((aui).Owner, aae, false);
 
-            aui.CurrentSingleUser = v;
+        return true;
+    }
 
-            RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent(), false);
-        }
+    public void SetCurrentSingleUser(EntityUid uid, IPlayerSession? v, ActivatableUIComponent? aui = null)
+    {
+        if (!Resolve(uid, ref aui))
+            return;
+        if (!aui.SingleUser)
+            return;
 
-        public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null)
-        {
-            if (!Resolve(uid, ref aui, false)) return;
-            aui.UserInterface?.CloseAll();
-        }
+        aui.CurrentSingleUser = v;
 
-        private void OnHandDeselected(EntityUid uid, ActivatableUIComponent? aui, HandDeselectedEvent args)
-        {
-            if (!Resolve(uid, ref aui, false)) return;
-            if (!aui.CloseOnHandDeselect)
-                return;
-            CloseAll(uid, aui);
-        }
+        RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent(), false);
     }
 
-    public sealed class ActivatableUIOpenAttemptEvent : CancellableEntityEventArgs
+    public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null)
     {
-        public EntityUid User { get; }
-        public ActivatableUIOpenAttemptEvent(EntityUid who)
-        {
-            User = who;
-        }
+        if (!Resolve(uid, ref aui, false)) return;
+        aui.UserInterface?.CloseAll();
     }
 
-    public sealed class UserOpenActivatableUIAttemptEvent : CancellableEntityEventArgs //have to one-up the already stroke-inducing name
+    private void OnHandDeselected(EntityUid uid, ActivatableUIComponent? aui, HandDeselectedEvent args)
     {
-        public EntityUid User { get; }
-        public EntityUid Target { get; }
-        public UserOpenActivatableUIAttemptEvent(EntityUid who, EntityUid target)
-        {
-            User = who;
-            Target = target;
-        }
+        if (!Resolve(uid, ref aui, false)) return;
+        if (!aui.CloseOnHandDeselect)
+            return;
+        CloseAll(uid, aui);
     }
+}
 
-    public sealed class AfterActivatableUIOpenEvent : EntityEventArgs
+public sealed class ActivatableUIOpenAttemptEvent : CancellableEntityEventArgs
+{
+    public EntityUid User { get; }
+    public ActivatableUIOpenAttemptEvent(EntityUid who)
     {
-        public EntityUid User { get; }
-        public readonly IPlayerSession Session;
+        User = who;
+    }
+}
 
-        public AfterActivatableUIOpenEvent(EntityUid who, IPlayerSession session)
-        {
-            User = who;
-            Session = session;
-        }
+public sealed class UserOpenActivatableUIAttemptEvent : CancellableEntityEventArgs //have to one-up the already stroke-inducing name
+{
+    public EntityUid User { get; }
+    public EntityUid Target { get; }
+    public UserOpenActivatableUIAttemptEvent(EntityUid who, EntityUid target)
+    {
+        User = who;
+        Target = target;
     }
+}
 
-    /// <summary>
-    /// This is after it's decided the user can open the UI,
-    /// but before the UI actually opens.
-    /// Use this if you need to prepare the UI itself
-    /// </summary>
-    public sealed class BeforeActivatableUIOpenEvent : EntityEventArgs
+public sealed class AfterActivatableUIOpenEvent : EntityEventArgs
+{
+    public EntityUid User { get; }
+    public readonly IPlayerSession Session;
+
+    public AfterActivatableUIOpenEvent(EntityUid who, IPlayerSession session)
     {
-        public EntityUid User { get; }
-        public BeforeActivatableUIOpenEvent(EntityUid who)
-        {
-            User = who;
-        }
+        User = who;
+        Session = session;
     }
+}
 
-    public sealed class ActivatableUIPlayerChangedEvent : EntityEventArgs
+/// <summary>
+/// This is after it's decided the user can open the UI,
+/// but before the UI actually opens.
+/// Use this if you need to prepare the UI itself
+/// </summary>
+public sealed class BeforeActivatableUIOpenEvent : EntityEventArgs
+{
+    public EntityUid User { get; }
+    public BeforeActivatableUIOpenEvent(EntityUid who)
     {
+        User = who;
     }
 }
+
+public sealed class ActivatableUIPlayerChangedEvent : EntityEventArgs
+{
+}
index f1406c477b1ab97f48cfb9ae5b48180f2039ea15..6190250a36cc5363871c38321eae6796bb2c72fc 100644 (file)
@@ -1 +1,3 @@
-power-cell-component-examine-details = The charge indicator reads {$currentCharge} %.
\ No newline at end of file
+power-cell-component-examine-details = The charge indicator reads [color=#5E7C16]{$currentCharge}[/color] %.
+power-cell-no-battery = No power cell found
+power-cell-insufficient = Insufficient power
index 61e8adf4a95deb4ebe0707f082f1f2037b27e0a4..cfe1ea936029e40a1475dc569119dcc88dd43982 100644 (file)
@@ -13,4 +13,4 @@ comp-stunbaton-activated-low-charge = Insufficient charge...
 
 stunbaton-component-low-charge = Insufficient charge...
 stunbaton-component-on-examine = The light is currently [color=darkgreen]on[/color].
-stunbaton-component-on-examine-charge = The charge indicator reads {$charge} %
+stunbaton-component-on-examine-charge = The charge indicator reads [color=#5E7C16]{$charge}[/color] %
index f88cd896ef3f771743ccabb8a7500851d0424198..d333417ff163f3e3183e8460a07ef89260cf3fac 100644 (file)
@@ -1,8 +1,10 @@
 - type: entity
-  id: StationMap
+  id: HandheldStationMap
   name: station map
   description: Displays a readout of the current station.
-  parent: BaseItem
+  parent:
+    - BaseItem
+    - PowerCellSlotSmallItem
   suffix: Handheld
   components:
     - type: StationMap
         - state: tablet
         - state: generic
           shader: unshaded
+    - type: PowerCellDraw
+      drawRate: 0
+      useRate: 20
+    - type: ActivatableUIRequiresPowerCell
     - type: ActivatableUI
       inHandsOnly: true
       singleUser: true
index 6e692044f69076b52e3f63cd1fdd83fea0902d38..b64b7b087f76025f29e835cc400137c032586bf8 100644 (file)
@@ -1,12 +1,18 @@
 - type: entity
   name: handheld crew monitor
-  parent: BaseItem
+  parent:
+  - BaseItem
+  - PowerCellSlotSmallItem
   id: HandheldCrewMonitor
   description: A hand-held crew monitor displaying the status of suit sensors.
   components:
   - type: Sprite
     sprite: Objects/Specific/Medical/handheldcrewmonitor.rsi
     state: scanner
+  - type: PowerCellDraw
+    drawRate: 0
+    useRate: 20
+  - type: ActivatableUIRequiresPowerCell
   - type: ActivatableUI
     key: enum.CrewMonitoringUIKey.Key
     closeOnHandDeselect: false
index 0ae2385e03ee6adb6be69e563ae7c9a0b1de7fe3..a36bf732f96a996b1fda89e24b08693a74a9340f 100644 (file)
@@ -1,6 +1,8 @@
 - type: entity
   name: health analyzer
-  parent: BaseItem
+  parent:
+    - BaseItem
+    - PowerCellSlotSmallItem
   id: HandheldHealthAnalyzer
   description: A hand-held body scanner capable of distinguishing vital signs of the subject.
   components:
@@ -8,6 +10,10 @@
     sprite: Objects/Specific/Medical/healthanalyzer.rsi
     netsync: false
     state: analyzer
+  - type: PowerCellDraw
+    drawRate: 0
+    useRate: 20
+  - type: ActivatableUIRequiresPowerCell
   - type: ActivatableUI
     key: enum.HealthAnalyzerUiKey.Key
     closeOnHandDeselect: false
index e72823220c42af71c9482b8f987b62fe46b9825e..a9b70dd6622b7b8e3862853020a9cd09c4f2c06f 100644 (file)
       storagebase: !type:Container
         ents: []
 
+
+# PowerCellSlot parents
+- type: entity
+  name: a
+  id: PowerCellSlotSmallItem
+  abstract: true
+  components:
+    - type: ContainerContainer
+      containers:
+        cell_slot: !type:ContainerSlot { }
+    - type: PowerCellSlot
+      cellSlotId: cell_slot
+    - type: ItemSlots
+      slots:
+        cell_slot:
+          name: power-cell-slot-component-slot-name-default
+          startingItem: PowerCellSmall
+
+- type: entity
+  name: a
+  id: PowerCellSlotMediumItem
+  abstract: true
+  components:
+    - type: ContainerContainer
+      containers:
+        cell_slot: !type:ContainerSlot { }
+    - type: PowerCellSlot
+      cellSlotId: cell_slot
+    - type: ItemSlots
+      slots:
+        cell_slot:
+          name: power-cell-slot-component-slot-name-default
+          startingItem: PowerCellMedium
index 5fa2f4467af74e0b5bde790f247a4dc90a042aba..071c8cca71e32ae1032465e26dc212d945f0218d 100644 (file)
@@ -1,5 +1,5 @@
 - type: entity
-  id: WallStationMapBroken
+  id: StationMapBroken
   name: station map
   description: A virtual map of the surrounding station.
   suffix: Wall broken
@@ -29,9 +29,9 @@
               acts: [ "Destruction" ]
 
 - type: entity
-  id: WallStationMap
+  id: StationMap
   name: station map
-  parent: WallStationMapBroken
+  parent: StationMapBroken
   suffix: Wall
   placement:
     mode: SnapgridCenter
@@ -65,7 +65,7 @@
                 collection: GlassBreak
             - !type:SpawnEntitiesBehavior
               spawn:
-                WallStationMapBroken:
+                StationMapBroken:
                   min: 1
                   max: 1
             - !type:DoActsBehavior