CatStation/Content.Shared/Light/EntitySystems/LightReplacerSystem.cs
Sir Warock e6b3b3a441
Predict Light Replacers & Add Radial Menu (#40636)
* Predict Light Replacers

* Public once more

* Light changes

* Introduce an OrderList.

* Added Radial Menu

* Fix missing Dirty()

* Introduce EntityProvider

* Migratiooon!

* Adress Reviews

* Forgot Comments

* Test-Fail Fix

* Fixes

* Update buttons

* Revert "Update buttons"

This reverts commit f84d8f2502600905a2ab4bce5463af6f2b4b1c3e.

* Introduce Dedicated Short String

* review

* Another review has hit the PR

Co-Authored-By: Whatstone <166147148+whatston3@users.noreply.github.com>

* Adressing reviews

Co-Authored-By: Whatstone <166147148+whatston3@users.noreply.github.com>

* Update LightReplacerMenuBoundUserInterface.cs

* refactor: do not pass ref type by ref, try not to create impure methods where it is not required

* refactor: random xml-doc changes

* refactor: more xml-doc, and 1 call per line convention

* Small fixes

* Iterate through the container

* updated xml docs

* IReadOnlyfix

* refactor: renaming, whitespaces, use deconstructor in foreach, use Enumerable instead of creating additional list when its possible that it will be empty and wont be modified

* refactor: fix collction cloning to not throw during modificaiton

* Replace popups with sounds.

* Added singular sounds

* refactor: fix error on moving items from one EntityProvider to another and back when they have some spawned entities in container

* Replaced Popup with Sound

---------

Co-authored-by: Pok245 <113675512+Pok27@users.noreply.github.com>
Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
Co-authored-by: Fildrance <fildrance@gmail.com>
2026-08-18 10:21:46 +00:00

184 lines
7.2 KiB
C#

using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Light.Components;
using Content.Shared.Light.Events;
using Content.Shared.Popups;
using Content.Shared.Storage.EntitySystems;
using Content.Shared.Storage.Events;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Serialization;
namespace Content.Shared.Light.EntitySystems;
/// <summary>
/// System that handles light replacer tool (which picks proper light entity for socket
/// and replaces it with other one, hopefully working one!).
/// </summary>
public sealed partial class LightReplacerSystem : EntitySystem
{
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private EntityProviderSystem _provider = default!;
[Dependency] private SharedPopupSystem _popup = default!;
[Dependency] private SharedPoweredLightSystem _poweredLight = default!;
[Dependency] private SharedUserInterfaceSystem _ui = default!;
[Dependency] private EntityQuery<LightBulbComponent> _lightBulbQuery = default!;
/// <summary> Adds contents info into examine. </summary>
[SubscribeLocalEvent]
private void OnExamined(Entity<LightReplacerComponent> replacer, ref ExaminedEvent args)
{
if (!_provider.TryGetEntityCounter(replacer.Owner, out var entities))
return;
using (args.PushGroup(nameof(LightReplacerComponent)))
{
if (entities.Count == 0)
{
args.PushMarkup(Loc.GetString("comp-light-replacer-no-lights"));
return;
}
args.PushMarkup(Loc.GetString("comp-light-replacer-has-lights"));
foreach (var bulb in entities)
{
if (!ProtoMan.Resolve(bulb.Key, out var bulbPrototype))
continue;
args.PushMarkup(Loc.GetString("comp-light-replacer-light-listing", ("amount", bulb.Value), ("name", bulbPrototype.Name)));
}
}
}
/// <summary> Attempts to open UI for replacer, if there are any viable options to put into it. </summary>
[SubscribeLocalEvent]
private void OnUse(Entity<LightReplacerComponent> replacer, ref UseInHandEvent args)
{
if (args.Handled || !_provider.TryGetEntityCounter(replacer.Owner, out var entities))
return;
args.ApplyDelay = false;
if (entities.Count == 0)
{
_popup.PopupEntity(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);
}
/// <summary> Tries to replace bulb/tube if applicable.</summary>
[SubscribeLocalEvent]
private void OnAfterInteract(Entity<LightReplacerComponent> 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<PoweredLightComponent>(targetUid, out var fixture))
eventArgs.Handled = TryReplaceBulb(replacer.AsNullable(), (targetUid, fixture), eventArgs.User);
}
[SubscribeLocalEvent]
private void OnEjectMessage(Entity<LightReplacerComponent> replacer, ref EjectLightTypeMessage args)
{
_provider.TryEjectEntities(replacer.Owner, args.LightEntProtoId, out _, user: args.Actor);
}
/// <summary> Attempts to switch currently selected bulb type according to request. </summary>
[SubscribeLocalEvent]
private void OnSwitchMessage(Entity<LightReplacerComponent> replacer, ref SwitchLightTypeMessage args)
{
if (!ProtoMan.Resolve(args.LightEntProtoId, out var prototype)
|| !_provider.TryGetEntityCounter(replacer.Owner, out var entities)
|| !entities.TryGetValue(args.LightEntProtoId, out var amount)
|| amount <= 0)
return;
if (args.LightType == LightBulbType.Tube)
replacer.Comp.ActiveLightTube = args.LightEntProtoId;
else
replacer.Comp.ActiveLightBulb = args.LightEntProtoId;
Dirty(replacer);
_audio.PlayPredicted(replacer.Comp.Sound, replacer, args.Actor);
}
[SubscribeLocalEvent]
private void OnLightProviderInsertedCheck(Entity<LightBulbComponent> bulb, ref EntityProviderInsertCheckEvent args)
{
if (bulb.Comp.State == LightBulbState.Broken)
args.FailureMessage = Loc.GetString("comp-light-replacer-insert-broken-light");
}
/// <summary>
/// Try to replace a light bulb in <paramref name="lightHolder"/>
/// using light replacer. Light fixture should have <see cref="PoweredLightComponent"/>.
/// </summary>
/// <param name="replacer">The light replacer used to replace the bulb.</param>
/// <param name="lightHolder">The fixture whose light is being replaced.</param>
/// <param name="userUid">The user who is replacing the light.</param>
/// <returns>True if successfully replaced light, false otherwise</returns>
public bool TryReplaceBulb(Entity<LightReplacerComponent?> replacer, Entity<PoweredLightComponent?> lightHolder, EntityUid? userUid = null)
{
if (!Resolve(replacer, ref replacer.Comp)
|| !Resolve(lightHolder, ref lightHolder.Comp))
return false;
var activeType = lightHolder.Comp.BulbType == LightBulbType.Tube
? replacer.Comp.ActiveLightTube
: replacer.Comp.ActiveLightBulb;
// check if light bulb is broken or missing
EntityUid? currentBulbInHolder = _poweredLight.GetBulb(lightHolder, lightHolder.Comp);
if (currentBulbInHolder != null)
{
if (!_lightBulbQuery.TryComp(currentBulbInHolder.Value, out var fixtureBulb))
return false;
var prototype = MetaData(currentBulbInHolder.Value).EntityPrototype;
if (fixtureBulb.State == LightBulbState.Normal && prototype != null && prototype.ID == activeType)
{
_popup.PopupEntity(Loc.GetString("comp-light-replacer-same-light", ("light", currentBulbInHolder)), lightHolder, userUid, PopupType.Medium);
return false;
}
}
if (!_provider.TryGetEntity(replacer.Owner, activeType, out var insertedBulb))
{
if (userUid == null || !ProtoMan.Resolve(activeType, out var bulbPrototype))
return false;
var msg = Loc.GetString("comp-light-replacer-missing-light",
("light-name", bulbPrototype.Name),
("light-replacer", replacer));
_popup.PopupEntity(msg, replacer, userUid.Value);
return false;
}
// insert it into fixture
var wasReplaced = _poweredLight.ReplaceBulb(lightHolder, insertedBulb.Value, lightHolder.Comp);
if (wasReplaced)
{
_audio.PlayPredicted(replacer.Comp.Sound, replacer, userUid);
}
return wasReplaced;
}
}
[Serializable, NetSerializable]
public enum LightReplacerUiKey : byte
{
Key,
}