using Content.Server.AlertLevel; using Content.Server.Audio; using Content.Server.Light.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Station.Systems; using Content.Shared.Examine; using Content.Shared.Light; using Content.Shared.Light.Components; using Content.Shared.Power; using Content.Shared.Station.Components; using Robust.Server.Audio; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Color = Robust.Shared.Maths.Color; namespace Content.Server.Light.EntitySystems; public sealed class EmergencyLightSystem : SharedEmergencyLightSystem { [Dependency] private readonly AmbientSoundSystem _ambient = default!; [Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly PointLightSystem _pointLight = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly AudioSystem _audioSystem = default!; // White Dream [Dependency] private readonly IGameTiming _timing = default!; // White Dream public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); // WD EDIT SubscribeLocalEvent(OnEmergencyLightEvent); SubscribeLocalEvent(OnAlertLevelChanged); SubscribeLocalEvent(OnEmergencyExamine); SubscribeLocalEvent(OnEmergencyPower); } private void OnMapInit(Entity entity, ref MapInitEvent args) => UpdateState(entity); // WD EDIT private void OnEmergencyPower(Entity entity, ref PowerChangedEvent args) { var meta = MetaData(entity.Owner); // TODO: PowerChangedEvent shouldn't be issued for paused ents but this is the world we live in. if (meta.EntityLifeStage >= EntityLifeStage.Terminating || meta.EntityPaused) { return; } UpdateState(entity); } private void OnEmergencyExamine(EntityUid uid, EmergencyLightComponent component, ExaminedEvent args) { using (args.PushGroup(nameof(EmergencyLightComponent))) { args.PushMarkup( Loc.GetString("emergency-light-component-on-examine", ("batteryStateText", Loc.GetString(component.BatteryStateText[component.State])))); // Show alert level on the light itself. if (!TryComp(_station.GetOwningStation(uid), out var alerts)) return; if (alerts.AlertLevels == null) return; var name = alerts.CurrentLevel; var color = Color.White; if (alerts.AlertLevels.Levels.TryGetValue(alerts.CurrentLevel, out var details)) color = details.Color; args.PushMarkup( Loc.GetString("emergency-light-component-on-examine-alert", ("color", color.ToHex()), ("level", Loc.GetString($"alert-level-{name.ToString().ToLower()}")))); } } private void OnEmergencyLightEvent(EntityUid uid, EmergencyLightComponent component, EmergencyLightEvent args) { switch (args.State) { case EmergencyLightState.On: case EmergencyLightState.Charging: case EmergencyLightState.Full: // White Dream moved for audio EnsureComp(uid); break; case EmergencyLightState.Empty: RemComp(uid); break; default: throw new ArgumentOutOfRangeException(); } } private void OnAlertLevelChanged(AlertLevelChangedEvent ev) { if (!TryComp(ev.Station, out var alert)) return; if (alert.AlertLevels == null || !alert.AlertLevels.Levels.TryGetValue(ev.AlertLevel, out var details)) return; var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var light, out var pointLight, out var appearance, out var xform)) { if (CompOrNull(xform.GridUid)?.Station != ev.Station) continue; _pointLight.SetColor(uid, details.EmergencyLightColor, pointLight); _appearance.SetData(uid, EmergencyLightVisuals.Color, details.EmergencyLightColor, appearance); UpdateAlarmSound((uid, light), details); // White Dream if (details.ForceEnableEmergencyLights && !light.ForciblyEnabled) { light.ForciblyEnabled = true; TurnOn((uid, light)); } else if (!details.ForceEnableEmergencyLights && light.ForciblyEnabled) { // Previously forcibly enabled, and we went down an alert level. light.ForciblyEnabled = false; UpdateState((uid, light)); } } } public void SetState(EntityUid uid, EmergencyLightComponent component, EmergencyLightState state) { if (component.State == state) return; component.State = state; RaiseLocalEvent(uid, new EmergencyLightEvent(state)); } // White Dream edit start public override void Update(float frameTime) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out _, out var emergencyLight, out var battery)) { if (emergencyLight.State == EmergencyLightState.On) { if (!_battery.TryUseCharge(uid, emergencyLight.Wattage * frameTime, battery)) { SetState(uid, emergencyLight, EmergencyLightState.Empty); TurnOff((uid, emergencyLight)); } } else { _battery.SetCharge( uid, battery.CurrentCharge + emergencyLight.ChargingWattage * frameTime * emergencyLight.ChargingEfficiency, battery); if (_battery.IsFull(uid, battery)) { if (TryComp(uid, out var receiver)) { receiver.Load = 1; } SetState(uid, emergencyLight, EmergencyLightState.Full); } } // Audio Alarm if (emergencyLight.AlarmNextSound >= _timing.CurTime || emergencyLight.AlarmSound == null) continue; _audioSystem.PlayEntity(emergencyLight.AlarmSound, Filter.Pvs(uid, 0.5f), uid, true); emergencyLight.AlarmNextSound = _timing.CurTime.Add(emergencyLight.AlarmInterval); } } // White Dream edit end /// /// Updates the light's power drain, battery drain, sprite and actual light state. /// public void UpdateState(Entity entity) { if (!TryComp(entity.Owner, out var receiver)) return; if (!TryComp(_station.GetOwningStation(entity.Owner), out var alerts)) return; if (alerts.AlertLevels == null || !alerts.AlertLevels.Levels.TryGetValue(alerts.CurrentLevel, out var details)) { TurnOff(entity, Color.Red); // if no alert, default to off red state return; } if (receiver.Powered && !entity.Comp.ForciblyEnabled) // Green alert { receiver.Load = (int) Math.Abs(entity.Comp.Wattage); TurnOff(entity, details.Color); SetState(entity.Owner, entity.Comp, EmergencyLightState.Charging); } else if (!receiver.Powered) // If internal battery runs out { // WD EDIT START if (!entity.Comp.ForciblyEnabled) TurnOn(entity, Color.Red); // WD EDIT END SetState(entity.Owner, entity.Comp, EmergencyLightState.On); } else // Powered and enabled { TurnOn(entity, details.Color); SetState(entity.Owner, entity.Comp, EmergencyLightState.On); UpdateAlarmSound(entity, details); // White Dream } } private void TurnOff(Entity entity) { _pointLight.SetEnabled(entity.Owner, false); _appearance.SetData(entity.Owner, EmergencyLightVisuals.On, false); _ambient.SetAmbience(entity.Owner, false); } /// /// Turn off emergency light and set color. /// private void TurnOff(Entity entity, Color color) { _pointLight.SetEnabled(entity.Owner, false); _pointLight.SetColor(entity.Owner, color); _appearance.SetData(entity.Owner, EmergencyLightVisuals.Color, color); _appearance.SetData(entity.Owner, EmergencyLightVisuals.On, false); _ambient.SetAmbience(entity.Owner, false); } private void TurnOn(Entity entity) { _pointLight.SetEnabled(entity.Owner, true); _appearance.SetData(entity.Owner, EmergencyLightVisuals.On, true); _ambient.SetAmbience(entity.Owner, true); } /// /// Turn on emergency light and set color. /// private void TurnOn(Entity entity, Color color) { _pointLight.SetEnabled(entity.Owner, true); _pointLight.SetColor(entity.Owner, color); _appearance.SetData(entity.Owner, EmergencyLightVisuals.Color, color); _appearance.SetData(entity.Owner, EmergencyLightVisuals.On, true); _ambient.SetAmbience(entity.Owner, true); } // White Dream edit start - Audio Alert by Alert Level private void UpdateAlarmSound(Entity entity, AlertLevelDetail alertLevel) { if (alertLevel.AlarmSound == null) { entity.Comp.AlarmSound = null; return; } entity.Comp.AlarmSound = alertLevel.AlarmSound; entity.Comp.AlarmInterval = alertLevel.AlarmInterval; if (entity.Comp.AlarmInterval < TimeSpan.FromSeconds(1f)) // Safeguard against spam and client crash entity.Comp.AlarmInterval = TimeSpan.FromSeconds(1f); entity.Comp.AlarmNextSound = _timing.CurTime.Add(entity.Comp.AlarmInterval); } // White Dream edit end }