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