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 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-dv", ("amount", amount), ("light-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-name", fixtureBulbUid)), fixture, userUid); 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, }