]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Refactor Crayons to use shared charges system and autonetworking. Adds auto rechargin...
authorDavid <david.owen.dev@gmail.com>
Fri, 10 Oct 2025 19:45:48 +0000 (13:45 -0600)
committerGitHub <noreply@github.com>
Fri, 10 Oct 2025 19:45:48 +0000 (19:45 +0000)
* Added special crayon with infinite charges for borg usage.

* Use battery system to manage charges.

* Reverted extra changes

* Set charge on init

* removed init assignment

* Added comments to crayoncomponent

* tweaked comments

* Working with the new charges component, but at what cost?

* Remvoed extra field

* Apply suggestion from @slarticodefast

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Apply suggestion from @slarticodefast

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Apply suggestion from @slarticodefast

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Apply suggestion from @slarticodefast

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
* Fix renamed variables and descriptions in comments

* Variable naming, comment cleanup and autonetworking.

* Fix for test case, modified on init

* Cleaned up/merged charges logic

* review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Content.Client/Crayon/CrayonComponent.cs [deleted file]
Content.Client/Crayon/CrayonSystem.cs
Content.Server/Crayon/CrayonComponent.cs [deleted file]
Content.Server/Crayon/CrayonSystem.cs
Content.Shared/Crayon/CrayonComponent.cs [new file with mode: 0644]
Content.Shared/Crayon/SharedCrayonComponent.cs [deleted file]
Resources/Prototypes/Entities/Objects/Fun/crayons.yml

diff --git a/Content.Client/Crayon/CrayonComponent.cs b/Content.Client/Crayon/CrayonComponent.cs
deleted file mode 100644 (file)
index 5729c61..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using Content.Shared.Crayon;
-using Robust.Shared.GameObjects;
-using Robust.Shared.ViewVariables;
-
-namespace Content.Client.Crayon
-{
-    [RegisterComponent]
-    public sealed partial class CrayonComponent : SharedCrayonComponent
-    {
-        [ViewVariables(VVAccess.ReadWrite)] public bool UIUpdateNeeded;
-        [ViewVariables] public int Charges { get; set; }
-        [ViewVariables] public int Capacity { get; set; }
-    }
-}
index dc039794813e9bdc3b08832a233d39c4c083b82d..e990263bf31d06df36a97abcd5441e5899c57704 100644 (file)
@@ -1,67 +1,52 @@
 using Content.Client.Items;
 using Content.Client.Message;
 using Content.Client.Stylesheets;
+using Content.Shared.Charges.Components;
+using Content.Shared.Charges.Systems;
 using Content.Shared.Crayon;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
-using Robust.Shared.GameObjects;
-using Robust.Shared.GameStates;
-using Robust.Shared.Localization;
 using Robust.Shared.Timing;
 
 namespace Content.Client.Crayon;
 
 public sealed class CrayonSystem : SharedCrayonSystem
 {
-    // Didn't do in shared because I don't think most of the server stuff can be predicted.
+    [Dependency] private readonly SharedChargesSystem _charges = default!;
+    [Dependency] private readonly EntityManager _entityManager = default!;
+
     public override void Initialize()
     {
         base.Initialize();
-        SubscribeLocalEvent<CrayonComponent, ComponentHandleState>(OnCrayonHandleState);
-        Subs.ItemStatus<CrayonComponent>(ent => new StatusControl(ent));
-    }
-
-    private static void OnCrayonHandleState(EntityUid uid, CrayonComponent component, ref ComponentHandleState args)
-    {
-        if (args.Current is not CrayonComponentState state) return;
 
-        component.Color = state.Color;
-        component.SelectedState = state.State;
-        component.Charges = state.Charges;
-        component.Capacity = state.Capacity;
-
-        component.UIUpdateNeeded = true;
+        Subs.ItemStatus<CrayonComponent>(ent => new StatusControl(ent, _charges, _entityManager));
     }
 
     private sealed class StatusControl : Control
     {
-        private readonly CrayonComponent _parent;
+        private readonly Entity<CrayonComponent> _crayon;
+        private readonly SharedChargesSystem _charges;
         private readonly RichTextLabel _label;
+        private readonly int _capacity;
 
-        public StatusControl(CrayonComponent parent)
+        public StatusControl(Entity<CrayonComponent> crayon, SharedChargesSystem charges, EntityManager entityManage)
         {
-            _parent = parent;
+            _crayon = crayon;
+            _charges = charges;
+            _capacity = entityManage.GetComponent<LimitedChargesComponent>(_crayon.Owner).MaxCharges;
             _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
             AddChild(_label);
-
-            parent.UIUpdateNeeded = true;
         }
 
         protected override void FrameUpdate(FrameEventArgs args)
         {
             base.FrameUpdate(args);
 
-            if (!_parent.UIUpdateNeeded)
-            {
-                return;
-            }
-
-            _parent.UIUpdateNeeded = false;
             _label.SetMarkup(Robust.Shared.Localization.Loc.GetString("crayon-drawing-label",
-                ("color",_parent.Color),
-                ("state",_parent.SelectedState),
-                ("charges", _parent.Charges),
-                ("capacity",_parent.Capacity)));
+                ("color",_crayon.Comp.Color),
+                ("state",_crayon.Comp.SelectedState),
+                ("charges", _charges.GetCurrentCharges(_crayon.Owner)),
+                ("capacity", _capacity)));
         }
     }
 }
diff --git a/Content.Server/Crayon/CrayonComponent.cs b/Content.Server/Crayon/CrayonComponent.cs
deleted file mode 100644 (file)
index df20681..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-using Content.Server.UserInterface;
-using Content.Shared.Crayon;
-using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
-
-namespace Content.Server.Crayon
-{
-    [RegisterComponent]
-    public sealed partial class CrayonComponent : SharedCrayonComponent
-    {
-        [DataField("useSound")] public SoundSpecifier? UseSound;
-
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("selectableColor")]
-        public bool SelectableColor { get; set; }
-
-        [ViewVariables(VVAccess.ReadWrite)]
-        public int Charges { get; set; }
-
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("capacity")]
-        public int Capacity { get; set; } = 30;
-
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("deleteEmpty")]
-        public bool DeleteEmpty = true;
-    }
-}
index f3abc2bf7a86a386118e7f1f708f41326bc7f905..07b580fba56665832a0e1189854c066203c526ac 100644 (file)
@@ -3,6 +3,7 @@ using System.Numerics;
 using Content.Server.Administration.Logs;
 using Content.Server.Decals;
 using Content.Server.Popups;
+using Content.Shared.Charges.Systems;
 using Content.Shared.Crayon;
 using Content.Shared.Database;
 using Content.Shared.Decals;
@@ -12,7 +13,6 @@ using Content.Shared.Nutrition.EntitySystems;
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
-using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
 
 namespace Content.Server.Crayon;
@@ -24,23 +24,27 @@ public sealed class CrayonSystem : SharedCrayonSystem
     [Dependency] private readonly DecalSystem _decals = default!;
     [Dependency] private readonly PopupSystem _popup = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedChargesSystem _charges = default!;
     [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
 
     public override void Initialize()
     {
         base.Initialize();
-        SubscribeLocalEvent<CrayonComponent, ComponentInit>(OnCrayonInit);
+
+        SubscribeLocalEvent<CrayonComponent, MapInitEvent>(OnMapInit);
         SubscribeLocalEvent<CrayonComponent, CrayonSelectMessage>(OnCrayonBoundUI);
         SubscribeLocalEvent<CrayonComponent, CrayonColorMessage>(OnCrayonBoundUIColor);
         SubscribeLocalEvent<CrayonComponent, UseInHandEvent>(OnCrayonUse, before: new[] { typeof(FoodSystem) });
         SubscribeLocalEvent<CrayonComponent, AfterInteractEvent>(OnCrayonAfterInteract, after: new[] { typeof(FoodSystem) });
         SubscribeLocalEvent<CrayonComponent, DroppedEvent>(OnCrayonDropped);
-        SubscribeLocalEvent<CrayonComponent, ComponentGetState>(OnCrayonGetState);
     }
 
-    private static void OnCrayonGetState(EntityUid uid, CrayonComponent component, ref ComponentGetState args)
+    private void OnMapInit(Entity<CrayonComponent> ent, ref MapInitEvent args)
     {
-        args.State = new CrayonComponentState(component.Color, component.SelectedState, component.Charges, component.Capacity);
+        // Get the first one from the catalog and set it as default
+        var decal = _prototypeManager.EnumeratePrototypes<DecalPrototype>().FirstOrDefault(x => x.Tags.Contains("crayon"));
+        ent.Comp.SelectedState = decal?.ID ?? string.Empty;
+        Dirty(ent);
     }
 
     private void OnCrayonAfterInteract(EntityUid uid, CrayonComponent component, AfterInteractEvent args)
@@ -48,7 +52,7 @@ public sealed class CrayonSystem : SharedCrayonSystem
         if (args.Handled || !args.CanReach)
             return;
 
-        if (component.Charges <= 0)
+        if (_charges.IsEmpty(uid))
         {
             if (component.DeleteEmpty)
                 UseUpCrayon(uid, args.User);
@@ -72,17 +76,15 @@ public sealed class CrayonSystem : SharedCrayonSystem
         if (component.UseSound != null)
             _audio.PlayPvs(component.UseSound, uid, AudioParams.Default.WithVariation(0.125f));
 
-        // Decrease "Ammo"
-        component.Charges--;
-        Dirty(uid, component);
+        _charges.TryUseCharge(uid);
 
         _adminLogger.Add(LogType.CrayonDraw, LogImpact.Low, $"{ToPrettyString(args.User):user} drew a {component.Color:color} {component.SelectedState}");
         args.Handled = true;
 
-        if (component.DeleteEmpty && component.Charges <= 0)
+        if (component.DeleteEmpty && _charges.IsEmpty(uid))
             UseUpCrayon(uid, args.User);
         else
-            _uiSystem.ServerSendUiMessage(uid, SharedCrayonComponent.CrayonUiKey.Key, new CrayonUsedMessage(component.SelectedState));
+            _uiSystem.ServerSendUiMessage(uid, CrayonUiKey.Key, new CrayonUsedMessage(component.SelectedState));
     }
 
     private void OnCrayonUse(EntityUid uid, CrayonComponent component, UseInHandEvent args)
@@ -91,14 +93,12 @@ public sealed class CrayonSystem : SharedCrayonSystem
         if (args.Handled)
             return;
 
-        if (!_uiSystem.HasUi(uid, SharedCrayonComponent.CrayonUiKey.Key))
-        {
+        if (!_uiSystem.HasUi(uid, CrayonUiKey.Key))
             return;
-        }
 
-        _uiSystem.TryToggleUi(uid, SharedCrayonComponent.CrayonUiKey.Key, args.User);
+        _uiSystem.TryToggleUi(uid, CrayonUiKey.Key, args.User);
 
-        _uiSystem.SetUiState(uid, SharedCrayonComponent.CrayonUiKey.Key, new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color));
+        _uiSystem.SetUiState(uid, CrayonUiKey.Key, new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color));
         args.Handled = true;
     }
 
@@ -109,35 +109,23 @@ public sealed class CrayonSystem : SharedCrayonSystem
             return;
 
         component.SelectedState = args.State;
-
         Dirty(uid, component);
     }
 
     private void OnCrayonBoundUIColor(EntityUid uid, CrayonComponent component, CrayonColorMessage args)
     {
-        // you still need to ensure that the given color is a valid color
+        // Ensure that the given color can be changed or already matches
         if (!component.SelectableColor || args.Color == component.Color)
             return;
 
         component.Color = args.Color;
         Dirty(uid, component);
-
-    }
-
-    private void OnCrayonInit(EntityUid uid, CrayonComponent component, ComponentInit args)
-    {
-        component.Charges = component.Capacity;
-
-        // Get the first one from the catalog and set it as default
-        var decal = _prototypeManager.EnumeratePrototypes<DecalPrototype>().FirstOrDefault(x => x.Tags.Contains("crayon"));
-        component.SelectedState = decal?.ID ?? string.Empty;
-        Dirty(uid, component);
     }
 
     private void OnCrayonDropped(EntityUid uid, CrayonComponent component, DroppedEvent args)
     {
         // TODO: Use the existing event.
-        _uiSystem.CloseUi(uid, SharedCrayonComponent.CrayonUiKey.Key, args.User);
+        _uiSystem.CloseUi(uid, CrayonUiKey.Key, args.User);
     }
 
     private void UseUpCrayon(EntityUid uid, EntityUid user)
diff --git a/Content.Shared/Crayon/CrayonComponent.cs b/Content.Shared/Crayon/CrayonComponent.cs
new file mode 100644 (file)
index 0000000..c772b43
--- /dev/null
@@ -0,0 +1,119 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Crayon;
+
+/// <summary>
+/// Component holding the state of a crayon-like component
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedCrayonSystem))]
+public sealed partial class CrayonComponent : Component
+{
+    /// <summary>
+    /// The ID of currently selected decal prototype that will be placed when the crayon is used.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string SelectedState;
+
+    /// <summary>
+    /// Color with which the crayon will draw.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public Color Color;
+
+    /// <summary>
+    /// Play a sound when drawing if specified.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier? UseSound;
+
+    /// <summary>
+    /// Can the color can be changed?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool SelectableColor;
+
+    /// <summary>
+    /// Should the crayon be deleted when all charges are consumed?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool DeleteEmpty = true;
+}
+
+/// <summary>
+/// Opens the crayon window for decal and color selection.
+/// </summary>
+[Serializable, NetSerializable]
+public enum CrayonUiKey : byte
+{
+    Key,
+}
+
+/// <summary>
+/// Used by the client to notify the server about the selected decal ID
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class CrayonSelectMessage : BoundUserInterfaceMessage
+{
+    public readonly string State;
+    public CrayonSelectMessage(string selected)
+    {
+        State = selected;
+    }
+}
+
+/// <summary>
+/// Sets the color of the crayon, used by Rainbow Crayon
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class CrayonColorMessage : BoundUserInterfaceMessage
+{
+    public readonly Color Color;
+    public CrayonColorMessage(Color color)
+    {
+        Color = color;
+    }
+}
+
+/// <summary>
+/// Server to CLIENT. Notifies the BUI that a decal with given ID has been drawn.
+/// Allows the client UI to advance forward in the client-only ephemeral queue,
+/// preventing the crayon from becoming a magic text storage device.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class CrayonUsedMessage : BoundUserInterfaceMessage
+{
+    public readonly string DrawnDecal;
+
+    public CrayonUsedMessage(string drawn)
+    {
+        DrawnDecal = drawn;
+    }
+}
+
+/// <summary>
+/// The state of the crayon UI as sent by the server
+/// </summary>
+/// <summary>
+/// TODO: Delete this and use component states and predict the UI.
+/// This info is already networked on its own.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class CrayonBoundUserInterfaceState : BoundUserInterfaceState
+{
+    public string Selected;
+    /// <summary>
+    /// Can the color can be changed
+    /// </summary>
+    public bool SelectableColor;
+    public Color Color;
+
+    public CrayonBoundUserInterfaceState(string selected, bool selectableColor, Color color)
+    {
+        Selected = selected;
+        SelectableColor = selectableColor;
+        Color = color;
+    }
+}
diff --git a/Content.Shared/Crayon/SharedCrayonComponent.cs b/Content.Shared/Crayon/SharedCrayonComponent.cs
deleted file mode 100644 (file)
index a9c2198..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Crayon
-{
-
-    /// <summary>
-    /// Component holding the state of a crayon-like component
-    /// </summary>
-    [NetworkedComponent, ComponentProtoName("Crayon"), Access(typeof(SharedCrayonSystem))]
-    public abstract partial class SharedCrayonComponent : Component
-    {
-        /// <summary>
-        /// The ID of currently selected decal prototype that will be placed when the crayon is used
-        /// </summary>
-        public string SelectedState { get; set; } = string.Empty;
-
-        /// <summary>
-        /// Color with which the crayon will draw
-        /// </summary>
-        [DataField("color")]
-        public Color Color;
-
-        [Serializable, NetSerializable]
-        public enum CrayonUiKey : byte
-        {
-            Key,
-        }
-    }
-
-    /// <summary>
-    /// Used by the client to notify the server about the selected decal ID
-    /// </summary>
-    [Serializable, NetSerializable]
-    public sealed class CrayonSelectMessage : BoundUserInterfaceMessage
-    {
-        public readonly string State;
-        public CrayonSelectMessage(string selected)
-        {
-            State = selected;
-        }
-    }
-
-    /// <summary>
-    /// Sets the color of the crayon, used by Rainbow Crayon
-    /// </summary>
-    [Serializable, NetSerializable]
-    public sealed class CrayonColorMessage : BoundUserInterfaceMessage
-    {
-        public readonly Color Color;
-        public CrayonColorMessage(Color color)
-        {
-            Color = color;
-        }
-    }
-
-    /// <summary>
-    /// Server to CLIENT. Notifies the BUI that a decal with given ID has been drawn.
-    /// Allows the client UI to advance forward in the client-only ephemeral queue,
-    /// preventing the crayon from becoming a magic text storage device.
-    /// </summary>
-    [Serializable, NetSerializable]
-    public sealed class CrayonUsedMessage : BoundUserInterfaceMessage
-    {
-        public readonly string DrawnDecal;
-
-        public CrayonUsedMessage(string drawn)
-        {
-            DrawnDecal = drawn;
-        }
-    }
-
-    /// <summary>
-    /// Component state, describes how many charges are left in the crayon in the near-hand UI
-    /// </summary>
-    [Serializable, NetSerializable]
-    public sealed class CrayonComponentState : ComponentState
-    {
-        public readonly Color Color;
-        public readonly string State;
-        public readonly int Charges;
-        public readonly int Capacity;
-
-        public CrayonComponentState(Color color, string state, int charges, int capacity)
-        {
-            Color = color;
-            State = state;
-            Charges = charges;
-            Capacity = capacity;
-        }
-    }
-
-    /// <summary>
-    /// The state of the crayon UI as sent by the server
-    /// </summary>
-    [Serializable, NetSerializable]
-    public sealed class CrayonBoundUserInterfaceState : BoundUserInterfaceState
-    {
-        public string Selected;
-        /// <summary>
-        /// Whether or not the color can be selected
-        /// </summary>
-        public bool SelectableColor;
-        public Color Color;
-
-        public CrayonBoundUserInterfaceState(string selected, bool selectableColor, Color color)
-        {
-            Selected = selected;
-            SelectableColor = selectableColor;
-            Color = color;
-        }
-    }
-}
index a32e3ba89caae08bb363ff4351c2c5113fad0da1..85be8ece4529a5d261b4d2a023385c591578d58f 100644 (file)
@@ -21,7 +21,9 @@
       enum.CrayonUiKey.Key:
         type: CrayonBoundUserInterface
   - type: Crayon
-    capacity: 25
+    selectedState: like
+  - type: LimitedCharges
+    maxCharges: 25
   - type: Food
   - type: FlavorProfile
     flavors:
@@ -88,7 +90,8 @@
   - type: Crayon
     color: Red
     selectableColor: true
-    capacity: 30
+  - type: LimitedCharges
+    maxCharges: 30
   - type: Tag
     tags:
     - Write
     - Recyclable
     - Trash
 
+- type: entity
+  parent: CrayonRainbow
+  id: CrayonInfinite # should not be player available to prevent decal spam
+  name: infinite crayon
+  components:
+  - type: Crayon
+    deleteEmpty: false
+  - type: AutoRecharge
+    rechargeDuration: 5
+
 - type: entity
   parent: Crayon
   id: CrayonBlack