Fix item/clothing visual & networking bugs (#10116)

This commit is contained in:
Leon Friedrich
2022-07-29 13:02:09 +12:00
committed by GitHub
parent e22679501c
commit d279f6172a
16 changed files with 278 additions and 239 deletions

View File

@@ -1,4 +1,4 @@
using Content.Client.Items.Components; using Content.Shared.Light;
using Content.Shared.Light.Component; using Content.Shared.Light.Component;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
@@ -6,103 +6,79 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using static Robust.Client.UserInterface.Controls.BoxContainer; using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Light.Components namespace Content.Client.Light.Components;
public sealed class HandheldLightStatus : Control
{ {
[RegisterComponent] private const float TimerCycle = 1;
[Access(typeof(HandheldLightSystem))]
public sealed class HandheldLightComponent : SharedHandheldLightComponent, IItemStatus 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; BackgroundColor = Color.LimeGreen
public bool Activated; };
/// <summary> private static readonly StyleBoxFlat StyleBoxUnlit = new()
/// Whether to automatically set item-prefixes when toggling the flashlight. {
/// </summary> BackgroundColor = Color.Black
/// <remarks> };
/// Flashlights should probably be using explicit unshaded sprite, in-hand and clothing layers, this is
/// mostly here for backwards compatibility.
/// </remarks>
[DataField("addPrefix")]
public bool AddPrefix = false;
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; if (i == 0)
private readonly HandheldLightComponent _parent;
private readonly PanelContainer[] _sections = new PanelContainer[StatusLevels - 1];
private float _timer;
private static readonly StyleBoxFlat StyleBoxLit = new()
{ {
BackgroundColor = Color.LimeGreen if (level == 0 || level == null)
};
private static readonly StyleBoxFlat StyleBoxUnlit = new()
{
BackgroundColor = Color.Black
};
public StatusControl(HandheldLightComponent parent)
{
_parent = parent;
var wrapper = new BoxContainer
{ {
Orientation = LayoutOrientation.Horizontal, _sections[0].PanelOverride = StyleBoxUnlit;
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;
} }
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) _sections[i].PanelOverride = level >= i + 2 ? StyleBoxLit : StyleBoxUnlit;
{
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;
}
}
} }
} }
} }

View File

@@ -1,38 +1,21 @@
using Content.Client.Items.Systems; using Content.Client.Items;
using Content.Client.Light.Components; using Content.Client.Light.Components;
using Content.Shared.Item; using Content.Shared.Light;
using Content.Shared.Light.Component; using Content.Shared.Light.Component;
using Robust.Shared.GameStates;
namespace Content.Client.Light; namespace Content.Client.Light;
public sealed class HandheldLightSystem : EntitySystem public sealed class HandheldLightSystem : SharedHandheldLightSystem
{ {
[Dependency] private readonly ItemSystem _itemSys = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<HandheldLightComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<HandheldLightComponent, ItemStatusCollectMessage>(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) args.Controls.Add(new HandheldLightStatus(component));
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);
}
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using Content.Shared.Light;
using Content.Shared.Light.Component; using Content.Shared.Light.Component;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.Animations; using Robust.Client.Animations;

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Clothing.Components; using Content.Server.Clothing.Components;
using Content.Server.Inventory; using Content.Server.Inventory;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Item; using Content.Shared.Item;
using NUnit.Framework; using NUnit.Framework;
@@ -37,7 +38,8 @@ namespace Content.IntegrationTests.Tests
var child = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); var child = entMgr.SpawnEntity(null, MapCoordinates.Nullspace);
var item = entMgr.AddComponent<ClothingComponent>(child); var item = entMgr.AddComponent<ClothingComponent>(child);
item.Slots = SlotFlags.HEAD;
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ClothingSystem>().SetSlots(item.Owner, SlotFlags.HEAD, item);
// Equip item. // Equip item.
Assert.That(invSystem.TryEquip(container, child, "head"), Is.True); Assert.That(invSystem.TryEquip(container, child, "head"), Is.True);

View File

@@ -1,31 +0,0 @@
using Content.Server.Light.EntitySystems;
using Content.Shared.Light.Component;
using Content.Shared.Sound;
namespace Content.Server.Light.Components
{
/// <summary>
/// Component that represents a powered handheld light source which can be toggled on and off.
/// </summary>
[RegisterComponent]
[Access(typeof(HandheldLightSystem))]
public sealed class HandheldLightComponent : SharedHandheldLightComponent
{
[ViewVariables(VVAccess.ReadWrite)] [DataField("wattage")] public float Wattage { get; set; } = .8f;
/// <summary>
/// Status of light, whether or not it is emitting light.
/// </summary>
[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");
/// <summary>
/// Client-side ItemStatus level
/// </summary>
public byte? LastLevel;
}
}

View File

@@ -1,13 +1,11 @@
using Content.Server.Actions; using Content.Server.Actions;
using Content.Server.Light.Components;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.PowerCell; using Content.Server.PowerCell;
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes; using Content.Shared.Actions.ActionTypes;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Light.Component; using Content.Shared.Light;
using Content.Shared.Rounding; using Content.Shared.Rounding;
using Content.Shared.Toggleable; using Content.Shared.Toggleable;
using Content.Shared.Verbs; using Content.Shared.Verbs;
@@ -23,7 +21,7 @@ using Robust.Shared.Utility;
namespace Content.Server.Light.EntitySystems namespace Content.Server.Light.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public sealed class HandheldLightSystem : EntitySystem public sealed class HandheldLightSystem : SharedHandheldLightSystem
{ {
[Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!;
@@ -38,7 +36,6 @@ namespace Content.Server.Light.EntitySystems
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<HandheldLightComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<HandheldLightComponent, ComponentRemove>(OnRemove); SubscribeLocalEvent<HandheldLightComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<HandheldLightComponent, ComponentGetState>(OnGetState); SubscribeLocalEvent<HandheldLightComponent, ComponentGetState>(OnGetState);
@@ -99,7 +96,7 @@ namespace Content.Server.Light.EntitySystems
private void OnGetState(EntityUid uid, HandheldLightComponent component, ref ComponentGetState args) 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) private byte? GetLevel(HandheldLightComponent component)
@@ -113,15 +110,7 @@ namespace Content.Server.Light.EntitySystems
if (MathHelper.CloseToPercent(battery.CurrentCharge, 0) || component.Wattage > battery.CurrentCharge) if (MathHelper.CloseToPercent(battery.CurrentCharge, 0) || component.Wattage > battery.CurrentCharge)
return 0; return 0;
return (byte?) ContentHelpers.RoundToNearestLevels(battery.CurrentCharge / battery.MaxCharge * 255, 255, SharedHandheldLightComponent.StatusLevels); return (byte?) ContentHelpers.RoundToNearestLevels(battery.CurrentCharge / battery.MaxCharge * 255, 255, HandheldLightComponent.StatusLevels);
}
private void OnInit(EntityUid uid, HandheldLightComponent component, ComponentInit args)
{
EntityManager.EnsureComponent<PointLightComponent>(uid);
// Want to make sure client has latest data on level so battery displays properly.
component.Dirty(EntityManager);
} }
private void OnRemove(EntityUid uid, HandheldLightComponent component, ComponentRemove args) private void OnRemove(EntityUid uid, HandheldLightComponent component, ComponentRemove args)
@@ -201,19 +190,9 @@ namespace Content.Server.Light.EntitySystems
{ {
if (!component.Activated) return false; if (!component.Activated) return false;
component.Activated = false; SetActivated(component.Owner, false, component, makeNoise);
if (component.ToggleAction != null) component.Level = null;
_actionSystem.SetToggled(component.ToggleAction, false);
_activeLights.Remove(component); _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; return true;
} }
@@ -239,17 +218,9 @@ namespace Content.Server.Light.EntitySystems
return false; return false;
} }
component.Activated = true; SetActivated(component.Owner, true, component, true);
if (component.ToggleAction != null)
_actionSystem.SetToggled(component.ToggleAction, true);
_activeLights.Add(component); _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; return true;
} }
@@ -288,10 +259,10 @@ namespace Content.Server.Light.EntitySystems
{ {
var level = GetLevel(comp); var level = GetLevel(comp);
if (level == comp.LastLevel) if (level == comp.Level)
return; return;
comp.LastLevel = level; comp.Level = level;
Dirty(comp); Dirty(comp);
} }
} }

View File

@@ -10,6 +10,7 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.Clothing.EntitySystems; using Content.Shared.Clothing.EntitySystems;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Item;
using Content.Shared.Smoking; using Content.Shared.Smoking;
using Content.Shared.Temperature; using Content.Shared.Temperature;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -26,6 +27,7 @@ namespace Content.Server.Nutrition.EntitySystems
[Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly ClothingSystem _clothing = default!; [Dependency] private readonly ClothingSystem _clothing = default!;
[Dependency] private readonly SharedItemSystem _items = default!;
private const float UpdateTimer = 3f; private const float UpdateTimer = 3f;
@@ -61,6 +63,7 @@ namespace Content.Server.Nutrition.EntitySystems
}; };
_clothing.SetEquippedPrefix(uid, newState, clothing); _clothing.SetEquippedPrefix(uid, newState, clothing);
_items.SetHeldPrefix(uid, newState);
if (state == SmokableState.Lit) if (state == SmokableState.Lit)
_active.Add(uid); _active.Add(uid);

View File

@@ -1,4 +1,4 @@
using Content.Shared.Clothing.EntitySystems; using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -10,9 +10,11 @@ namespace Content.Shared.Clothing.Components;
/// This handles entities which can be equipped. /// This handles entities which can be equipped.
/// </summary> /// </summary>
[NetworkedComponent] [NetworkedComponent]
[Access(typeof(ClothingSystem), typeof(InventorySystem))]
public abstract class SharedClothingComponent : Component public abstract class SharedClothingComponent : Component
{ {
[DataField("clothingVisuals")] [DataField("clothingVisuals")]
[Access(typeof(ClothingSystem), typeof(InventorySystem), Other = AccessPermissions.ReadExecute)] // TODO remove execute permissions.
public Dictionary<string, List<SharedSpriteComponent.PrototypeLayerData>> ClothingVisuals = new(); public Dictionary<string, List<SharedSpriteComponent.PrototypeLayerData>> ClothingVisuals = new();
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
@@ -21,6 +23,7 @@ public abstract class SharedClothingComponent : Component
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("slots", required: true)] [DataField("slots", required: true)]
[Access(typeof(ClothingSystem), typeof(InventorySystem), Other = AccessPermissions.ReadExecute)]
public SlotFlags Slots = SlotFlags.NONE; public SlotFlags Slots = SlotFlags.NONE;
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]

View File

@@ -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; using Robust.Shared.GameStates;
namespace Content.Shared.Clothing.EntitySystems; namespace Content.Shared.Clothing.EntitySystems;
public sealed class ClothingSystem : EntitySystem public sealed class ClothingSystem : EntitySystem
{ {
[Dependency] private readonly SharedItemSystem _itemSys = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -21,17 +25,30 @@ public sealed class ClothingSystem : EntitySystem
private void OnHandleState(EntityUid uid, SharedClothingComponent component, ref ComponentHandleState args) private void OnHandleState(EntityUid uid, SharedClothingComponent component, ref ComponentHandleState args)
{ {
if (args.Current is ClothingComponentState state) if (args.Current is ClothingComponentState state)
component.EquippedPrefix = state.EquippedPrefix; SetEquippedPrefix(uid, state.EquippedPrefix, component);
} }
#region Public API #region Public API
public void SetEquippedPrefix(EntityUid uid, string? prefix, SharedClothingComponent? clothing = null) 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; return;
clothing.EquippedPrefix = prefix; 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); Dirty(clothing);
} }

View File

@@ -1,4 +1,5 @@
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Item; namespace Content.Shared.Item;
@@ -8,11 +9,13 @@ namespace Content.Shared.Item;
/// like backpacks. /// like backpacks.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
[NetworkedComponent]
[Access(typeof(SharedItemSystem))]
public sealed class ItemComponent : Component public sealed class ItemComponent : Component
{ {
[Access(typeof(SharedItemSystem), Other = AccessPermissions.ReadExecute)]
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("size")] [DataField("size")]
[Access(typeof(SharedItemSystem), Other = AccessPermissions.ReadExecute)]
public int Size = 5; public int Size = 5;
[DataField("inhandVisuals")] [DataField("inhandVisuals")]

View File

@@ -1,4 +1,4 @@
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
using Content.Shared.Verbs; using Content.Shared.Verbs;
@@ -38,7 +38,10 @@ public abstract class SharedItemSystem : EntitySystem
public void SetHeldPrefix(EntityUid uid, string? heldPrefix, ItemComponent? component = null) 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; return;
component.HeldPrefix = heldPrefix; component.HeldPrefix = heldPrefix;
@@ -62,7 +65,7 @@ public abstract class SharedItemSystem : EntitySystem
return; return;
component.Size = state.Size; component.Size = state.Size;
component.HeldPrefix = state.HeldPrefix; SetHeldPrefix(uid, state.HeldPrefix, component);
} }
private void OnGetState(EntityUid uid, ItemComponent component, ref ComponentGetState args) private void OnGetState(EntityUid uid, ItemComponent component, ref ComponentGetState args)

View File

@@ -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");
/// <summary>
/// Whether to automatically set item-prefixes when toggling the flashlight.
/// </summary>
/// <remarks>
/// Flashlights should probably be using explicit unshaded sprite, in-hand and clothing layers, this is
/// mostly here for backwards compatibility.
/// </remarks>
[DataField("addPrefix")]
public bool AddPrefix = false;
[DataField("toggleActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
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,
}
}

View File

@@ -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<InstantActionPrototype>))]
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,
}
}

View File

@@ -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<HandheldLightComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<HandheldLightComponent, ComponentHandleState>(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);
}
}

View File

@@ -292,6 +292,7 @@
map: [ "light" ] map: [ "light" ]
- type: HandheldLight - type: HandheldLight
addPrefix: false addPrefix: false
- type: ToggleableLightVisuals
clothingVisuals: clothingVisuals:
head: head:
- state: equipped-head-light - state: equipped-head-light

View File

@@ -63,6 +63,7 @@
startingItem: PowerCellHigh startingItem: PowerCellHigh
- type: HandheldLight - type: HandheldLight
addPrefix: false addPrefix: false
- type: ToggleableLightVisuals
inhandVisuals: inhandVisuals:
left: left:
- state: inhand-left-light - state: inhand-left-light