From f688d89606f40035892579ed7d4f7d86c2470f56 Mon Sep 17 00:00:00 2001 From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Sun, 3 May 2026 23:54:44 +0200 Subject: [PATCH 01/10] OneCommitOps --- .../LightReplacerMenuBoundUserInterface.cs | 129 ++++++++ .../Controls/LightReplacerStatusControl.cs | 66 ++++ .../LightReplacerStatusControlSystem.cs | 16 + .../EntitySystems/LightReplacerSystem.cs | 2 +- .../Components/DVLightReplacerComponent.cs | 49 +++ .../EntitySystems/DVLightReplacerSystem.cs | 309 ++++++++++++++++++ .../Light/Events/LightReplacerBUIMessages.cs | 25 ++ .../components/light-replacer-component.ftl | 26 ++ .../Entities/Objects/Tools/light_replacer.yml | 14 +- .../Entities/Objects/Tools/light_replacer.yml | 16 + .../light_replacer.rsi/eject-bulbs.png | Bin 0 -> 329 bytes .../light_replacer.rsi/eject-tubes.png | Bin 0 -> 308 bytes .../Janitorial/light_replacer.rsi/meta.json | 17 + 13 files changed, 664 insertions(+), 5 deletions(-) create mode 100644 Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs create mode 100644 Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs create mode 100644 Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs create mode 100644 Content.Shared/_DV/Light/Components/DVLightReplacerComponent.cs create mode 100644 Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs create mode 100644 Content.Shared/_DV/Light/Events/LightReplacerBUIMessages.cs create mode 100644 Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl create mode 100644 Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml create mode 100644 Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/eject-bulbs.png create mode 100644 Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/eject-tubes.png create mode 100644 Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/meta.json diff --git a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs new file mode 100644 index 0000000000..c90487b625 --- /dev/null +++ b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs @@ -0,0 +1,129 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared._DV.Light.Events; +using Content.Shared.Light.Components; +using JetBrains.Annotations; +using Robust.Client.UserInterface; +using Robust.Shared.Prototypes; + +namespace Content.Client._DV.Light.BoundUserInterfaces; + +[UsedImplicitly] +public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) +{ + private EntityQuery _lightBulbQuery; + private EntityQuery _metaDataQuery; + + private SimpleRadialMenu? _menu; + + private readonly EntProtoId _ejectTubes = "EjectTubes"; + private readonly EntProtoId _ejectBulbs = "EjectBulbs"; + + protected override void Open() + { + base.Open(); + + _lightBulbQuery = EntMan.GetEntityQuery(); + _metaDataQuery = EntMan.GetEntityQuery(); + + if (!EntMan.TryGetComponent(Owner, out var replacer)) + return; + + var lightTypes = CreateButtons(replacer); + + if (lightTypes == null) + return; + + _menu = this.CreateWindow(); + _menu.SetButtons(lightTypes); + + _menu.OpenCentered(); + } + + private IEnumerable? CreateButtons(Shared._DV.Light.Components.DVLightReplacerComponent replacer) + { + var options = new List(); + + Dictionary tubes = []; + Dictionary bulbs = []; + + var hasActiveTubes = false; + var hasActiveBulbs = false; + + foreach (var light in replacer.InsertedBulbs.ContainedEntities) + { + if (!_lightBulbQuery.TryComp(light, out var bulb) + || !_metaDataQuery.TryComp(light, out var metaData)) + continue; + + if (bulb.Type == LightBulbType.Tube) + { + if (metaData.EntityName != replacer.ActiveLightTube) + tubes.TryAdd(metaData.EntityName, light); + else + hasActiveTubes = true; + } + else + { + if (metaData.EntityName != replacer.ActiveLightBulb) + bulbs.TryAdd(metaData.EntityName, light); + else + hasActiveBulbs = true; + } + } + + if (hasActiveTubes) + { + var toggleLightTubes = new RadialMenuActionOption(EjectLights, replacer.ActiveLightTube) + { + IconSpecifier = RadialMenuIconSpecifier.With(_ejectTubes), + ToolTip = Loc.GetString("comp-light-replacer-eject-specified-lights", ("light", replacer.ActiveLightTube)), + }; + options.Add(toggleLightTubes); + } + + if (hasActiveBulbs) + { + var toggleLightBulbs = new RadialMenuActionOption(EjectLights, replacer.ActiveLightBulb) + { + IconSpecifier = RadialMenuIconSpecifier.With(_ejectBulbs), + ToolTip = Loc.GetString("comp-light-replacer-eject-specified-lights", ("light", replacer.ActiveLightBulb)), + }; + options.Add(toggleLightBulbs); + } + + // This iterates through every unique light to add them as options. + foreach (var light in tubes) + { + PopulateOptions(light.Key, light.Value, LightBulbType.Tube, ref options); + } + + foreach (var light in bulbs) + { + PopulateOptions(light.Key, light.Value, LightBulbType.Bulb, ref options); + } + + return options; + } + + private void PopulateOptions(string name, EntityUid uid, LightBulbType lightType, ref List options) + { + var switchLight = new RadialMenuActionOption<(string, LightBulbType)>(SwitchActiveLight, (name, lightType)) + { + IconSpecifier = RadialMenuIconSpecifier.With(uid), + ToolTip = Loc.GetString("comp-light-replacer-select-lights", ("light", uid)), + }; + options.Add(switchLight); + } + + private void SwitchActiveLight((string, LightBulbType) light) + { + var message = new SwitchLightTypeMessage(light); + SendPredictedMessage(message); + } + + private void EjectLights(string lightName) + { + var message = new EjectLightTypeMessage(lightName); + SendPredictedMessage(message); + } +} diff --git a/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs b/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs new file mode 100644 index 0000000000..34eb0cd023 --- /dev/null +++ b/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs @@ -0,0 +1,66 @@ +using Content.Client.Message; +using Content.Client.Stylesheets; +using Content.Shared.Light.Components; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Timing; + +namespace Content.Client._DV.Light.Controls; + +/// +/// Handles the label on the light replacer +/// +public sealed class LightReplacerStatusControl : Control +{ + + private readonly Entity _parent; + private readonly RichTextLabel _label; + + private string? _prevActiveLightTube; + private string? _prevActiveLightBulb; + private string? _labelTube; + private string? _labelBulb; + + public LightReplacerStatusControl(Entity parent) + { + _parent = parent; + _label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } }; + AddChild(_label); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + // only updates the UI if any of the details are different than they previously were + if (_prevActiveLightTube == _parent.Comp.ActiveLightTube + && _prevActiveLightBulb == _parent.Comp.ActiveLightBulb) + return; + + _prevActiveLightTube = _parent.Comp.ActiveLightTube; + _prevActiveLightBulb = _parent.Comp.ActiveLightBulb; + _labelTube = _prevActiveLightTube; + _labelBulb = _prevActiveLightBulb; + + // Remove " light tube" at the end to save precious label space. + if (_labelTube.EndsWith(" light tube")) + { + _labelTube = _labelTube.Remove(_labelTube.Length - 11); + // Remove " crystal" in case of colored lights + if (_labelTube.EndsWith(" crystal")) + _labelTube = _labelTube.Remove(_labelTube.Length - 8); + } + // Same with bulbs. + if (_labelBulb.EndsWith(" light bulb")) + { + _labelBulb = _labelBulb.Remove(_labelBulb.Length - 11); + if (_labelBulb.EndsWith(" crystal")) + _labelBulb = _labelBulb.Remove(_labelBulb.Length - 8); + } + + // Update current active lights + _label.SetMarkup(Loc.GetString("comp-light-replacer-label", + ("tube", _labelTube), + ("bulb", _labelBulb))); + } +} diff --git a/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs b/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs new file mode 100644 index 0000000000..28a7b14de6 --- /dev/null +++ b/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs @@ -0,0 +1,16 @@ +using Content.Client._DV.Light.Controls; +using Content.Client.Items; +using Content.Shared.Light.Components; + +namespace Content.Client._DV.Light.EntitySystems; + +/// +/// Handles the label on the light replacer +/// +public sealed class LightReplacerStatusControlSystem : EntitySystem +{ + public override void Initialize() + { + Subs.ItemStatus(replacer => new LightReplacerStatusControl(replacer)); + } +} diff --git a/Content.Server/Light/EntitySystems/LightReplacerSystem.cs b/Content.Server/Light/EntitySystems/LightReplacerSystem.cs index 71cc0173da..86ba229d47 100644 --- a/Content.Server/Light/EntitySystems/LightReplacerSystem.cs +++ b/Content.Server/Light/EntitySystems/LightReplacerSystem.cs @@ -14,7 +14,7 @@ using Robust.Shared.Containers; namespace Content.Server.Light.EntitySystems; [UsedImplicitly] -public sealed class LightReplacerSystem : SharedLightReplacerSystem +public sealed class LightReplacerSystem : SharedLightReplacerSystem // THIS HAS BEEN REPLACED BY DVLightReplacerSystem! { [Dependency] private readonly PoweredLightSystem _poweredLight = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; diff --git a/Content.Shared/_DV/Light/Components/DVLightReplacerComponent.cs b/Content.Shared/_DV/Light/Components/DVLightReplacerComponent.cs new file mode 100644 index 0000000000..11661775c4 --- /dev/null +++ b/Content.Shared/_DV/Light/Components/DVLightReplacerComponent.cs @@ -0,0 +1,49 @@ +using Content.Shared._DV.Light.EntitySystems; +using Content.Shared.Light.Components; +using Content.Shared.Storage; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; + +namespace Content.Shared._DV.Light.Components; + +/// +/// Device that allows user to quickly change bulbs in +/// Can be reloaded by new light tubes or light bulbs +/// +[RegisterComponent, NetworkedComponent, Access(typeof(DVLightReplacerSystem)), AutoGenerateComponentState] +public sealed partial class DVLightReplacerComponent : Component +{ + [DataField] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Weapons/click.ogg") + { + Params = new AudioParams + { + Volume = -4f, + } + }; + + /// + /// Bulbs that were inserted inside light replacer + /// + [ViewVariables] + public Container InsertedBulbs = default!; + + /// + /// This string defines what kind of tube will be inserted into light fixtures. + /// + [DataField, AutoNetworkedField] + public string ActiveLightTube = "fluorescent light tube"; + + /// + /// This string defines what kind of bulb will be inserted into light fixtures. + /// + [DataField, AutoNetworkedField] + public string ActiveLightBulb = "incandescent light bulb"; + + /// + /// The default starting bulbs + /// + [DataField] + public List StartingContent = []; +} diff --git a/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs b/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs new file mode 100644 index 0000000000..dcf8227c03 --- /dev/null +++ b/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs @@ -0,0 +1,309 @@ +using System.Linq; +using Content.Shared._DV.Light.Events; +using Content.Shared.Examine; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; +using Content.Shared.Popups; +using Content.Shared.Storage; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Serialization; + +namespace Content.Shared._DV.Light.EntitySystems; + +public sealed class DVLightReplacerSystem : EntitySystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPoweredLightSystem _poweredLight = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + + private EntityQuery _lightBulbQuery; + private EntityQuery _metaDataQuery; + + public override void Initialize() + { + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(HandleInteract); + SubscribeLocalEvent(HandleAfterInteract); + SubscribeLocalEvent(OnEjectMessage); + SubscribeLocalEvent(OnSwitchMessage); + + _lightBulbQuery = GetEntityQuery(); + _metaDataQuery = GetEntityQuery(); + } + + private void OnInit(Entity replacer, ref ComponentInit args) + { + // This needs to be handled on CompInit because otherwise, it's empty on the client. + replacer.Comp.InsertedBulbs = _container.EnsureContainer(replacer, "light_replacer_storage"); + } + + private void OnMapInit(Entity replacer, ref MapInitEvent args) + { + var xform = Transform(replacer); + foreach (var spawn in EntitySpawnCollection.GetSpawns(replacer.Comp.StartingContent)) + { + var light = Spawn(spawn, xform.Coordinates); + TryInsertBulb(replacer.AsNullable(), light); + } + } + + private void OnExamined(Entity replacer, ref ExaminedEvent args) + { + using (args.PushGroup(nameof(Components.DVLightReplacerComponent))) + { + if (!replacer.Comp.InsertedBulbs.ContainedEntities.Any()) + { + args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights")); + return; + } + + args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights")); + var groups = new Dictionary(); + foreach (var bulb in replacer.Comp.InsertedBulbs.ContainedEntities) + { + var metaData = _metaDataQuery.GetComponent(bulb); + groups[metaData.EntityName] = groups.GetValueOrDefault(metaData.EntityName) + 1; + } + + foreach (var (name, amount) in groups) + { + args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name))); + } + } + } + + private void OnUse(Entity replacer, ref UseInHandEvent args) + { + if (args.Handled) + return; + + args.ApplyDelay = false; + + if (!replacer.Comp.InsertedBulbs.ContainedEntities.Any()) + { + _popup.PopupClient(Loc.GetString("comp-light-replacer-open-empty", ("light-replacer", replacer)), replacer, args.User); + return; + } + + args.Handled = true; + _ui.OpenUi(replacer.Owner, LightReplacerUiKey.Key, args.User); + } + + private void HandleAfterInteract(Entity replacer, ref AfterInteractEvent eventArgs) + { + if (eventArgs.Handled + || !eventArgs.CanReach // standard interaction checks + || eventArgs.Target == null) // behavior will depend on the target type + return; + + var targetUid = (EntityUid) eventArgs.Target; + + // replace broken light in fixture? + if (TryComp(targetUid, out var fixture)) + eventArgs.Handled = TryReplaceBulb(replacer.AsNullable(), (targetUid, fixture), eventArgs.User); + // add new bulb to light replacer container? + else if (_lightBulbQuery.TryComp(targetUid, out var bulb)) + eventArgs.Handled = TryInsertBulb(replacer.AsNullable(), (targetUid, bulb), eventArgs.User, true); + } + + private void HandleInteract(Entity replacer, ref InteractUsingEvent eventArgs) + { + if (eventArgs.Handled) + return; + + var usedUid = eventArgs.Used; + + // want to insert a new light bulb? + if (_lightBulbQuery.TryComp(usedUid, out var bulb)) + eventArgs.Handled = TryInsertBulb(replacer.AsNullable(), (usedUid, bulb), eventArgs.User, true); + // add bulbs from storage? + else if (TryComp(usedUid, out var storage)) + eventArgs.Handled = TryInsertBulbsFromStorage(replacer.AsNullable(), (usedUid, storage), eventArgs.User); + } + + private void OnEjectMessage(Entity replacer, ref EjectLightTypeMessage args) + { + HashSet lightsToEject = []; + foreach (var light in replacer.Comp.InsertedBulbs.ContainedEntities) + { + if (_metaDataQuery.TryComp(light, out var metaData) && metaData.EntityName == args.LightName) + lightsToEject.Add(light); + } + + foreach (var light in lightsToEject) + { + _container.Remove(light, replacer.Comp.InsertedBulbs); + } + } + + private void OnSwitchMessage(Entity replacer, ref SwitchLightTypeMessage args) + { + if (args.LightType == LightBulbType.Tube) + replacer.Comp.ActiveLightTube = args.LightName; + else + replacer.Comp.ActiveLightBulb = args.LightName; + Dirty(replacer); + } + + /// + /// Try to replace a light bulb in + /// using light replacer. Light fixture should have . + /// + /// The light replacer used to replace the bulb. + /// The fixture whose light is being replaced. + /// The user who is replacing the light. + /// True if successfully replaced light, false otherwise + public bool TryReplaceBulb(Entity replacer, Entity fixture, EntityUid? userUid = null) + { + if (!Resolve(replacer, ref replacer.Comp) + || !Resolve(fixture, ref fixture.Comp)) + return false; + + var activeType = fixture.Comp.BulbType == LightBulbType.Tube + ? replacer.Comp.ActiveLightTube + : replacer.Comp.ActiveLightBulb; + + // check if light bulb is broken or missing + var fixtureBulbUid = _poweredLight.GetBulb(fixture, fixture.Comp); + if (fixtureBulbUid != null) + { + if (!_lightBulbQuery.TryComp(fixtureBulbUid.Value, out var fixtureBulb)) + return false; + + if (fixtureBulb.State == LightBulbState.Normal + && _metaDataQuery.TryComp(fixtureBulbUid, out var metaData) + && metaData.EntityName == activeType) + { + _popup.PopupClient(Loc.GetString("comp-light-replacer-same-light", ("light", fixtureBulbUid)), fixture, userUid, PopupType.Medium); + return false; + } + } + + EntityUid? bulb = null; + foreach (var insertedBulb in replacer.Comp.InsertedBulbs.ContainedEntities) + { + if (!_metaDataQuery.TryComp(insertedBulb, out var metaData) || metaData.EntityName != activeType) + continue; + + bulb = insertedBulb; + break; + } + + // found bulb in inserted storage + if (bulb.HasValue) + { + // try to remove it + var hasRemoved = _container.Remove(bulb.Value, replacer.Comp.InsertedBulbs); + if (!hasRemoved) + return false; + } + else + { + if (userUid == null) + return false; + + var msg = Loc.GetString("comp-light-replacer-missing-light-dv", + ("light-name", activeType), + ("light-replacer", replacer)); + _popup.PopupClient(msg, replacer, userUid.Value); + return false; + } + + // insert it into fixture + var wasReplaced = _poweredLight.ReplaceBulb(fixture, bulb.Value, fixture.Comp); + if (wasReplaced) + { + _audio.PlayPredicted(replacer.Comp.Sound, replacer, userUid); + } + + return wasReplaced; + } + + /// + /// Try to insert a new bulb inside light replacer + /// + /// The light replacer to insert a light into. + /// The light to insert into the replacer. + /// The user who is inserting the light. + /// Whether to show a popup. + /// True if successfully inserted light, false otherwise + public bool TryInsertBulb(Entity replacer, Entity bulb, EntityUid? userUid = null, bool showPopup = false) + { + if (!Resolve(replacer, ref replacer.Comp) + || !Resolve(bulb, ref bulb.Comp)) + return false; + + // only normal (non-broken) bulbs can be inserted inside light replacer + if (bulb.Comp.State != LightBulbState.Normal) + { + if (!showPopup || userUid == null) + return false; + + var error = Loc.GetString("comp-light-replacer-insert-broken-light"); + _popup.PopupClient(error, replacer, userUid.Value); + return false; + } + // try insert light and show message + var hasInsert = _container.Insert(bulb.Owner, replacer.Comp.InsertedBulbs); + + if (!hasInsert || !showPopup || userUid == null) + return hasInsert; + + var message = Loc.GetString("comp-light-replacer-insert-light", + ("light-replacer", replacer), ("bulb", bulb)); + _popup.PopupClient(message, replacer, userUid.Value, PopupType.Medium); + + return hasInsert; + } + + /// + /// Try to insert all light bulbs from storage (for example light tubes box) + /// + /// The light replacer to insert bulbs into. + /// The storage whose contents should be inserted. + /// The user who inserts the contents. + /// + /// Returns true if storage contained at least one light bulb + /// which was successfully inserted inside light replacer + /// + public bool TryInsertBulbsFromStorage(Entity replacer, Entity storage, EntityUid? userUid = null) + { + if (!Resolve(replacer, ref replacer.Comp) + || !Resolve(storage, ref storage.Comp)) + return false; + + var insertedBulbs = 0; + var storedEntities = storage.Comp.Container.ContainedEntities.ToArray(); + + foreach (var ent in storedEntities) + { + if (TryInsertBulb(replacer, ent, userUid)) + { + insertedBulbs++; + } + } + + // show some message if success + if (insertedBulbs > 0 && userUid != null) + { + var msg = Loc.GetString("comp-light-replacer-refill-from-storage", ("light-replacer", replacer)); + _popup.PopupClient(msg, replacer, userUid.Value, PopupType.Medium); + } + + return insertedBulbs > 0; + } +} + +[Serializable, NetSerializable] +public enum LightReplacerUiKey : byte +{ + Key, +} diff --git a/Content.Shared/_DV/Light/Events/LightReplacerBUIMessages.cs b/Content.Shared/_DV/Light/Events/LightReplacerBUIMessages.cs new file mode 100644 index 0000000000..1aeda0a51b --- /dev/null +++ b/Content.Shared/_DV/Light/Events/LightReplacerBUIMessages.cs @@ -0,0 +1,25 @@ +using Content.Shared.Light.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared._DV.Light.Events; + +/// +/// This message is sent from the client when the player wants to switch the active light bulb. +/// +/// A mix of the light name and the light bulb type. +[Serializable, NetSerializable] +public sealed class SwitchLightTypeMessage((string, LightBulbType) light) : BoundUserInterfaceMessage +{ + public string LightName = light.Item1; + public LightBulbType LightType = light.Item2; +} + +/// +/// This message is sent from the client when the player wants to eject all lights of a specific type. +/// +/// The name of the lights to be ejected. +[Serializable, NetSerializable] +public sealed class EjectLightTypeMessage(string lightName) : BoundUserInterfaceMessage +{ + public string LightName = lightName; +} diff --git a/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl b/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl new file mode 100644 index 0000000000..c08e8bf0fc --- /dev/null +++ b/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl @@ -0,0 +1,26 @@ + +### Interaction Messages + +# Shown when player tries to replace light, but there is no lights left +comp-light-replacer-missing-light-dv = No {$light-name}s left in {THE($light-replacer)}. + +# Shown when a player attempts to replace a light with the same color & type as the active light. +comp-light-replacer-same-light = This fixture already holds a {$light}! + +# Radial Menu messages +comp-light-replacer-eject-specified-lights = Eject all {$light}s. +comp-light-replacer-select-lights = Select {$light}s. +comp-light-replacer-open-empty = {CAPITALIZE(THE($light-replacer))} is completely empty! + +# Label +comp-light-replacer-label = Tube: {$tube} + Bulb: {$bulb} + +### Examine + +comp-light-replacer-no-lights = It's empty. +comp-light-replacer-has-lights = It contains the following: +comp-light-replacer-light-listing = {$amount -> + [one] [color=yellow]{$amount}[/color] [color=gray]{$name}[/color] + *[other] [color=yellow]{$amount}[/color] [color=gray]{$name}s[/color] +} diff --git a/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml b/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml index 34dcd66b71..b5709aaa51 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml @@ -9,8 +9,8 @@ state: icon - type: Item sprite: Objects/Specific/Janitorial/light_replacer.rsi - - type: LightReplacer - contents: + - type: DVLightReplacer # DeltaV - Replaced the LightReplacer System. + startingContent: # DeltaV - Renamed the field more descriptive. - id: LightTube amount: 8 - id: LightBulb @@ -20,13 +20,19 @@ - type: ContainerContainer containers: light_replacer_storage: !type:Container + # Begin DeltaV Additions - Added Radial Menu to Light Replacers + - type: UserInterface + interfaces: + enum.LightReplacerUiKey.Key: + type: LightReplacerMenuBoundUserInterface + # Begin DeltaV Additions - Added Radial Menu to Light Replacers - type: entity parent: LightReplacer id: LightReplacerEmpty suffix: Empty components: - - type: LightReplacer - contents: + - type: DVLightReplacer # DeltaV - Replaced the LightReplacer System. + startingContent: # DeltaV - Renamed the field more descriptive. - id: LightTube amount: 0 diff --git a/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml b/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml new file mode 100644 index 0000000000..c3e84a3bee --- /dev/null +++ b/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml @@ -0,0 +1,16 @@ +# The following entities are solely used for sprites in the radial menu. +- type: entity + id: EjectTubes + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: Objects/Specific/Janitorial/light_replacer.rsi + state: eject-tubes + +- type: entity + id: EjectBulbs + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: Objects/Specific/Janitorial/light_replacer.rsi + state: eject-bulbs diff --git a/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/eject-bulbs.png b/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/eject-bulbs.png new file mode 100644 index 0000000000000000000000000000000000000000..e67817348e238f3b0fc7fd2bfd1994cc1912a3be GIT binary patch literal 329 zcmV-P0k-~$P)Px$14%?dR9J=Wl`#s!KoCX$igie*MFcJEJb;Dl2`s#cSFp78CKet*at1p)5lk6u zv>O{YtRcb7Of-V|)v_=%Z~o4(K&w{&E+Y-x?j{5P{qPC^n9fe3T%w?SaVH4`J0M>? z8ZQ9==i^2M(cuF4a>euKuK*-zkaZ2ufsX?$=7&;?B{QIU0w-zrVgTntk_OqGbPd%G zXu7Wjz;{8SE;k^KW3two`~H&M-9mXE>_}_v%Ukyp#asP^D(@~45i7AVm2x*eLF_er z*BRHT#vM=r&{Or{GyuSMfQVK^G%UD}h-j^WsS4mU^3DUP8=^nFtWee6R|6D5NQ$df b#}GaM7<_}(176we00000NkvXXu0mjfX;FiW literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/eject-tubes.png b/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/eject-tubes.png new file mode 100644 index 0000000000000000000000000000000000000000..5665b1d1a7524f4ba818332f8dfd3470d8c25471 GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}?>t=`Ln2z= zPTR=aY#`vee{zq*Ob^e)Y$amZ1`ORx&Iqk3WM(k!-m!Gyuay%tWHvNT4*Fs4wyorS zB9F`hnQYzb*Ysx<2tJ(FGOMAuk0Ic?={1!QmN~^|Hu1OyCdjylgtqQ`-^+HITm58Z z&h4_VJSr7U>R|L=KgLBv>D7nT`In+juX{b=xn*180TW-1(?L(`-{pEdd$urBiXosm zKkcTQq0z#g2iD!1Tnxu%EV#9O#v;YmWY^H!MWu~5p4V2L6X>|dI4OUU|M@?2pFHpI zS#e;_^+&%pmYbK=Ce$@L#qT}LWY=l?;jranfjUMpo5N>k^8K6v^f`m4tDnm{r-UW| DrI><> literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/meta.json b/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/meta.json new file mode 100644 index 0000000000..bd8de8f939 --- /dev/null +++ b/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Eject sprites made by SirWarock on Github using sprites from cev-eris at https://github.com/discordia-space/CEV-Eris/commit/740ff31a81313086cf16761f3677cf1e2ab46c93", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "eject-tubes" + }, + { + "name": "eject-bulbs" + } + ] +} From 502f96a02cac07a061d8bd836aa1ecf7680c4ab1 Mon Sep 17 00:00:00 2001 From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Sun, 3 May 2026 23:56:33 +0200 Subject: [PATCH 02/10] code qualifiers --- .../EntitySystems/DVLightReplacerSystem.cs | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs b/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs index dcf8227c03..2b214b6c02 100644 --- a/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs +++ b/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared._DV.Light.Components; using Content.Shared._DV.Light.Events; using Content.Shared.Examine; using Content.Shared.Interaction; @@ -26,26 +27,26 @@ public sealed class DVLightReplacerSystem : EntitySystem public override void Initialize() { - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnUse); - SubscribeLocalEvent(HandleInteract); - SubscribeLocalEvent(HandleAfterInteract); - SubscribeLocalEvent(OnEjectMessage); - SubscribeLocalEvent(OnSwitchMessage); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(HandleInteract); + SubscribeLocalEvent(HandleAfterInteract); + SubscribeLocalEvent(OnEjectMessage); + SubscribeLocalEvent(OnSwitchMessage); _lightBulbQuery = GetEntityQuery(); _metaDataQuery = GetEntityQuery(); } - private void OnInit(Entity replacer, ref ComponentInit args) + private void OnInit(Entity replacer, ref ComponentInit args) { // This needs to be handled on CompInit because otherwise, it's empty on the client. replacer.Comp.InsertedBulbs = _container.EnsureContainer(replacer, "light_replacer_storage"); } - private void OnMapInit(Entity replacer, ref MapInitEvent args) + private void OnMapInit(Entity replacer, ref MapInitEvent args) { var xform = Transform(replacer); foreach (var spawn in EntitySpawnCollection.GetSpawns(replacer.Comp.StartingContent)) @@ -55,9 +56,9 @@ public sealed class DVLightReplacerSystem : EntitySystem } } - private void OnExamined(Entity replacer, ref ExaminedEvent args) + private void OnExamined(Entity replacer, ref ExaminedEvent args) { - using (args.PushGroup(nameof(Components.DVLightReplacerComponent))) + using (args.PushGroup(nameof(DVLightReplacerComponent))) { if (!replacer.Comp.InsertedBulbs.ContainedEntities.Any()) { @@ -80,7 +81,7 @@ public sealed class DVLightReplacerSystem : EntitySystem } } - private void OnUse(Entity replacer, ref UseInHandEvent args) + private void OnUse(Entity replacer, ref UseInHandEvent args) { if (args.Handled) return; @@ -97,7 +98,7 @@ public sealed class DVLightReplacerSystem : EntitySystem _ui.OpenUi(replacer.Owner, LightReplacerUiKey.Key, args.User); } - private void HandleAfterInteract(Entity replacer, ref AfterInteractEvent eventArgs) + private void HandleAfterInteract(Entity replacer, ref AfterInteractEvent eventArgs) { if (eventArgs.Handled || !eventArgs.CanReach // standard interaction checks @@ -114,7 +115,7 @@ public sealed class DVLightReplacerSystem : EntitySystem eventArgs.Handled = TryInsertBulb(replacer.AsNullable(), (targetUid, bulb), eventArgs.User, true); } - private void HandleInteract(Entity replacer, ref InteractUsingEvent eventArgs) + private void HandleInteract(Entity replacer, ref InteractUsingEvent eventArgs) { if (eventArgs.Handled) return; @@ -129,7 +130,7 @@ public sealed class DVLightReplacerSystem : EntitySystem eventArgs.Handled = TryInsertBulbsFromStorage(replacer.AsNullable(), (usedUid, storage), eventArgs.User); } - private void OnEjectMessage(Entity replacer, ref EjectLightTypeMessage args) + private void OnEjectMessage(Entity replacer, ref EjectLightTypeMessage args) { HashSet lightsToEject = []; foreach (var light in replacer.Comp.InsertedBulbs.ContainedEntities) @@ -144,7 +145,7 @@ public sealed class DVLightReplacerSystem : EntitySystem } } - private void OnSwitchMessage(Entity replacer, ref SwitchLightTypeMessage args) + private void OnSwitchMessage(Entity replacer, ref SwitchLightTypeMessage args) { if (args.LightType == LightBulbType.Tube) replacer.Comp.ActiveLightTube = args.LightName; @@ -161,7 +162,7 @@ public sealed class DVLightReplacerSystem : EntitySystem /// The fixture whose light is being replaced. /// The user who is replacing the light. /// True if successfully replaced light, false otherwise - public bool TryReplaceBulb(Entity replacer, Entity fixture, EntityUid? userUid = null) + public bool TryReplaceBulb(Entity replacer, Entity fixture, EntityUid? userUid = null) { if (!Resolve(replacer, ref replacer.Comp) || !Resolve(fixture, ref fixture.Comp)) @@ -235,7 +236,7 @@ public sealed class DVLightReplacerSystem : EntitySystem /// The user who is inserting the light. /// Whether to show a popup. /// True if successfully inserted light, false otherwise - public bool TryInsertBulb(Entity replacer, Entity bulb, EntityUid? userUid = null, bool showPopup = false) + public bool TryInsertBulb(Entity replacer, Entity bulb, EntityUid? userUid = null, bool showPopup = false) { if (!Resolve(replacer, ref replacer.Comp) || !Resolve(bulb, ref bulb.Comp)) @@ -274,7 +275,7 @@ public sealed class DVLightReplacerSystem : EntitySystem /// Returns true if storage contained at least one light bulb /// which was successfully inserted inside light replacer /// - public bool TryInsertBulbsFromStorage(Entity replacer, Entity storage, EntityUid? userUid = null) + public bool TryInsertBulbsFromStorage(Entity replacer, Entity storage, EntityUid? userUid = null) { if (!Resolve(replacer, ref replacer.Comp) || !Resolve(storage, ref storage.Comp)) From 900f5147873041ffdf8e023d07be1daafa35368d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 21:59:50 +0000 Subject: [PATCH 03/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../LightReplacerMenuBoundUserInterface.cs | 2 +- .../Controls/LightReplacerStatusControl.cs | 2 +- .../LightReplacerStatusControlSystem.cs | 2 +- .../Light/Events/LightReplacerBUIMessages.cs | 2 +- .../Entities/Objects/Tools/light_replacer.yml | 2 +- .../Janitorial/light_replacer.rsi/meta.json | 28 +++++++++---------- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs index c90487b625..88d5ac0ed0 100644 --- a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs +++ b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs @@ -1,4 +1,4 @@ -using Content.Client.UserInterface.Controls; +using Content.Client.UserInterface.Controls; using Content.Shared._DV.Light.Events; using Content.Shared.Light.Components; using JetBrains.Annotations; diff --git a/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs b/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs index 34eb0cd023..26884b5945 100644 --- a/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs +++ b/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs @@ -1,4 +1,4 @@ -using Content.Client.Message; +using Content.Client.Message; using Content.Client.Stylesheets; using Content.Shared.Light.Components; using Robust.Client.UserInterface; diff --git a/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs b/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs index 28a7b14de6..de69b6c15c 100644 --- a/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs +++ b/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs @@ -1,4 +1,4 @@ -using Content.Client._DV.Light.Controls; +using Content.Client._DV.Light.Controls; using Content.Client.Items; using Content.Shared.Light.Components; diff --git a/Content.Shared/_DV/Light/Events/LightReplacerBUIMessages.cs b/Content.Shared/_DV/Light/Events/LightReplacerBUIMessages.cs index 1aeda0a51b..e887edd3ac 100644 --- a/Content.Shared/_DV/Light/Events/LightReplacerBUIMessages.cs +++ b/Content.Shared/_DV/Light/Events/LightReplacerBUIMessages.cs @@ -1,4 +1,4 @@ -using Content.Shared.Light.Components; +using Content.Shared.Light.Components; using Robust.Shared.Serialization; namespace Content.Shared._DV.Light.Events; diff --git a/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml b/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml index c3e84a3bee..d578ce09f6 100644 --- a/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml +++ b/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml @@ -1,4 +1,4 @@ -# The following entities are solely used for sprites in the radial menu. +# The following entities are solely used for sprites in the radial menu. - type: entity id: EjectTubes categories: [ HideSpawnMenu ] diff --git a/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/meta.json b/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/meta.json index bd8de8f939..05d4107377 100644 --- a/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/meta.json +++ b/Resources/Textures/_DV/Objects/Specific/Janitorial/light_replacer.rsi/meta.json @@ -1,17 +1,17 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Eject sprites made by SirWarock on Github using sprites from cev-eris at https://github.com/discordia-space/CEV-Eris/commit/740ff31a81313086cf16761f3677cf1e2ab46c93", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "eject-tubes" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Eject sprites made by SirWarock on Github using sprites from cev-eris at https://github.com/discordia-space/CEV-Eris/commit/740ff31a81313086cf16761f3677cf1e2ab46c93", + "size": { + "x": 32, + "y": 32 }, - { - "name": "eject-bulbs" - } - ] + "states": [ + { + "name": "eject-tubes" + }, + { + "name": "eject-bulbs" + } + ] } From 6388fb19501118e76bfb096f98378328d01eec60 Mon Sep 17 00:00:00 2001 From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Mon, 4 May 2026 13:16:00 +0200 Subject: [PATCH 04/10] Fix --- Resources/Prototypes/Entities/Clothing/Belt/job.yml | 4 ++-- .../Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/Prototypes/Entities/Clothing/Belt/job.yml b/Resources/Prototypes/Entities/Clothing/Belt/job.yml index c605568653..96adc08a2e 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/job.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/job.yml @@ -138,7 +138,7 @@ - GoldenPlunger - WireBrush components: - - LightReplacer + - DVLightReplacer # DeltaV - Replaced the LightReplacerSystem. - SmokeOnTrigger - type: ItemMapper sprite: *BeltOverlay @@ -418,7 +418,7 @@ - Whistle - BalloonPopper - type: ItemMapper # DeltaV - Custom sprite and item mapper - sprite: _DV/Clothing/Belt/belt_overlay.rsi + sprite: _DV/Clothing/Belt/belt_overlay.rsi mapLayers: baton: whitelist: diff --git a/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml b/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml index d578ce09f6..d9580df676 100644 --- a/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml +++ b/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml @@ -4,7 +4,7 @@ categories: [ HideSpawnMenu ] components: - type: Sprite - sprite: Objects/Specific/Janitorial/light_replacer.rsi + sprite: _DV/Objects/Specific/Janitorial/light_replacer.rsi state: eject-tubes - type: entity @@ -12,5 +12,5 @@ categories: [ HideSpawnMenu ] components: - type: Sprite - sprite: Objects/Specific/Janitorial/light_replacer.rsi + sprite: _DV/Objects/Specific/Janitorial/light_replacer.rsi state: eject-bulbs From ce73ff1cce67a38b457fa69ae53034fb4fd2904a Mon Sep 17 00:00:00 2001 From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Mon, 4 May 2026 13:58:20 +0200 Subject: [PATCH 05/10] Revert to Original System --- .../LightReplacerMenuBoundUserInterface.cs | 2 +- .../Controls/LightReplacerStatusControl.cs | 4 +- .../LightReplacerStatusControlSystem.cs | 2 +- .../EntitySystems/LightReplacerSystem.cs | 485 +++++++++--------- .../Components/LightReplacerComponent.cs | 31 +- .../SharedLightReplacerSystem.cs | 305 ++++++++++- .../Components/DVLightReplacerComponent.cs | 49 -- .../EntitySystems/DVLightReplacerSystem.cs | 310 ----------- .../Prototypes/Entities/Clothing/Belt/job.yml | 2 +- .../Entities/Objects/Tools/light_replacer.yml | 8 +- .../Entities/Objects/Tools/light_replacer.yml | 16 - 11 files changed, 586 insertions(+), 628 deletions(-) delete mode 100644 Content.Shared/_DV/Light/Components/DVLightReplacerComponent.cs delete mode 100644 Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs delete mode 100644 Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml diff --git a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs index 88d5ac0ed0..5d055872f5 100644 --- a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs +++ b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs @@ -25,7 +25,7 @@ public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum ui _lightBulbQuery = EntMan.GetEntityQuery(); _metaDataQuery = EntMan.GetEntityQuery(); - if (!EntMan.TryGetComponent(Owner, out var replacer)) + if (!EntMan.TryGetComponent(Owner, out var replacer)) return; var lightTypes = CreateButtons(replacer); diff --git a/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs b/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs index 26884b5945..2fcfe88133 100644 --- a/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs +++ b/Content.Client/_DV/Light/Controls/LightReplacerStatusControl.cs @@ -13,7 +13,7 @@ namespace Content.Client._DV.Light.Controls; public sealed class LightReplacerStatusControl : Control { - private readonly Entity _parent; + private readonly Entity _parent; private readonly RichTextLabel _label; private string? _prevActiveLightTube; @@ -21,7 +21,7 @@ public sealed class LightReplacerStatusControl : Control private string? _labelTube; private string? _labelBulb; - public LightReplacerStatusControl(Entity parent) + public LightReplacerStatusControl(Entity parent) { _parent = parent; _label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } }; diff --git a/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs b/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs index de69b6c15c..e362665605 100644 --- a/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs +++ b/Content.Client/_DV/Light/EntitySystems/LightReplacerStatusControlSystem.cs @@ -11,6 +11,6 @@ public sealed class LightReplacerStatusControlSystem : EntitySystem { public override void Initialize() { - Subs.ItemStatus(replacer => new LightReplacerStatusControl(replacer)); + Subs.ItemStatus(replacer => new LightReplacerStatusControl(replacer)); } } diff --git a/Content.Server/Light/EntitySystems/LightReplacerSystem.cs b/Content.Server/Light/EntitySystems/LightReplacerSystem.cs index 86ba229d47..21e4f1045d 100644 --- a/Content.Server/Light/EntitySystems/LightReplacerSystem.cs +++ b/Content.Server/Light/EntitySystems/LightReplacerSystem.cs @@ -1,242 +1,243 @@ -using System.Linq; -using Content.Server.Light.Components; -using Content.Shared.Examine; -using Content.Shared.Interaction; -using Content.Shared.Light.EntitySystems; -using Content.Shared.Light.Components; -using Content.Shared.Popups; -using Content.Shared.Storage; -using JetBrains.Annotations; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Containers; - -namespace Content.Server.Light.EntitySystems; - -[UsedImplicitly] -public sealed class LightReplacerSystem : SharedLightReplacerSystem // THIS HAS BEEN REPLACED BY DVLightReplacerSystem! -{ - [Dependency] private readonly PoweredLightSystem _poweredLight = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedPopupSystem _popupSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(HandleInteract); - SubscribeLocalEvent(HandleAfterInteract); - } - - private void OnExamined(EntityUid uid, LightReplacerComponent component, ExaminedEvent args) - { - using (args.PushGroup(nameof(LightReplacerComponent))) - { - if (!component.InsertedBulbs.ContainedEntities.Any()) - { - args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights")); - return; - } - - args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights")); - var groups = new Dictionary(); - var metaQuery = GetEntityQuery(); - foreach (var bulb in component.InsertedBulbs.ContainedEntities) - { - var metaData = metaQuery.GetComponent(bulb); - groups[metaData.EntityName] = groups.GetValueOrDefault(metaData.EntityName) + 1; - } - - foreach (var (name, amount) in groups) - { - args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name))); - } - } - } - - private void OnMapInit(EntityUid uid, LightReplacerComponent component, MapInitEvent args) - { - var xform = Transform(uid); - foreach (var spawn in EntitySpawnCollection.GetSpawns(component.Contents)) - { - var ent = Spawn(spawn, xform.Coordinates); - TryInsertBulb(uid, ent, replacer: component); - } - } - - private void OnInit(EntityUid uid, LightReplacerComponent replacer, ComponentInit args) - { - replacer.InsertedBulbs = _container.EnsureContainer(uid, "light_replacer_storage"); - } - - private void HandleAfterInteract(EntityUid uid, LightReplacerComponent component, AfterInteractEvent eventArgs) - { - if (eventArgs.Handled) - return; - - // standard interaction checks - if (!eventArgs.CanReach) - return; - - // behaviour will depends on target type - if (eventArgs.Target != null) - { - var targetUid = (EntityUid) eventArgs.Target; - - // replace broken light in fixture? - if (TryComp(targetUid, out var fixture)) - eventArgs.Handled = TryReplaceBulb(uid, targetUid, eventArgs.User, component, fixture); - // add new bulb to light replacer container? - else if (TryComp(targetUid, out var bulb)) - eventArgs.Handled = TryInsertBulb(uid, targetUid, eventArgs.User, true, component, bulb); - } - } - - private void HandleInteract(EntityUid uid, LightReplacerComponent component, InteractUsingEvent eventArgs) - { - if (eventArgs.Handled) - return; - - var usedUid = eventArgs.Used; - - // want to insert a new light bulb? - if (TryComp(usedUid, out var bulb)) - eventArgs.Handled = TryInsertBulb(uid, usedUid, eventArgs.User, true, component, bulb); - // add bulbs from storage? - else if (TryComp(usedUid, out var storage)) - eventArgs.Handled = TryInsertBulbsFromStorage(uid, usedUid, eventArgs.User, component, storage); - } - - /// - /// Try to replace a light bulb in - /// using light replacer. Light fixture should have . - /// - /// True if successfully replaced light, false otherwise - public bool TryReplaceBulb(EntityUid replacerUid, EntityUid fixtureUid, EntityUid? userUid = null, - LightReplacerComponent? replacer = null, PoweredLightComponent? fixture = null) - { - if (!Resolve(replacerUid, ref replacer)) - return false; - if (!Resolve(fixtureUid, ref fixture)) - return false; - - // check if light bulb is broken or missing - var fixtureBulbUid = _poweredLight.GetBulb(fixtureUid, fixture); - if (fixtureBulbUid != null) - { - if (!TryComp(fixtureBulbUid.Value, out var fixtureBulb)) - return false; - if (fixtureBulb.State == LightBulbState.Normal) - return false; - } - - // try get first inserted bulb of the same type as targeted light fixtutre - var bulb = replacer.InsertedBulbs.ContainedEntities.FirstOrDefault( - e => CompOrNull(e)?.Type == fixture.BulbType); - - // found bulb in inserted storage - if (bulb.Valid) // FirstOrDefault can return default/invalid uid. - { - // try to remove it - var hasRemoved = _container.Remove(bulb, replacer.InsertedBulbs); - if (!hasRemoved) - return false; - } - else - { - if (userUid != null) - { - var msg = Loc.GetString("comp-light-replacer-missing-light", - ("light-replacer", replacerUid)); - _popupSystem.PopupEntity(msg, replacerUid, userUid.Value); - } - return false; - } - - // insert it into fixture - var wasReplaced = _poweredLight.ReplaceBulb(fixtureUid, bulb, fixture); - if (wasReplaced) - { - _audio.PlayPvs(replacer.Sound, replacerUid); - } - - return wasReplaced; - } - - /// - /// Try to insert a new bulb inside light replacer - /// - /// True if successfully inserted light, false otherwise - public bool TryInsertBulb(EntityUid replacerUid, EntityUid bulbUid, EntityUid? userUid = null, bool showTooltip = false, - LightReplacerComponent? replacer = null, LightBulbComponent? bulb = null) - { - if (!Resolve(replacerUid, ref replacer)) - return false; - if (!Resolve(bulbUid, ref bulb)) - return false; - - // only normal (non-broken) bulbs can be inserted inside light replacer - if (bulb.State != LightBulbState.Normal) - { - if (showTooltip && userUid != null) - { - var msg = Loc.GetString("comp-light-replacer-insert-broken-light"); - _popupSystem.PopupEntity(msg, replacerUid, userUid.Value); - } - - return false; - } - - // try insert light and show message - var hasInsert = _container.Insert(bulbUid, replacer.InsertedBulbs); - if (hasInsert && showTooltip && userUid != null) - { - var msg = Loc.GetString("comp-light-replacer-insert-light", - ("light-replacer", replacerUid), ("bulb", bulbUid)); - _popupSystem.PopupEntity(msg, replacerUid, userUid.Value, PopupType.Medium); - } - - return hasInsert; - } - - /// - /// Try to insert all light bulbs from storage (for example light tubes box) - /// - /// - /// Returns true if storage contained at least one light bulb - /// which was successfully inserted inside light replacer - /// - public bool TryInsertBulbsFromStorage(EntityUid replacerUid, EntityUid storageUid, EntityUid? userUid = null, - LightReplacerComponent? replacer = null, StorageComponent? storage = null) - { - if (!Resolve(replacerUid, ref replacer)) - return false; - if (!Resolve(storageUid, ref storage)) - return false; - - var insertedBulbs = 0; - var storagedEnts = storage.Container.ContainedEntities.ToArray(); - - foreach (var ent in storagedEnts) - { - if (TryComp(ent, out var bulb) && - TryInsertBulb(replacerUid, ent, userUid, false, replacer, bulb)) - { - insertedBulbs++; - } - } - - // show some message if success - if (insertedBulbs > 0 && userUid != null) - { - var msg = Loc.GetString("comp-light-replacer-refill-from-storage", ("light-replacer", replacerUid)); - _popupSystem.PopupEntity(msg, replacerUid, userUid.Value, PopupType.Medium); - } - - return insertedBulbs > 0; - } -} +// THIS HAS BEEN REMOVED BECAUSE THIS SYSTEM WAS OVERHAULED IN Delta-V! +// using System.Linq; +// using Content.Server.Light.Components; +// using Content.Shared.Examine; +// using Content.Shared.Interaction; +// using Content.Shared.Light.EntitySystems; +// using Content.Shared.Light.Components; +// using Content.Shared.Popups; +// using Content.Shared.Storage; +// using JetBrains.Annotations; +// using Robust.Shared.Audio; +// using Robust.Shared.Audio.Systems; +// using Robust.Shared.Containers; +// +// namespace Content.Server.Light.EntitySystems; +// +// [UsedImplicitly] +// public sealed class LightReplacerSystem : SharedLightReplacerSystem +// { +// [Dependency] private readonly PoweredLightSystem _poweredLight = default!; +// [Dependency] private readonly SharedAudioSystem _audio = default!; +// [Dependency] private readonly SharedContainerSystem _container = default!; +// [Dependency] private readonly SharedPopupSystem _popupSystem = default!; +// +// public override void Initialize() +// { +// base.Initialize(); +// +// SubscribeLocalEvent(OnExamined); +// SubscribeLocalEvent(OnMapInit); +// SubscribeLocalEvent(OnInit); +// SubscribeLocalEvent(HandleInteract); +// SubscribeLocalEvent(HandleAfterInteract); +// } +// +// private void OnExamined(EntityUid uid, LightReplacerComponent component, ExaminedEvent args) +// { +// using (args.PushGroup(nameof(LightReplacerComponent))) +// { +// if (!component.InsertedBulbs.ContainedEntities.Any()) +// { +// args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights")); +// return; +// } +// +// args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights")); +// var groups = new Dictionary(); +// var metaQuery = GetEntityQuery(); +// foreach (var bulb in component.InsertedBulbs.ContainedEntities) +// { +// var metaData = metaQuery.GetComponent(bulb); +// groups[metaData.EntityName] = groups.GetValueOrDefault(metaData.EntityName) + 1; +// } +// +// foreach (var (name, amount) in groups) +// { +// args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name))); +// } +// } +// } +// +// private void OnMapInit(EntityUid uid, LightReplacerComponent component, MapInitEvent args) +// { +// var xform = Transform(uid); +// foreach (var spawn in EntitySpawnCollection.GetSpawns(component.Contents)) +// { +// var ent = Spawn(spawn, xform.Coordinates); +// TryInsertBulb(uid, ent, replacer: component); +// } +// } +// +// private void OnInit(EntityUid uid, LightReplacerComponent replacer, ComponentInit args) +// { +// replacer.InsertedBulbs = _container.EnsureContainer(uid, "light_replacer_storage"); +// } +// +// private void HandleAfterInteract(EntityUid uid, LightReplacerComponent component, AfterInteractEvent eventArgs) +// { +// if (eventArgs.Handled) +// return; +// +// // standard interaction checks +// if (!eventArgs.CanReach) +// return; +// +// // behaviour will depends on target type +// if (eventArgs.Target != null) +// { +// var targetUid = (EntityUid) eventArgs.Target; +// +// // replace broken light in fixture? +// if (TryComp(targetUid, out var fixture)) +// eventArgs.Handled = TryReplaceBulb(uid, targetUid, eventArgs.User, component, fixture); +// // add new bulb to light replacer container? +// else if (TryComp(targetUid, out var bulb)) +// eventArgs.Handled = TryInsertBulb(uid, targetUid, eventArgs.User, true, component, bulb); +// } +// } +// +// private void HandleInteract(EntityUid uid, LightReplacerComponent component, InteractUsingEvent eventArgs) +// { +// if (eventArgs.Handled) +// return; +// +// var usedUid = eventArgs.Used; +// +// // want to insert a new light bulb? +// if (TryComp(usedUid, out var bulb)) +// eventArgs.Handled = TryInsertBulb(uid, usedUid, eventArgs.User, true, component, bulb); +// // add bulbs from storage? +// else if (TryComp(usedUid, out var storage)) +// eventArgs.Handled = TryInsertBulbsFromStorage(uid, usedUid, eventArgs.User, component, storage); +// } +// +// /// +// /// Try to replace a light bulb in +// /// using light replacer. Light fixture should have . +// /// +// /// True if successfully replaced light, false otherwise +// public bool TryReplaceBulb(EntityUid replacerUid, EntityUid fixtureUid, EntityUid? userUid = null, +// LightReplacerComponent? replacer = null, PoweredLightComponent? fixture = null) +// { +// if (!Resolve(replacerUid, ref replacer)) +// return false; +// if (!Resolve(fixtureUid, ref fixture)) +// return false; +// +// // check if light bulb is broken or missing +// var fixtureBulbUid = _poweredLight.GetBulb(fixtureUid, fixture); +// if (fixtureBulbUid != null) +// { +// if (!TryComp(fixtureBulbUid.Value, out var fixtureBulb)) +// return false; +// if (fixtureBulb.State == LightBulbState.Normal) +// return false; +// } +// +// // try get first inserted bulb of the same type as targeted light fixtutre +// var bulb = replacer.InsertedBulbs.ContainedEntities.FirstOrDefault( +// e => CompOrNull(e)?.Type == fixture.BulbType); +// +// // found bulb in inserted storage +// if (bulb.Valid) // FirstOrDefault can return default/invalid uid. +// { +// // try to remove it +// var hasRemoved = _container.Remove(bulb, replacer.InsertedBulbs); +// if (!hasRemoved) +// return false; +// } +// else +// { +// if (userUid != null) +// { +// var msg = Loc.GetString("comp-light-replacer-missing-light", +// ("light-replacer", replacerUid)); +// _popupSystem.PopupEntity(msg, replacerUid, userUid.Value); +// } +// return false; +// } +// +// // insert it into fixture +// var wasReplaced = _poweredLight.ReplaceBulb(fixtureUid, bulb, fixture); +// if (wasReplaced) +// { +// _audio.PlayPvs(replacer.Sound, replacerUid); +// } +// +// return wasReplaced; +// } +// +// /// +// /// Try to insert a new bulb inside light replacer +// /// +// /// True if successfully inserted light, false otherwise +// public bool TryInsertBulb(EntityUid replacerUid, EntityUid bulbUid, EntityUid? userUid = null, bool showTooltip = false, +// LightReplacerComponent? replacer = null, LightBulbComponent? bulb = null) +// { +// if (!Resolve(replacerUid, ref replacer)) +// return false; +// if (!Resolve(bulbUid, ref bulb)) +// return false; +// +// // only normal (non-broken) bulbs can be inserted inside light replacer +// if (bulb.State != LightBulbState.Normal) +// { +// if (showTooltip && userUid != null) +// { +// var msg = Loc.GetString("comp-light-replacer-insert-broken-light"); +// _popupSystem.PopupEntity(msg, replacerUid, userUid.Value); +// } +// +// return false; +// } +// +// // try insert light and show message +// var hasInsert = _container.Insert(bulbUid, replacer.InsertedBulbs); +// if (hasInsert && showTooltip && userUid != null) +// { +// var msg = Loc.GetString("comp-light-replacer-insert-light", +// ("light-replacer", replacerUid), ("bulb", bulbUid)); +// _popupSystem.PopupEntity(msg, replacerUid, userUid.Value, PopupType.Medium); +// } +// +// return hasInsert; +// } +// +// /// +// /// Try to insert all light bulbs from storage (for example light tubes box) +// /// +// /// +// /// Returns true if storage contained at least one light bulb +// /// which was successfully inserted inside light replacer +// /// +// public bool TryInsertBulbsFromStorage(EntityUid replacerUid, EntityUid storageUid, EntityUid? userUid = null, +// LightReplacerComponent? replacer = null, StorageComponent? storage = null) +// { +// if (!Resolve(replacerUid, ref replacer)) +// return false; +// if (!Resolve(storageUid, ref storage)) +// return false; +// +// var insertedBulbs = 0; +// var storagedEnts = storage.Container.ContainedEntities.ToArray(); +// +// foreach (var ent in storagedEnts) +// { +// if (TryComp(ent, out var bulb) && +// TryInsertBulb(replacerUid, ent, userUid, false, replacer, bulb)) +// { +// insertedBulbs++; +// } +// } +// +// // show some message if success +// if (insertedBulbs > 0 && userUid != null) +// { +// var msg = Loc.GetString("comp-light-replacer-refill-from-storage", ("light-replacer", replacerUid)); +// _popupSystem.PopupEntity(msg, replacerUid, userUid.Value, PopupType.Medium); +// } +// +// return insertedBulbs > 0; +// } +// } diff --git a/Content.Shared/Light/Components/LightReplacerComponent.cs b/Content.Shared/Light/Components/LightReplacerComponent.cs index 1276ff9edc..4c175043ec 100644 --- a/Content.Shared/Light/Components/LightReplacerComponent.cs +++ b/Content.Shared/Light/Components/LightReplacerComponent.cs @@ -4,6 +4,7 @@ using Content.Shared.Storage; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Utility; // DeltaV - Predicted Light Replacer & Added Radial Menu namespace Content.Shared.Light.Components; @@ -11,7 +12,8 @@ namespace Content.Shared.Light.Components; /// Device that allows user to quikly change bulbs in /// Can be reloaded by new light tubes or light bulbs /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedLightReplacerSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] // DeltaV - Predicted Light Replacer & Added Radial Menu +[Access(typeof(SharedLightReplacerSystem))] public sealed partial class LightReplacerComponent : Component { [DataField("sound")] @@ -34,4 +36,31 @@ public sealed partial class LightReplacerComponent : Component /// [DataField("contents")] public List Contents = new(); + + // Start DeltaV - Predicting Light Replacers & Adding Radial Menu + /// + /// This string defines what kind of tube will be inserted into light fixtures. + /// + [DataField, AutoNetworkedField] + public string ActiveLightTube = "fluorescent light tube"; + + /// + /// This string defines what kind of bulb will be inserted into light fixtures. + /// + [DataField, AutoNetworkedField] + public string ActiveLightBulb = "incandescent light bulb"; + + /// + /// The Icon Sprite for ejecting tubes in the radial menu. + /// + [DataField] + public SpriteSpecifier EjectTubes = new SpriteSpecifier.Rsi(new ResPath("_DV/Objects/Specific/Janitorial/light_replacer.rsi"), "eject-tubes"); + + /// + /// The Icon Sprite for ejecting bulbs in the radial menu. + /// + [DataField] + public SpriteSpecifier EjectBulbs = new SpriteSpecifier.Rsi(new ResPath("_DV/Objects/Specific/Janitorial/light_replacer.rsi"), "eject-bulbs"); + + // End DeltaV - Predicting Light Replacers & Adding Radial Menu } diff --git a/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs b/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs index 29001d6c73..1772616ecc 100644 --- a/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs +++ b/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs @@ -1,5 +1,308 @@ +using System.Linq; +using Content.Shared._DV.Light.Events; +using Content.Shared.Examine; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Light.Components; +using Content.Shared.Popups; +using Content.Shared.Storage; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Serialization; + namespace Content.Shared.Light.EntitySystems; -public abstract class SharedLightReplacerSystem : EntitySystem +public sealed class SharedLightReplacerSystem : EntitySystem // There is no Client and Server System anymore, this has been greatly overhauled in Delta-V. { + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPoweredLightSystem _poweredLight = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + + private EntityQuery _lightBulbQuery; + private EntityQuery _metaDataQuery; + + public override void Initialize() + { + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(HandleInteract); + SubscribeLocalEvent(HandleAfterInteract); + SubscribeLocalEvent(OnEjectMessage); + SubscribeLocalEvent(OnSwitchMessage); + + _lightBulbQuery = GetEntityQuery(); + _metaDataQuery = GetEntityQuery(); + } + + private void OnInit(Entity replacer, ref ComponentInit args) + { + // This needs to be handled on CompInit because otherwise, it's empty on the client. + replacer.Comp.InsertedBulbs = _container.EnsureContainer(replacer, "light_replacer_storage"); + } + + private void OnMapInit(Entity replacer, ref MapInitEvent args) + { + var xform = Transform(replacer); + foreach (var spawn in EntitySpawnCollection.GetSpawns(replacer.Comp.Contents)) + { + var light = Spawn(spawn, xform.Coordinates); + TryInsertBulb(replacer.AsNullable(), light); + } + } + + private void OnExamined(Entity replacer, ref ExaminedEvent args) + { + using (args.PushGroup(nameof(LightReplacerComponent))) + { + if (!replacer.Comp.InsertedBulbs.ContainedEntities.Any()) + { + args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights")); + return; + } + + args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights")); + var groups = new Dictionary(); + foreach (var bulb in replacer.Comp.InsertedBulbs.ContainedEntities) + { + var metaData = _metaDataQuery.GetComponent(bulb); + groups[metaData.EntityName] = groups.GetValueOrDefault(metaData.EntityName) + 1; + } + + foreach (var (name, amount) in groups) + { + args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name))); + } + } + } + + private void OnUse(Entity replacer, ref UseInHandEvent args) + { + if (args.Handled) + return; + + args.ApplyDelay = false; + + if (!replacer.Comp.InsertedBulbs.ContainedEntities.Any()) + { + _popup.PopupClient(Loc.GetString("comp-light-replacer-open-empty", ("light-replacer", replacer)), replacer, args.User); + return; + } + + args.Handled = true; + _ui.OpenUi(replacer.Owner, LightReplacerUiKey.Key, args.User); + } + + private void HandleAfterInteract(Entity replacer, ref AfterInteractEvent eventArgs) + { + if (eventArgs.Handled + || !eventArgs.CanReach // standard interaction checks + || eventArgs.Target == null) // behavior will depend on the target type + return; + + var targetUid = (EntityUid) eventArgs.Target; + + // replace broken light in fixture? + if (TryComp(targetUid, out var fixture)) + eventArgs.Handled = TryReplaceBulb(replacer.AsNullable(), (targetUid, fixture), eventArgs.User); + // add new bulb to light replacer container? + else if (_lightBulbQuery.TryComp(targetUid, out var bulb)) + eventArgs.Handled = TryInsertBulb(replacer.AsNullable(), (targetUid, bulb), eventArgs.User, true); + } + + private void HandleInteract(Entity replacer, ref InteractUsingEvent eventArgs) + { + if (eventArgs.Handled) + return; + + var usedUid = eventArgs.Used; + + // want to insert a new light bulb? + if (_lightBulbQuery.TryComp(usedUid, out var bulb)) + eventArgs.Handled = TryInsertBulb(replacer.AsNullable(), (usedUid, bulb), eventArgs.User, true); + // add bulbs from storage? + else if (TryComp(usedUid, out var storage)) + eventArgs.Handled = TryInsertBulbsFromStorage(replacer.AsNullable(), (usedUid, storage), eventArgs.User); + } + + private void OnEjectMessage(Entity replacer, ref EjectLightTypeMessage args) + { + HashSet lightsToEject = []; + foreach (var light in replacer.Comp.InsertedBulbs.ContainedEntities) + { + if (_metaDataQuery.TryComp(light, out var metaData) && metaData.EntityName == args.LightName) + lightsToEject.Add(light); + } + + foreach (var light in lightsToEject) + { + _container.Remove(light, replacer.Comp.InsertedBulbs); + } + } + + private void OnSwitchMessage(Entity replacer, ref SwitchLightTypeMessage args) + { + if (args.LightType == LightBulbType.Tube) + replacer.Comp.ActiveLightTube = args.LightName; + else + replacer.Comp.ActiveLightBulb = args.LightName; + Dirty(replacer); + } + + /// + /// Try to replace a light bulb in + /// using light replacer. Light fixture should have . + /// + /// The light replacer used to replace the bulb. + /// The fixture whose light is being replaced. + /// The user who is replacing the light. + /// True if successfully replaced light, false otherwise + public bool TryReplaceBulb(Entity replacer, Entity fixture, EntityUid? userUid = null) + { + if (!Resolve(replacer, ref replacer.Comp) + || !Resolve(fixture, ref fixture.Comp)) + return false; + + var activeType = fixture.Comp.BulbType == LightBulbType.Tube + ? replacer.Comp.ActiveLightTube + : replacer.Comp.ActiveLightBulb; + + // check if light bulb is broken or missing + var fixtureBulbUid = _poweredLight.GetBulb(fixture, fixture.Comp); + if (fixtureBulbUid != null) + { + if (!_lightBulbQuery.TryComp(fixtureBulbUid.Value, out var fixtureBulb)) + return false; + + if (fixtureBulb.State == LightBulbState.Normal + && _metaDataQuery.TryComp(fixtureBulbUid, out var metaData) + && metaData.EntityName == activeType) + { + _popup.PopupClient(Loc.GetString("comp-light-replacer-same-light", ("light", fixtureBulbUid)), fixture, userUid, PopupType.Medium); + return false; + } + } + + EntityUid? bulb = null; + foreach (var insertedBulb in replacer.Comp.InsertedBulbs.ContainedEntities) + { + if (!_metaDataQuery.TryComp(insertedBulb, out var metaData) || metaData.EntityName != activeType) + continue; + + bulb = insertedBulb; + break; + } + + // found bulb in inserted storage + if (bulb.HasValue) + { + // try to remove it + var hasRemoved = _container.Remove(bulb.Value, replacer.Comp.InsertedBulbs); + if (!hasRemoved) + return false; + } + else + { + if (userUid == null) + return false; + + var msg = Loc.GetString("comp-light-replacer-missing-light-dv", + ("light-name", activeType), + ("light-replacer", replacer)); + _popup.PopupClient(msg, replacer, userUid.Value); + return false; + } + + // insert it into fixture + var wasReplaced = _poweredLight.ReplaceBulb(fixture, bulb.Value, fixture.Comp); + if (wasReplaced) + { + _audio.PlayPredicted(replacer.Comp.Sound, replacer, userUid); + } + + return wasReplaced; + } + + /// + /// Try to insert a new bulb inside light replacer + /// + /// The light replacer to insert a light into. + /// The light to insert into the replacer. + /// The user who is inserting the light. + /// Whether to show a popup. + /// True if successfully inserted light, false otherwise + public bool TryInsertBulb(Entity replacer, Entity bulb, EntityUid? userUid = null, bool showPopup = false) + { + if (!Resolve(replacer, ref replacer.Comp) + || !Resolve(bulb, ref bulb.Comp)) + return false; + + // only normal (non-broken) bulbs can be inserted inside light replacer + if (bulb.Comp.State != LightBulbState.Normal) + { + if (!showPopup || userUid == null) + return false; + + var error = Loc.GetString("comp-light-replacer-insert-broken-light"); + _popup.PopupClient(error, replacer, userUid.Value); + return false; + } + // try insert light and show message + var hasInsert = _container.Insert(bulb.Owner, replacer.Comp.InsertedBulbs); + + if (!hasInsert || !showPopup || userUid == null) + return hasInsert; + + var message = Loc.GetString("comp-light-replacer-insert-light", + ("light-replacer", replacer), ("bulb", bulb)); + _popup.PopupClient(message, replacer, userUid.Value); + + return hasInsert; + } + + /// + /// Try to insert all light bulbs from storage (for example light tubes box) + /// + /// The light replacer to insert bulbs into. + /// The storage whose contents should be inserted. + /// The user who inserts the contents. + /// + /// Returns true if storage contained at least one light bulb + /// which was successfully inserted inside light replacer + /// + public bool TryInsertBulbsFromStorage(Entity replacer, Entity storage, EntityUid? userUid = null) + { + if (!Resolve(replacer, ref replacer.Comp) + || !Resolve(storage, ref storage.Comp)) + return false; + + var insertedBulbs = 0; + var storedEntities = storage.Comp.Container.ContainedEntities.ToArray(); + + foreach (var ent in storedEntities) + { + if (TryInsertBulb(replacer, ent, userUid)) + { + insertedBulbs++; + } + } + + // show some message if success + if (insertedBulbs > 0 && userUid != null) + { + var msg = Loc.GetString("comp-light-replacer-refill-from-storage", ("light-replacer", replacer)); + _popup.PopupClient(msg, replacer, userUid.Value); + } + + return insertedBulbs > 0; + } +} + +[Serializable, NetSerializable] +public enum LightReplacerUiKey : byte +{ + Key, } diff --git a/Content.Shared/_DV/Light/Components/DVLightReplacerComponent.cs b/Content.Shared/_DV/Light/Components/DVLightReplacerComponent.cs deleted file mode 100644 index 11661775c4..0000000000 --- a/Content.Shared/_DV/Light/Components/DVLightReplacerComponent.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Content.Shared._DV.Light.EntitySystems; -using Content.Shared.Light.Components; -using Content.Shared.Storage; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.GameStates; - -namespace Content.Shared._DV.Light.Components; - -/// -/// Device that allows user to quickly change bulbs in -/// Can be reloaded by new light tubes or light bulbs -/// -[RegisterComponent, NetworkedComponent, Access(typeof(DVLightReplacerSystem)), AutoGenerateComponentState] -public sealed partial class DVLightReplacerComponent : Component -{ - [DataField] - public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Weapons/click.ogg") - { - Params = new AudioParams - { - Volume = -4f, - } - }; - - /// - /// Bulbs that were inserted inside light replacer - /// - [ViewVariables] - public Container InsertedBulbs = default!; - - /// - /// This string defines what kind of tube will be inserted into light fixtures. - /// - [DataField, AutoNetworkedField] - public string ActiveLightTube = "fluorescent light tube"; - - /// - /// This string defines what kind of bulb will be inserted into light fixtures. - /// - [DataField, AutoNetworkedField] - public string ActiveLightBulb = "incandescent light bulb"; - - /// - /// The default starting bulbs - /// - [DataField] - public List StartingContent = []; -} diff --git a/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs b/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs deleted file mode 100644 index 2b214b6c02..0000000000 --- a/Content.Shared/_DV/Light/EntitySystems/DVLightReplacerSystem.cs +++ /dev/null @@ -1,310 +0,0 @@ -using System.Linq; -using Content.Shared._DV.Light.Components; -using Content.Shared._DV.Light.Events; -using Content.Shared.Examine; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Light.Components; -using Content.Shared.Light.EntitySystems; -using Content.Shared.Popups; -using Content.Shared.Storage; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Containers; -using Robust.Shared.Serialization; - -namespace Content.Shared._DV.Light.EntitySystems; - -public sealed class DVLightReplacerSystem : EntitySystem -{ - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly SharedPoweredLightSystem _poweredLight = default!; - [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; - - private EntityQuery _lightBulbQuery; - private EntityQuery _metaDataQuery; - - public override void Initialize() - { - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnUse); - SubscribeLocalEvent(HandleInteract); - SubscribeLocalEvent(HandleAfterInteract); - SubscribeLocalEvent(OnEjectMessage); - SubscribeLocalEvent(OnSwitchMessage); - - _lightBulbQuery = GetEntityQuery(); - _metaDataQuery = GetEntityQuery(); - } - - private void OnInit(Entity replacer, ref ComponentInit args) - { - // This needs to be handled on CompInit because otherwise, it's empty on the client. - replacer.Comp.InsertedBulbs = _container.EnsureContainer(replacer, "light_replacer_storage"); - } - - private void OnMapInit(Entity replacer, ref MapInitEvent args) - { - var xform = Transform(replacer); - foreach (var spawn in EntitySpawnCollection.GetSpawns(replacer.Comp.StartingContent)) - { - var light = Spawn(spawn, xform.Coordinates); - TryInsertBulb(replacer.AsNullable(), light); - } - } - - private void OnExamined(Entity replacer, ref ExaminedEvent args) - { - using (args.PushGroup(nameof(DVLightReplacerComponent))) - { - if (!replacer.Comp.InsertedBulbs.ContainedEntities.Any()) - { - args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights")); - return; - } - - args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights")); - var groups = new Dictionary(); - foreach (var bulb in replacer.Comp.InsertedBulbs.ContainedEntities) - { - var metaData = _metaDataQuery.GetComponent(bulb); - groups[metaData.EntityName] = groups.GetValueOrDefault(metaData.EntityName) + 1; - } - - foreach (var (name, amount) in groups) - { - args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name))); - } - } - } - - private void OnUse(Entity replacer, ref UseInHandEvent args) - { - if (args.Handled) - return; - - args.ApplyDelay = false; - - if (!replacer.Comp.InsertedBulbs.ContainedEntities.Any()) - { - _popup.PopupClient(Loc.GetString("comp-light-replacer-open-empty", ("light-replacer", replacer)), replacer, args.User); - return; - } - - args.Handled = true; - _ui.OpenUi(replacer.Owner, LightReplacerUiKey.Key, args.User); - } - - private void HandleAfterInteract(Entity replacer, ref AfterInteractEvent eventArgs) - { - if (eventArgs.Handled - || !eventArgs.CanReach // standard interaction checks - || eventArgs.Target == null) // behavior will depend on the target type - return; - - var targetUid = (EntityUid) eventArgs.Target; - - // replace broken light in fixture? - if (TryComp(targetUid, out var fixture)) - eventArgs.Handled = TryReplaceBulb(replacer.AsNullable(), (targetUid, fixture), eventArgs.User); - // add new bulb to light replacer container? - else if (_lightBulbQuery.TryComp(targetUid, out var bulb)) - eventArgs.Handled = TryInsertBulb(replacer.AsNullable(), (targetUid, bulb), eventArgs.User, true); - } - - private void HandleInteract(Entity replacer, ref InteractUsingEvent eventArgs) - { - if (eventArgs.Handled) - return; - - var usedUid = eventArgs.Used; - - // want to insert a new light bulb? - if (_lightBulbQuery.TryComp(usedUid, out var bulb)) - eventArgs.Handled = TryInsertBulb(replacer.AsNullable(), (usedUid, bulb), eventArgs.User, true); - // add bulbs from storage? - else if (TryComp(usedUid, out var storage)) - eventArgs.Handled = TryInsertBulbsFromStorage(replacer.AsNullable(), (usedUid, storage), eventArgs.User); - } - - private void OnEjectMessage(Entity replacer, ref EjectLightTypeMessage args) - { - HashSet lightsToEject = []; - foreach (var light in replacer.Comp.InsertedBulbs.ContainedEntities) - { - if (_metaDataQuery.TryComp(light, out var metaData) && metaData.EntityName == args.LightName) - lightsToEject.Add(light); - } - - foreach (var light in lightsToEject) - { - _container.Remove(light, replacer.Comp.InsertedBulbs); - } - } - - private void OnSwitchMessage(Entity replacer, ref SwitchLightTypeMessage args) - { - if (args.LightType == LightBulbType.Tube) - replacer.Comp.ActiveLightTube = args.LightName; - else - replacer.Comp.ActiveLightBulb = args.LightName; - Dirty(replacer); - } - - /// - /// Try to replace a light bulb in - /// using light replacer. Light fixture should have . - /// - /// The light replacer used to replace the bulb. - /// The fixture whose light is being replaced. - /// The user who is replacing the light. - /// True if successfully replaced light, false otherwise - public bool TryReplaceBulb(Entity replacer, Entity fixture, EntityUid? userUid = null) - { - if (!Resolve(replacer, ref replacer.Comp) - || !Resolve(fixture, ref fixture.Comp)) - return false; - - var activeType = fixture.Comp.BulbType == LightBulbType.Tube - ? replacer.Comp.ActiveLightTube - : replacer.Comp.ActiveLightBulb; - - // check if light bulb is broken or missing - var fixtureBulbUid = _poweredLight.GetBulb(fixture, fixture.Comp); - if (fixtureBulbUid != null) - { - if (!_lightBulbQuery.TryComp(fixtureBulbUid.Value, out var fixtureBulb)) - return false; - - if (fixtureBulb.State == LightBulbState.Normal - && _metaDataQuery.TryComp(fixtureBulbUid, out var metaData) - && metaData.EntityName == activeType) - { - _popup.PopupClient(Loc.GetString("comp-light-replacer-same-light", ("light", fixtureBulbUid)), fixture, userUid, PopupType.Medium); - return false; - } - } - - EntityUid? bulb = null; - foreach (var insertedBulb in replacer.Comp.InsertedBulbs.ContainedEntities) - { - if (!_metaDataQuery.TryComp(insertedBulb, out var metaData) || metaData.EntityName != activeType) - continue; - - bulb = insertedBulb; - break; - } - - // found bulb in inserted storage - if (bulb.HasValue) - { - // try to remove it - var hasRemoved = _container.Remove(bulb.Value, replacer.Comp.InsertedBulbs); - if (!hasRemoved) - return false; - } - else - { - if (userUid == null) - return false; - - var msg = Loc.GetString("comp-light-replacer-missing-light-dv", - ("light-name", activeType), - ("light-replacer", replacer)); - _popup.PopupClient(msg, replacer, userUid.Value); - return false; - } - - // insert it into fixture - var wasReplaced = _poweredLight.ReplaceBulb(fixture, bulb.Value, fixture.Comp); - if (wasReplaced) - { - _audio.PlayPredicted(replacer.Comp.Sound, replacer, userUid); - } - - return wasReplaced; - } - - /// - /// Try to insert a new bulb inside light replacer - /// - /// The light replacer to insert a light into. - /// The light to insert into the replacer. - /// The user who is inserting the light. - /// Whether to show a popup. - /// True if successfully inserted light, false otherwise - public bool TryInsertBulb(Entity replacer, Entity bulb, EntityUid? userUid = null, bool showPopup = false) - { - if (!Resolve(replacer, ref replacer.Comp) - || !Resolve(bulb, ref bulb.Comp)) - return false; - - // only normal (non-broken) bulbs can be inserted inside light replacer - if (bulb.Comp.State != LightBulbState.Normal) - { - if (!showPopup || userUid == null) - return false; - - var error = Loc.GetString("comp-light-replacer-insert-broken-light"); - _popup.PopupClient(error, replacer, userUid.Value); - return false; - } - // try insert light and show message - var hasInsert = _container.Insert(bulb.Owner, replacer.Comp.InsertedBulbs); - - if (!hasInsert || !showPopup || userUid == null) - return hasInsert; - - var message = Loc.GetString("comp-light-replacer-insert-light", - ("light-replacer", replacer), ("bulb", bulb)); - _popup.PopupClient(message, replacer, userUid.Value, PopupType.Medium); - - return hasInsert; - } - - /// - /// Try to insert all light bulbs from storage (for example light tubes box) - /// - /// The light replacer to insert bulbs into. - /// The storage whose contents should be inserted. - /// The user who inserts the contents. - /// - /// Returns true if storage contained at least one light bulb - /// which was successfully inserted inside light replacer - /// - public bool TryInsertBulbsFromStorage(Entity replacer, Entity storage, EntityUid? userUid = null) - { - if (!Resolve(replacer, ref replacer.Comp) - || !Resolve(storage, ref storage.Comp)) - return false; - - var insertedBulbs = 0; - var storedEntities = storage.Comp.Container.ContainedEntities.ToArray(); - - foreach (var ent in storedEntities) - { - if (TryInsertBulb(replacer, ent, userUid)) - { - insertedBulbs++; - } - } - - // show some message if success - if (insertedBulbs > 0 && userUid != null) - { - var msg = Loc.GetString("comp-light-replacer-refill-from-storage", ("light-replacer", replacer)); - _popup.PopupClient(msg, replacer, userUid.Value, PopupType.Medium); - } - - return insertedBulbs > 0; - } -} - -[Serializable, NetSerializable] -public enum LightReplacerUiKey : byte -{ - Key, -} diff --git a/Resources/Prototypes/Entities/Clothing/Belt/job.yml b/Resources/Prototypes/Entities/Clothing/Belt/job.yml index 96adc08a2e..75af2d97a9 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/job.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/job.yml @@ -138,7 +138,7 @@ - GoldenPlunger - WireBrush components: - - DVLightReplacer # DeltaV - Replaced the LightReplacerSystem. + - LightReplacer - SmokeOnTrigger - type: ItemMapper sprite: *BeltOverlay diff --git a/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml b/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml index b5709aaa51..893b9f2b9d 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml @@ -9,8 +9,8 @@ state: icon - type: Item sprite: Objects/Specific/Janitorial/light_replacer.rsi - - type: DVLightReplacer # DeltaV - Replaced the LightReplacer System. - startingContent: # DeltaV - Renamed the field more descriptive. + - type: LightReplacer + contents: - id: LightTube amount: 8 - id: LightBulb @@ -32,7 +32,7 @@ id: LightReplacerEmpty suffix: Empty components: - - type: DVLightReplacer # DeltaV - Replaced the LightReplacer System. - startingContent: # DeltaV - Renamed the field more descriptive. + - type: LightReplacer + contents: - id: LightTube amount: 0 diff --git a/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml b/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml deleted file mode 100644 index d9580df676..0000000000 --- a/Resources/Prototypes/_DV/Entities/Objects/Tools/light_replacer.yml +++ /dev/null @@ -1,16 +0,0 @@ -# The following entities are solely used for sprites in the radial menu. -- type: entity - id: EjectTubes - categories: [ HideSpawnMenu ] - components: - - type: Sprite - sprite: _DV/Objects/Specific/Janitorial/light_replacer.rsi - state: eject-tubes - -- type: entity - id: EjectBulbs - categories: [ HideSpawnMenu ] - components: - - type: Sprite - sprite: _DV/Objects/Specific/Janitorial/light_replacer.rsi - state: eject-bulbs From 8e7f9ac5f0d2c8328f4c7e49bc5e7aa602695a4d Mon Sep 17 00:00:00 2001 From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Mon, 4 May 2026 13:58:35 +0200 Subject: [PATCH 06/10] Adhere to Maintainer Requests --- .../LightReplacerMenuBoundUserInterface.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs index 5d055872f5..2ebed48855 100644 --- a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs +++ b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs @@ -39,7 +39,7 @@ public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum ui _menu.OpenCentered(); } - private IEnumerable? CreateButtons(Shared._DV.Light.Components.DVLightReplacerComponent replacer) + private List? CreateButtons(LightReplacerComponent replacer) { var options = new List(); @@ -75,7 +75,7 @@ public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum ui { var toggleLightTubes = new RadialMenuActionOption(EjectLights, replacer.ActiveLightTube) { - IconSpecifier = RadialMenuIconSpecifier.With(_ejectTubes), + IconSpecifier = RadialMenuIconSpecifier.With(replacer.EjectTubes), ToolTip = Loc.GetString("comp-light-replacer-eject-specified-lights", ("light", replacer.ActiveLightTube)), }; options.Add(toggleLightTubes); @@ -85,7 +85,7 @@ public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum ui { var toggleLightBulbs = new RadialMenuActionOption(EjectLights, replacer.ActiveLightBulb) { - IconSpecifier = RadialMenuIconSpecifier.With(_ejectBulbs), + IconSpecifier = RadialMenuIconSpecifier.With(replacer.EjectBulbs), ToolTip = Loc.GetString("comp-light-replacer-eject-specified-lights", ("light", replacer.ActiveLightBulb)), }; options.Add(toggleLightBulbs); From 6b81d286616b3c6d51bbd392282d41f97f112043 Mon Sep 17 00:00:00 2001 From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Mon, 4 May 2026 14:18:38 +0200 Subject: [PATCH 07/10] Adjust FTL lines Co-Authored-By: Tobias Berger <14962962+Toby222@users.noreply.github.com> --- .../LightReplacerMenuBoundUserInterface.cs | 10 +++++----- .../EntitySystems/SharedLightReplacerSystem.cs | 4 ++-- .../components/light-replacer-component.ftl | 16 +++++++--------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs index 2ebed48855..8d967954d9 100644 --- a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs +++ b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs @@ -76,7 +76,7 @@ public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum ui var toggleLightTubes = new RadialMenuActionOption(EjectLights, replacer.ActiveLightTube) { IconSpecifier = RadialMenuIconSpecifier.With(replacer.EjectTubes), - ToolTip = Loc.GetString("comp-light-replacer-eject-specified-lights", ("light", replacer.ActiveLightTube)), + ToolTip = Loc.GetString("comp-light-replacer-eject-specified-lights", ("light-name", replacer.ActiveLightTube)), }; options.Add(toggleLightTubes); } @@ -86,7 +86,7 @@ public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum ui var toggleLightBulbs = new RadialMenuActionOption(EjectLights, replacer.ActiveLightBulb) { IconSpecifier = RadialMenuIconSpecifier.With(replacer.EjectBulbs), - ToolTip = Loc.GetString("comp-light-replacer-eject-specified-lights", ("light", replacer.ActiveLightBulb)), + ToolTip = Loc.GetString("comp-light-replacer-eject-specified-lights", ("light-name", replacer.ActiveLightBulb)), }; options.Add(toggleLightBulbs); } @@ -105,12 +105,12 @@ public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum ui return options; } - private void PopulateOptions(string name, EntityUid uid, LightBulbType lightType, ref List options) + private void PopulateOptions(string name, EntityUid light, LightBulbType lightType, ref List options) { var switchLight = new RadialMenuActionOption<(string, LightBulbType)>(SwitchActiveLight, (name, lightType)) { - IconSpecifier = RadialMenuIconSpecifier.With(uid), - ToolTip = Loc.GetString("comp-light-replacer-select-lights", ("light", uid)), + IconSpecifier = RadialMenuIconSpecifier.With(light), + ToolTip = Loc.GetString("comp-light-replacer-select-lights", ("light-name", light)), }; options.Add(switchLight); } diff --git a/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs b/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs index 1772616ecc..72c8499cb1 100644 --- a/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs +++ b/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs @@ -74,7 +74,7 @@ public sealed class SharedLightReplacerSystem : EntitySystem // There is no Clie foreach (var (name, amount) in groups) { - args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", amount), ("name", name))); + args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing-dv", ("amount", amount), ("light-name", name))); } } } @@ -181,7 +181,7 @@ public sealed class SharedLightReplacerSystem : EntitySystem // There is no Clie && _metaDataQuery.TryComp(fixtureBulbUid, out var metaData) && metaData.EntityName == activeType) { - _popup.PopupClient(Loc.GetString("comp-light-replacer-same-light", ("light", fixtureBulbUid)), fixture, userUid, PopupType.Medium); + _popup.PopupClient(Loc.GetString("comp-light-replacer-same-light", ("light-name", fixtureBulbUid)), fixture, userUid, PopupType.Medium); return false; } } diff --git a/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl b/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl index c08e8bf0fc..8c29e123a3 100644 --- a/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl +++ b/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl @@ -2,14 +2,14 @@ ### Interaction Messages # Shown when player tries to replace light, but there is no lights left -comp-light-replacer-missing-light-dv = No {$light-name}s left in {THE($light-replacer)}. +comp-light-replacer-missing-light-dv = No {$light-name}s left in {THE($light-replacer)}. # Shown when a player attempts to replace a light with the same color & type as the active light. -comp-light-replacer-same-light = This fixture already holds a {$light}! +comp-light-replacer-same-light = This fixture already holds {INDEFINITE($light-name)} {$light-name}! # Radial Menu messages -comp-light-replacer-eject-specified-lights = Eject all {$light}s. -comp-light-replacer-select-lights = Select {$light}s. +comp-light-replacer-eject-specified-lights = Eject all {$light-name}s. +comp-light-replacer-select-lights = Select {$light-name}s. comp-light-replacer-open-empty = {CAPITALIZE(THE($light-replacer))} is completely empty! # Label @@ -18,9 +18,7 @@ comp-light-replacer-label = Tube: {$tube} ### Examine -comp-light-replacer-no-lights = It's empty. -comp-light-replacer-has-lights = It contains the following: -comp-light-replacer-light-listing = {$amount -> - [one] [color=yellow]{$amount}[/color] [color=gray]{$name}[/color] - *[other] [color=yellow]{$amount}[/color] [color=gray]{$name}s[/color] +comp-light-replacer-light-listing-dv = {$amount -> + [one] [color=yellow]{$amount}[/color] [color=gray]{$light-name}[/color] + *[other] [color=yellow]{$amount}[/color] [color=gray]{$light-name}s[/color] } From 0b6bb1baea5b93ef2e38314c35bd1ce242f71ccb Mon Sep 17 00:00:00 2001 From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Mon, 4 May 2026 14:23:36 +0200 Subject: [PATCH 08/10] Annihilate Empty Space Signed-off-by: Sir Warock <67167466+SirWarock@users.noreply.github.com> --- Content.Shared/Light/Components/LightReplacerComponent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Content.Shared/Light/Components/LightReplacerComponent.cs b/Content.Shared/Light/Components/LightReplacerComponent.cs index 4c175043ec..2d85f294c2 100644 --- a/Content.Shared/Light/Components/LightReplacerComponent.cs +++ b/Content.Shared/Light/Components/LightReplacerComponent.cs @@ -61,6 +61,5 @@ public sealed partial class LightReplacerComponent : Component /// [DataField] public SpriteSpecifier EjectBulbs = new SpriteSpecifier.Rsi(new ResPath("_DV/Objects/Specific/Janitorial/light_replacer.rsi"), "eject-bulbs"); - // End DeltaV - Predicting Light Replacers & Adding Radial Menu } From a8ba2dca9fe8f89c196655532376df728141d700 Mon Sep 17 00:00:00 2001 From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Mon, 4 May 2026 14:50:12 +0200 Subject: [PATCH 09/10] Small clean up --- .../BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs | 3 --- .../Light/EntitySystems/SharedLightReplacerSystem.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs index 8d967954d9..83ae6be276 100644 --- a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs +++ b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs @@ -15,9 +15,6 @@ public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum ui private SimpleRadialMenu? _menu; - private readonly EntProtoId _ejectTubes = "EjectTubes"; - private readonly EntProtoId _ejectBulbs = "EjectBulbs"; - protected override void Open() { base.Open(); diff --git a/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs b/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs index 72c8499cb1..964876d09b 100644 --- a/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs +++ b/Content.Shared/Light/EntitySystems/SharedLightReplacerSystem.cs @@ -181,7 +181,7 @@ public sealed class SharedLightReplacerSystem : EntitySystem // There is no Clie && _metaDataQuery.TryComp(fixtureBulbUid, out var metaData) && metaData.EntityName == activeType) { - _popup.PopupClient(Loc.GetString("comp-light-replacer-same-light", ("light-name", fixtureBulbUid)), fixture, userUid, PopupType.Medium); + _popup.PopupClient(Loc.GetString("comp-light-replacer-same-light", ("light-name", fixtureBulbUid)), fixture, userUid); return false; } } From 76f2a66b7c51a6a0f6f7c7d2535bc8671f369149 Mon Sep 17 00:00:00 2001 From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Mon, 4 May 2026 17:15:51 +0200 Subject: [PATCH 10/10] There we go --- .../LightReplacerMenuBoundUserInterface.cs | 2 +- .../_DV/light/components/light-replacer-component.ftl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs index 83ae6be276..3c77fcbb5f 100644 --- a/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs +++ b/Content.Client/_DV/Light/BoundUserInterfaces/LightReplacerMenuBoundUserInterface.cs @@ -107,7 +107,7 @@ public sealed class LightReplacerMenuBoundUserInterface(EntityUid owner, Enum ui var switchLight = new RadialMenuActionOption<(string, LightBulbType)>(SwitchActiveLight, (name, lightType)) { IconSpecifier = RadialMenuIconSpecifier.With(light), - ToolTip = Loc.GetString("comp-light-replacer-select-lights", ("light-name", light)), + ToolTip = Loc.GetString("comp-light-replacer-select-lights", ("light-name", name)), }; options.Add(switchLight); } diff --git a/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl b/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl index 8c29e123a3..8a8cae8287 100644 --- a/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl +++ b/Resources/Locale/en-US/_DV/light/components/light-replacer-component.ftl @@ -2,14 +2,14 @@ ### Interaction Messages # Shown when player tries to replace light, but there is no lights left -comp-light-replacer-missing-light-dv = No {$light-name}s left in {THE($light-replacer)}. +comp-light-replacer-missing-light-dv = No {MAKEPLURAL($light-name)} left in {THE($light-replacer)}. # Shown when a player attempts to replace a light with the same color & type as the active light. comp-light-replacer-same-light = This fixture already holds {INDEFINITE($light-name)} {$light-name}! # Radial Menu messages -comp-light-replacer-eject-specified-lights = Eject all {$light-name}s. -comp-light-replacer-select-lights = Select {$light-name}s. +comp-light-replacer-eject-specified-lights = Eject all {MAKEPLURAL($light-name)}. +comp-light-replacer-select-lights = Select {MAKEPLURAL($light-name)}. comp-light-replacer-open-empty = {CAPITALIZE(THE($light-replacer))} is completely empty! # Label @@ -20,5 +20,5 @@ comp-light-replacer-label = Tube: {$tube} comp-light-replacer-light-listing-dv = {$amount -> [one] [color=yellow]{$amount}[/color] [color=gray]{$light-name}[/color] - *[other] [color=yellow]{$amount}[/color] [color=gray]{$light-name}s[/color] + *[other] [color=yellow]{$amount}[/color] [color=gray]{MAKEPLURAL($light-name)}[/color] }