From d279f6172a7337fa9e2ac74ae1a37f3899c61043 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 29 Jul 2022 13:02:09 +1200 Subject: [PATCH] Fix item/clothing visual & networking bugs (#10116) --- .../Components/HandheldLightComponent.cs | 146 ++++++++---------- Content.Client/Light/HandheldLightSystem.cs | 33 +--- .../Light/Visualizers/FlashLightVisualizer.cs | 1 + .../Tests/DeleteInventoryTest.cs | 6 +- .../Components/HandheldLightComponent.cs | 31 ---- .../EntitySystems/HandheldLightSystem.cs | 47 ++---- .../Nutrition/EntitySystems/SmokingSystem.cs | 3 + .../Components/SharedClothingComponent.cs | 5 +- .../Clothing/EntitySystems/ClothingSystem.cs | 23 ++- Content.Shared/Item/ItemComponent.cs | 7 +- Content.Shared/Item/SharedItemSystem.cs | 9 +- .../Light/Component/HandheldLightComponent.cs | 76 +++++++++ .../Component/SharedHandheldLightComponent.cs | 49 ------ .../Light/SharedHandheldLightSystem.cs | 79 ++++++++++ .../Clothing/Head/hardsuit-helmets.yml | 1 + .../Entities/Objects/Tools/flashlights.yml | 1 + 16 files changed, 278 insertions(+), 239 deletions(-) delete mode 100644 Content.Server/Light/Components/HandheldLightComponent.cs create mode 100644 Content.Shared/Light/Component/HandheldLightComponent.cs delete mode 100644 Content.Shared/Light/Component/SharedHandheldLightComponent.cs create mode 100644 Content.Shared/Light/SharedHandheldLightSystem.cs diff --git a/Content.Client/Light/Components/HandheldLightComponent.cs b/Content.Client/Light/Components/HandheldLightComponent.cs index 0a5f43ea93..e777f14a63 100644 --- a/Content.Client/Light/Components/HandheldLightComponent.cs +++ b/Content.Client/Light/Components/HandheldLightComponent.cs @@ -1,4 +1,4 @@ -using Content.Client.Items.Components; +using Content.Shared.Light; using Content.Shared.Light.Component; using Robust.Client.Graphics; using Robust.Client.UserInterface; @@ -6,103 +6,79 @@ using Robust.Client.UserInterface.Controls; using Robust.Shared.Timing; using static Robust.Client.UserInterface.Controls.BoxContainer; -namespace Content.Client.Light.Components +namespace Content.Client.Light.Components; + +public sealed class HandheldLightStatus : Control { - [RegisterComponent] - [Access(typeof(HandheldLightSystem))] - public sealed class HandheldLightComponent : SharedHandheldLightComponent, IItemStatus + private const float TimerCycle = 1; + + private readonly HandheldLightComponent _parent; + private readonly PanelContainer[] _sections = new PanelContainer[HandheldLightComponent.StatusLevels - 1]; + + private float _timer; + + private static readonly StyleBoxFlat StyleBoxLit = new() { - public byte? Level; - public bool Activated; + BackgroundColor = Color.LimeGreen + }; - /// - /// Whether to automatically set item-prefixes when toggling the flashlight. - /// - /// - /// Flashlights should probably be using explicit unshaded sprite, in-hand and clothing layers, this is - /// mostly here for backwards compatibility. - /// - [DataField("addPrefix")] - public bool AddPrefix = false; + private static readonly StyleBoxFlat StyleBoxUnlit = new() + { + BackgroundColor = Color.Black + }; - public Control MakeControl() + public HandheldLightStatus(HandheldLightComponent parent) + { + _parent = parent; + + var wrapper = new BoxContainer { - return new StatusControl(this); + Orientation = LayoutOrientation.Horizontal, + SeparationOverride = 4, + HorizontalAlignment = HAlignment.Center + }; + + AddChild(wrapper); + + for (var i = 0; i < _sections.Length; i++) + { + var panel = new PanelContainer {MinSize = (20, 20)}; + wrapper.AddChild(panel); + _sections[i] = panel; } + } - private sealed class StatusControl : Control + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + _timer += args.DeltaSeconds; + _timer %= TimerCycle; + + var level = _parent.Level; + + for (var i = 0; i < _sections.Length; i++) { - private const float TimerCycle = 1; - - private readonly HandheldLightComponent _parent; - private readonly PanelContainer[] _sections = new PanelContainer[StatusLevels - 1]; - - private float _timer; - - private static readonly StyleBoxFlat StyleBoxLit = new() + if (i == 0) { - BackgroundColor = Color.LimeGreen - }; - - private static readonly StyleBoxFlat StyleBoxUnlit = new() - { - BackgroundColor = Color.Black - }; - - public StatusControl(HandheldLightComponent parent) - { - _parent = parent; - - var wrapper = new BoxContainer + if (level == 0 || level == null) { - Orientation = LayoutOrientation.Horizontal, - SeparationOverride = 4, - HorizontalAlignment = HAlignment.Center - }; - - AddChild(wrapper); - - for (var i = 0; i < _sections.Length; i++) - { - var panel = new PanelContainer {MinSize = (20, 20)}; - wrapper.AddChild(panel); - _sections[i] = panel; + _sections[0].PanelOverride = StyleBoxUnlit; } + else if (level == 1) + { + // Flash the last light. + _sections[0].PanelOverride = _timer > TimerCycle / 2 ? StyleBoxLit : StyleBoxUnlit; + } + else + { + _sections[0].PanelOverride = StyleBoxLit; + } + + continue; } - protected override void FrameUpdate(FrameEventArgs args) - { - base.FrameUpdate(args); - - _timer += args.DeltaSeconds; - _timer %= TimerCycle; - - var level = _parent.Level; - - for (var i = 0; i < _sections.Length; i++) - { - if (i == 0) - { - if (level == 0 || level == null) - { - _sections[0].PanelOverride = StyleBoxUnlit; - } - else if (level == 1) - { - // Flash the last light. - _sections[0].PanelOverride = _timer > TimerCycle / 2 ? StyleBoxLit : StyleBoxUnlit; - } - else - { - _sections[0].PanelOverride = StyleBoxLit; - } - - continue; - } - - _sections[i].PanelOverride = level >= i + 2 ? StyleBoxLit : StyleBoxUnlit; - } - } + _sections[i].PanelOverride = level >= i + 2 ? StyleBoxLit : StyleBoxUnlit; } } } diff --git a/Content.Client/Light/HandheldLightSystem.cs b/Content.Client/Light/HandheldLightSystem.cs index 1f248c497a..a78464abb9 100644 --- a/Content.Client/Light/HandheldLightSystem.cs +++ b/Content.Client/Light/HandheldLightSystem.cs @@ -1,38 +1,21 @@ -using Content.Client.Items.Systems; +using Content.Client.Items; using Content.Client.Light.Components; -using Content.Shared.Item; +using Content.Shared.Light; using Content.Shared.Light.Component; -using Robust.Shared.GameStates; namespace Content.Client.Light; -public sealed class HandheldLightSystem : EntitySystem +public sealed class HandheldLightSystem : SharedHandheldLightSystem { - [Dependency] private readonly ItemSystem _itemSys = default!; - public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnHandleState); + + SubscribeLocalEvent(OnGetStatusControl); } - - private void OnHandleState(EntityUid uid, HandheldLightComponent component, ref ComponentHandleState args) + + private static void OnGetStatusControl(EntityUid uid, HandheldLightComponent component, ItemStatusCollectMessage args) { - if (args.Current is not SharedHandheldLightComponent.HandheldLightComponentState state) - return; - - component.Level = state.Charge; - - if (state.Activated == component.Activated) - return; - - component.Activated = state.Activated; - - // really hand-held lights should be using a separate unshaded layer. (see FlashlightVisualizer) - // this prefix stuff is largely for backwards compatibility with RSIs/yamls that have not been updated. - if (component.AddPrefix && TryComp(uid, out ItemComponent? item)) - { - _itemSys.SetHeldPrefix(uid, state.Activated ? "on" : "off", item); - } + args.Controls.Add(new HandheldLightStatus(component)); } } diff --git a/Content.Client/Light/Visualizers/FlashLightVisualizer.cs b/Content.Client/Light/Visualizers/FlashLightVisualizer.cs index faa8b457bd..663676a237 100644 --- a/Content.Client/Light/Visualizers/FlashLightVisualizer.cs +++ b/Content.Client/Light/Visualizers/FlashLightVisualizer.cs @@ -1,4 +1,5 @@ using System; +using Content.Shared.Light; using Content.Shared.Light.Component; using JetBrains.Annotations; using Robust.Client.Animations; diff --git a/Content.IntegrationTests/Tests/DeleteInventoryTest.cs b/Content.IntegrationTests/Tests/DeleteInventoryTest.cs index 168cbcd9be..0bc69de7ea 100644 --- a/Content.IntegrationTests/Tests/DeleteInventoryTest.cs +++ b/Content.IntegrationTests/Tests/DeleteInventoryTest.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Content.Server.Clothing.Components; using Content.Server.Inventory; +using Content.Shared.Clothing.EntitySystems; using Content.Shared.Inventory; using Content.Shared.Item; using NUnit.Framework; @@ -37,7 +38,8 @@ namespace Content.IntegrationTests.Tests var child = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); var item = entMgr.AddComponent(child); - item.Slots = SlotFlags.HEAD; + + IoCManager.Resolve().GetEntitySystem().SetSlots(item.Owner, SlotFlags.HEAD, item); // Equip item. Assert.That(invSystem.TryEquip(container, child, "head"), Is.True); diff --git a/Content.Server/Light/Components/HandheldLightComponent.cs b/Content.Server/Light/Components/HandheldLightComponent.cs deleted file mode 100644 index 0c347227c3..0000000000 --- a/Content.Server/Light/Components/HandheldLightComponent.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Content.Server.Light.EntitySystems; -using Content.Shared.Light.Component; -using Content.Shared.Sound; - -namespace Content.Server.Light.Components -{ - /// - /// Component that represents a powered handheld light source which can be toggled on and off. - /// - [RegisterComponent] - [Access(typeof(HandheldLightSystem))] - public sealed class HandheldLightComponent : SharedHandheldLightComponent - { - [ViewVariables(VVAccess.ReadWrite)] [DataField("wattage")] public float Wattage { get; set; } = .8f; - - /// - /// Status of light, whether or not it is emitting light. - /// - [ViewVariables] - public bool Activated { get; set; } - - [ViewVariables(VVAccess.ReadWrite)] [DataField("turnOnSound")] public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Items/flashlight_on.ogg"); - [ViewVariables(VVAccess.ReadWrite)] [DataField("turnOnFailSound")] public SoundSpecifier TurnOnFailSound = new SoundPathSpecifier("/Audio/Machines/button.ogg"); - [ViewVariables(VVAccess.ReadWrite)] [DataField("turnOffSound")] public SoundSpecifier TurnOffSound = new SoundPathSpecifier("/Audio/Items/flashlight_off.ogg"); - - /// - /// Client-side ItemStatus level - /// - public byte? LastLevel; - } -} diff --git a/Content.Server/Light/EntitySystems/HandheldLightSystem.cs b/Content.Server/Light/EntitySystems/HandheldLightSystem.cs index 562f6c0eaf..bf85019552 100644 --- a/Content.Server/Light/EntitySystems/HandheldLightSystem.cs +++ b/Content.Server/Light/EntitySystems/HandheldLightSystem.cs @@ -1,13 +1,11 @@ using Content.Server.Actions; -using Content.Server.Light.Components; using Content.Server.Popups; -using Content.Server.Power.Components; using Content.Server.PowerCell; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; using Content.Shared.Examine; using Content.Shared.Interaction; -using Content.Shared.Light.Component; +using Content.Shared.Light; using Content.Shared.Rounding; using Content.Shared.Toggleable; using Content.Shared.Verbs; @@ -23,7 +21,7 @@ using Robust.Shared.Utility; namespace Content.Server.Light.EntitySystems { [UsedImplicitly] - public sealed class HandheldLightSystem : EntitySystem + public sealed class HandheldLightSystem : SharedHandheldLightSystem { [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; @@ -38,7 +36,6 @@ namespace Content.Server.Light.EntitySystems { base.Initialize(); - SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnGetState); @@ -99,7 +96,7 @@ namespace Content.Server.Light.EntitySystems private void OnGetState(EntityUid uid, HandheldLightComponent component, ref ComponentGetState args) { - args.State = new SharedHandheldLightComponent.HandheldLightComponentState(component.Activated, GetLevel(component)); + args.State = new HandheldLightComponent.HandheldLightComponentState(component.Activated, GetLevel(component)); } private byte? GetLevel(HandheldLightComponent component) @@ -113,15 +110,7 @@ namespace Content.Server.Light.EntitySystems if (MathHelper.CloseToPercent(battery.CurrentCharge, 0) || component.Wattage > battery.CurrentCharge) return 0; - return (byte?) ContentHelpers.RoundToNearestLevels(battery.CurrentCharge / battery.MaxCharge * 255, 255, SharedHandheldLightComponent.StatusLevels); - } - - private void OnInit(EntityUid uid, HandheldLightComponent component, ComponentInit args) - { - EntityManager.EnsureComponent(uid); - - // Want to make sure client has latest data on level so battery displays properly. - component.Dirty(EntityManager); + return (byte?) ContentHelpers.RoundToNearestLevels(battery.CurrentCharge / battery.MaxCharge * 255, 255, HandheldLightComponent.StatusLevels); } private void OnRemove(EntityUid uid, HandheldLightComponent component, ComponentRemove args) @@ -201,19 +190,9 @@ namespace Content.Server.Light.EntitySystems { if (!component.Activated) return false; - component.Activated = false; - if (component.ToggleAction != null) - _actionSystem.SetToggled(component.ToggleAction, false); + SetActivated(component.Owner, false, component, makeNoise); + component.Level = null; _activeLights.Remove(component); - component.LastLevel = null; - Dirty(component); - - if (TryComp(component.Owner, out AppearanceComponent? appearance)) - appearance.SetData(ToggleableLightVisuals.Enabled, false); - - if (makeNoise) - SoundSystem.Play(component.TurnOffSound.GetSound(), Filter.Pvs(component.Owner, entityManager: EntityManager), component.Owner); - return true; } @@ -239,17 +218,9 @@ namespace Content.Server.Light.EntitySystems return false; } - component.Activated = true; - if (component.ToggleAction != null) - _actionSystem.SetToggled(component.ToggleAction, true); + SetActivated(component.Owner, true, component, true); _activeLights.Add(component); - component.LastLevel = GetLevel(component); - Dirty(component); - if (TryComp(component.Owner, out AppearanceComponent? appearance)) - appearance.SetData(ToggleableLightVisuals.Enabled, true); - - SoundSystem.Play(component.TurnOnSound.GetSound(), Filter.Pvs(component.Owner, entityManager: EntityManager), component.Owner); return true; } @@ -288,10 +259,10 @@ namespace Content.Server.Light.EntitySystems { var level = GetLevel(comp); - if (level == comp.LastLevel) + if (level == comp.Level) return; - comp.LastLevel = level; + comp.Level = level; Dirty(comp); } } diff --git a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs index 286e1b39f4..e51456f539 100644 --- a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.Clothing.EntitySystems; using Content.Shared.FixedPoint; using Content.Shared.Inventory; +using Content.Shared.Item; using Content.Shared.Smoking; using Content.Shared.Temperature; using Robust.Server.GameObjects; @@ -26,6 +27,7 @@ namespace Content.Server.Nutrition.EntitySystems [Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly ClothingSystem _clothing = default!; + [Dependency] private readonly SharedItemSystem _items = default!; private const float UpdateTimer = 3f; @@ -61,6 +63,7 @@ namespace Content.Server.Nutrition.EntitySystems }; _clothing.SetEquippedPrefix(uid, newState, clothing); + _items.SetHeldPrefix(uid, newState); if (state == SmokableState.Lit) _active.Add(uid); diff --git a/Content.Shared/Clothing/Components/SharedClothingComponent.cs b/Content.Shared/Clothing/Components/SharedClothingComponent.cs index 8830703d20..edf6e09c07 100644 --- a/Content.Shared/Clothing/Components/SharedClothingComponent.cs +++ b/Content.Shared/Clothing/Components/SharedClothingComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Clothing.EntitySystems; +using Content.Shared.Clothing.EntitySystems; using Content.Shared.Inventory; using Content.Shared.Sound; using Robust.Shared.GameStates; @@ -10,9 +10,11 @@ namespace Content.Shared.Clothing.Components; /// This handles entities which can be equipped. /// [NetworkedComponent] +[Access(typeof(ClothingSystem), typeof(InventorySystem))] public abstract class SharedClothingComponent : Component { [DataField("clothingVisuals")] + [Access(typeof(ClothingSystem), typeof(InventorySystem), Other = AccessPermissions.ReadExecute)] // TODO remove execute permissions. public Dictionary> ClothingVisuals = new(); [ViewVariables(VVAccess.ReadWrite)] @@ -21,6 +23,7 @@ public abstract class SharedClothingComponent : Component [ViewVariables(VVAccess.ReadWrite)] [DataField("slots", required: true)] + [Access(typeof(ClothingSystem), typeof(InventorySystem), Other = AccessPermissions.ReadExecute)] public SlotFlags Slots = SlotFlags.NONE; [ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs index 7fa515f852..10336d2993 100644 --- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs @@ -1,10 +1,14 @@ -using Content.Shared.Clothing.Components; +using Content.Shared.Clothing.Components; +using Content.Shared.Inventory; +using Content.Shared.Item; using Robust.Shared.GameStates; namespace Content.Shared.Clothing.EntitySystems; public sealed class ClothingSystem : EntitySystem { + [Dependency] private readonly SharedItemSystem _itemSys = default!; + public override void Initialize() { base.Initialize(); @@ -21,17 +25,30 @@ public sealed class ClothingSystem : EntitySystem private void OnHandleState(EntityUid uid, SharedClothingComponent component, ref ComponentHandleState args) { if (args.Current is ClothingComponentState state) - component.EquippedPrefix = state.EquippedPrefix; + SetEquippedPrefix(uid, state.EquippedPrefix, component); } #region Public API public void SetEquippedPrefix(EntityUid uid, string? prefix, SharedClothingComponent? clothing = null) { - if (!Resolve(uid, ref clothing)) + if (!Resolve(uid, ref clothing, false)) + return; + + if (clothing.EquippedPrefix == prefix) return; clothing.EquippedPrefix = prefix; + _itemSys.VisualsChanged(uid); + Dirty(clothing); + } + + public void SetSlots(EntityUid uid, SlotFlags slots, SharedClothingComponent? clothing = null) + { + if (!Resolve(uid, ref clothing)) + return; + + clothing.Slots = slots; Dirty(clothing); } diff --git a/Content.Shared/Item/ItemComponent.cs b/Content.Shared/Item/ItemComponent.cs index 2fae763578..d53a81104f 100644 --- a/Content.Shared/Item/ItemComponent.cs +++ b/Content.Shared/Item/ItemComponent.cs @@ -1,4 +1,5 @@ -using Content.Shared.Hands.Components; +using Content.Shared.Hands.Components; +using Robust.Shared.GameStates; using Robust.Shared.Serialization; namespace Content.Shared.Item; @@ -8,11 +9,13 @@ namespace Content.Shared.Item; /// like backpacks. /// [RegisterComponent] +[NetworkedComponent] +[Access(typeof(SharedItemSystem))] public sealed class ItemComponent : Component { - [Access(typeof(SharedItemSystem), Other = AccessPermissions.ReadExecute)] [ViewVariables(VVAccess.ReadWrite)] [DataField("size")] + [Access(typeof(SharedItemSystem), Other = AccessPermissions.ReadExecute)] public int Size = 5; [DataField("inhandVisuals")] diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs index 117e8657a1..a00d594d7a 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/SharedItemSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.Hands.EntitySystems; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Verbs; @@ -38,7 +38,10 @@ public abstract class SharedItemSystem : EntitySystem public void SetHeldPrefix(EntityUid uid, string? heldPrefix, ItemComponent? component = null) { - if (!Resolve(uid, ref component)) + if (!Resolve(uid, ref component, false)) + return; + + if (component.HeldPrefix == heldPrefix) return; component.HeldPrefix = heldPrefix; @@ -62,7 +65,7 @@ public abstract class SharedItemSystem : EntitySystem return; component.Size = state.Size; - component.HeldPrefix = state.HeldPrefix; + SetHeldPrefix(uid, state.HeldPrefix, component); } private void OnGetState(EntityUid uid, ItemComponent component, ref ComponentGetState args) diff --git a/Content.Shared/Light/Component/HandheldLightComponent.cs b/Content.Shared/Light/Component/HandheldLightComponent.cs new file mode 100644 index 0000000000..90cdb1b515 --- /dev/null +++ b/Content.Shared/Light/Component/HandheldLightComponent.cs @@ -0,0 +1,76 @@ +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Sound; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Light +{ + [NetworkedComponent] + [RegisterComponent] + [Access(typeof(SharedHandheldLightSystem))] + public sealed class HandheldLightComponent : Robust.Shared.GameObjects.Component + { + public byte? Level; + public bool Activated; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("wattage")] + public float Wattage { get; set; } = .8f; + + [DataField("turnOnSound")] + public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Items/flashlight_on.ogg"); + + [DataField("turnOnFailSound")] + public SoundSpecifier TurnOnFailSound = new SoundPathSpecifier("/Audio/Machines/button.ogg"); + + [DataField("turnOffSound")] + public SoundSpecifier TurnOffSound = new SoundPathSpecifier("/Audio/Items/flashlight_off.ogg"); + + /// + /// Whether to automatically set item-prefixes when toggling the flashlight. + /// + /// + /// Flashlights should probably be using explicit unshaded sprite, in-hand and clothing layers, this is + /// mostly here for backwards compatibility. + /// + [DataField("addPrefix")] + public bool AddPrefix = false; + + [DataField("toggleActionId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ToggleActionId = "ToggleLight"; + + [DataField("toggleAction")] + public InstantAction? ToggleAction; + + public const int StatusLevels = 6; + + [Serializable, NetSerializable] + public sealed class HandheldLightComponentState : ComponentState + { + public byte? Charge { get; } + + public bool Activated { get; } + + public HandheldLightComponentState(bool activated, byte? charge) + { + Activated = activated; + Charge = charge; + } + } + } + + [Serializable, NetSerializable] + public enum HandheldLightVisuals + { + Power + } + + [Serializable, NetSerializable] + public enum HandheldLightPowerStates + { + FullPower, + LowPower, + Dying, + } +} diff --git a/Content.Shared/Light/Component/SharedHandheldLightComponent.cs b/Content.Shared/Light/Component/SharedHandheldLightComponent.cs deleted file mode 100644 index c172e63fda..0000000000 --- a/Content.Shared/Light/Component/SharedHandheldLightComponent.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Content.Shared.Actions.ActionTypes; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Light.Component -{ - [NetworkedComponent] - public abstract class SharedHandheldLightComponent : Robust.Shared.GameObjects.Component - { - [DataField("toggleActionId", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string ToggleActionId = "ToggleLight"; - - [DataField("toggleAction")] - public InstantAction? ToggleAction; - - public const int StatusLevels = 6; - - [Serializable, NetSerializable] - public sealed class HandheldLightComponentState : ComponentState - { - public byte? Charge { get; } - - public bool Activated { get; } - - public HandheldLightComponentState(bool activated, byte? charge) - { - Activated = activated; - Charge = charge; - } - } - } - - [Serializable, NetSerializable] - public enum HandheldLightVisuals - { - Power - } - - [Serializable, NetSerializable] - public enum HandheldLightPowerStates - { - FullPower, - LowPower, - Dying, - } - - -} diff --git a/Content.Shared/Light/SharedHandheldLightSystem.cs b/Content.Shared/Light/SharedHandheldLightSystem.cs new file mode 100644 index 0000000000..13aa6c7532 --- /dev/null +++ b/Content.Shared/Light/SharedHandheldLightSystem.cs @@ -0,0 +1,79 @@ +using Content.Shared.Actions; +using Content.Shared.Clothing.EntitySystems; +using Content.Shared.Item; +using Content.Shared.Toggleable; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Shared.Light; + +public abstract class SharedHandheldLightSystem : EntitySystem +{ + [Dependency] private readonly SharedItemSystem _itemSys = default!; + [Dependency] private readonly ClothingSystem _clothingSys = default!; + [Dependency] private readonly SharedActionsSystem _actionSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnHandleState); + } + + private void OnInit(EntityUid uid, HandheldLightComponent component, ComponentInit args) + { + UpdateVisuals(uid, component); + + // Want to make sure client has latest data on level so battery displays properly. + Dirty(component); + } + + private void OnHandleState(EntityUid uid, HandheldLightComponent component, ref ComponentHandleState args) + { + if (args.Current is not HandheldLightComponent.HandheldLightComponentState state) + return; + + component.Level = state.Charge; + SetActivated(uid, state.Activated, component, false); + } + + public void SetActivated(EntityUid uid, bool activated, HandheldLightComponent? component = null, bool makeNoise = true) + { + if (!Resolve(uid, ref component)) + return; + + if (component.Activated == activated) + return; + + component.Activated = activated; + + if (makeNoise) + { + var sound = component.Activated ? component.TurnOnSound : component.TurnOffSound; + SoundSystem.Play(sound.GetSound(), Filter.Pvs(component.Owner, entityManager: EntityManager), component.Owner); + } + + Dirty(component); + UpdateVisuals(uid, component); + } + + public void UpdateVisuals(EntityUid uid, HandheldLightComponent? component = null, AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref component, ref appearance, false)) + return; + + if (component.AddPrefix) + { + var prefix = component.Activated ? "on" : "off"; + _itemSys.SetHeldPrefix(uid, prefix); + _clothingSys.SetEquippedPrefix(uid, prefix); + } + + if (component.ToggleAction != null) + _actionSystem.SetToggled(component.ToggleAction, component.Activated); + + appearance.SetData(ToggleableLightVisuals.Enabled, component.Activated); + } +} diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index ec3687f783..ef5e5b8d4a 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -292,6 +292,7 @@ map: [ "light" ] - type: HandheldLight addPrefix: false + - type: ToggleableLightVisuals clothingVisuals: head: - state: equipped-head-light diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml index 169aa54bf5..bb436edf6c 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml @@ -63,6 +63,7 @@ startingItem: PowerCellHigh - type: HandheldLight addPrefix: false + - type: ToggleableLightVisuals inhandVisuals: left: - state: inhand-left-light