| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- using Content.Client.Message;
- using Content.Client.Pinpointer.UI;
- using Content.Client.Stylesheets;
- using Content.Client.UserInterface.Controls;
- using Content.Shared.Atmos.Components;
- using Content.Shared.Atmos.Monitor;
- using Content.Shared.Pinpointer;
- using Robust.Client.AutoGenerated;
- using Robust.Client.GameObjects;
- using Robust.Client.UserInterface;
- using Robust.Client.UserInterface.Controls;
- using Robust.Client.UserInterface.XAML;
- using Robust.Shared.Map;
- using Robust.Shared.Timing;
- using Robust.Shared.Utility;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- namespace Content.Client.Atmos.Consoles;
- [GenerateTypedNameReferences]
- public sealed partial class AtmosAlertsComputerWindow : FancyWindow
- {
- private readonly IEntityManager _entManager;
- private readonly SpriteSystem _spriteSystem;
- private readonly SharedNavMapSystem _navMapSystem;
- private EntityUid? _owner;
- private NetEntity? _trackedEntity;
- private AtmosAlertsComputerEntry[]? _airAlarms = null;
- private AtmosAlertsComputerEntry[]? _fireAlarms = null;
- private IEnumerable<AtmosAlertsComputerEntry>? _allAlarms = null;
- private IEnumerable<AtmosAlertsComputerEntry>? _activeAlarms = null;
- private Dictionary<NetEntity, float> _deviceSilencingProgress = new();
- public event Action<NetEntity?>? SendFocusChangeMessageAction;
- public event Action<NetEntity, bool>? SendDeviceSilencedMessageAction;
- private bool _autoScrollActive = false;
- private bool _autoScrollAwaitsUpdate = false;
- private const float SilencingDuration = 2.5f;
- // Colors
- private Color _wallColor = new Color(64, 64, 64);
- private Color _tileColor = new Color(28, 28, 28);
- private Color _monitorBlipColor = Color.Cyan;
- private Color _untrackedEntColor = Color.DimGray;
- private Color _regionBaseColor = new Color(154, 154, 154);
- private Color _inactiveColor = StyleNano.DisabledFore;
- private Color _statusTextColor = StyleNano.GoodGreenFore;
- private Color _goodColor = Color.LimeGreen;
- private Color _warningColor = new Color(255, 182, 72);
- private Color _dangerColor = new Color(255, 67, 67);
- public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInterface, EntityUid? owner)
- {
- RobustXamlLoader.Load(this);
- _entManager = IoCManager.Resolve<IEntityManager>();
- _spriteSystem = _entManager.System<SpriteSystem>();
- _navMapSystem = _entManager.System<SharedNavMapSystem>();
- // Pass the owner to nav map
- _owner = owner;
- NavMap.Owner = _owner;
- // Set nav map colors
- NavMap.WallColor = _wallColor;
- NavMap.TileColor = _tileColor;
- // Set nav map grid uid
- var stationName = Loc.GetString("atmos-alerts-window-unknown-location");
- if (_entManager.TryGetComponent<TransformComponent>(owner, out var xform))
- {
- NavMap.MapUid = xform.GridUid;
- // Assign station name
- if (_entManager.TryGetComponent<MetaDataComponent>(xform.GridUid, out var stationMetaData))
- stationName = stationMetaData.EntityName;
- var msg = new FormattedMessage();
- msg.TryAddMarkup(Loc.GetString("atmos-alerts-window-station-name", ("stationName", stationName)), out _);
- StationName.SetMessage(msg);
- }
- else
- {
- StationName.SetMessage(stationName);
- NavMap.Visible = false;
- }
- // Set trackable entity selected action
- NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
- // Update nav map
- NavMap.ForceNavMapUpdate();
- // Set tab container headers
- MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts"));
- MasterTabContainer.SetTabTitle(1, Loc.GetString("atmos-alerts-window-tab-air-alarms"));
- MasterTabContainer.SetTabTitle(2, Loc.GetString("atmos-alerts-window-tab-fire-alarms"));
- // Set UI toggles
- ShowInactiveAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowInactiveAlarms, AtmosAlarmType.Invalid);
- ShowNormalAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowNormalAlarms, AtmosAlarmType.Normal);
- ShowWarningAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowWarningAlarms, AtmosAlarmType.Warning);
- ShowDangerAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowDangerAlarms, AtmosAlarmType.Danger);
- // Set atmos monitoring message action
- SendFocusChangeMessageAction += userInterface.SendFocusChangeMessage;
- SendDeviceSilencedMessageAction += userInterface.SendDeviceSilencedMessage;
- }
- #region Toggle handling
- private void OnShowAlarmsToggled(CheckBox toggle, AtmosAlarmType toggledAlarmState)
- {
- if (_owner == null)
- return;
- if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner.Value, out var console))
- return;
- foreach (var device in console.AtmosDevices)
- {
- var alarmState = GetAlarmState(device.NetEntity);
- if (toggledAlarmState != alarmState)
- continue;
- if (toggle.Pressed)
- AddTrackedEntityToNavMap(device, alarmState);
- else
- NavMap.TrackedEntities.Remove(device.NetEntity);
- }
- }
- private void OnSilenceAlertsToggled(NetEntity netEntity, bool toggleState)
- {
- if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner, out var console))
- return;
- if (toggleState)
- _deviceSilencingProgress[netEntity] = SilencingDuration;
- else
- _deviceSilencingProgress.Remove(netEntity);
- foreach (AtmosAlarmEntryContainer entryContainer in AlertsTable.Children)
- {
- if (entryContainer.NetEntity == netEntity)
- entryContainer.SilenceAlarmProgressBar.Visible = toggleState;
- }
- SendDeviceSilencedMessageAction?.Invoke(netEntity, toggleState);
- }
- #endregion
- public void UpdateUI(EntityCoordinates? consoleCoords, AtmosAlertsComputerEntry[] airAlarms, AtmosAlertsComputerEntry[] fireAlarms, AtmosAlertsFocusDeviceData? focusData)
- {
- if (_owner == null)
- return;
- if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner.Value, out var console))
- return;
- if (_trackedEntity != focusData?.NetEntity)
- {
- SendFocusChangeMessageAction?.Invoke(_trackedEntity);
- focusData = null;
- }
- // Retain alarm data for use inbetween updates
- _airAlarms = airAlarms;
- _fireAlarms = fireAlarms;
- _allAlarms = airAlarms.Concat(fireAlarms);
- var silenced = console.SilencedDevices;
- _activeAlarms = _allAlarms.Where(x => x.AlarmState > AtmosAlarmType.Normal &&
- (!silenced.Contains(x.NetEntity) || _deviceSilencingProgress.ContainsKey(x.NetEntity)));
- // Reset nav map data
- NavMap.TrackedCoordinates.Clear();
- NavMap.TrackedEntities.Clear();
- // Add tracked entities to the nav map
- foreach (var device in console.AtmosDevices)
- {
- if (!device.NetEntity.Valid)
- continue;
- if (!NavMap.Visible)
- continue;
- var alarmState = GetAlarmState(device.NetEntity);
- if (_trackedEntity != device.NetEntity)
- {
- // Skip air alarms if the appropriate overlay is off
- if (!ShowInactiveAlarms.Pressed && alarmState == AtmosAlarmType.Invalid)
- continue;
- if (!ShowNormalAlarms.Pressed && alarmState == AtmosAlarmType.Normal)
- continue;
- if (!ShowWarningAlarms.Pressed && alarmState == AtmosAlarmType.Warning)
- continue;
- if (!ShowDangerAlarms.Pressed && alarmState == AtmosAlarmType.Danger)
- continue;
- }
- AddTrackedEntityToNavMap(device, alarmState);
- }
- // Show the monitor location
- var consoleUid = _entManager.GetNetEntity(_owner);
- if (consoleCoords != null && consoleUid != null)
- {
- var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
- var blip = new NavMapBlip(consoleCoords.Value, texture, _monitorBlipColor, true, false);
- NavMap.TrackedEntities[consoleUid.Value] = blip;
- }
- // Update the nav map
- NavMap.ForceNavMapUpdate();
- // Clear excess children from the tables
- var activeAlarmCount = _activeAlarms.Count();
- while (AlertsTable.ChildCount > activeAlarmCount)
- AlertsTable.RemoveChild(AlertsTable.GetChild(AlertsTable.ChildCount - 1));
- while (AirAlarmsTable.ChildCount > airAlarms.Length)
- AirAlarmsTable.RemoveChild(AirAlarmsTable.GetChild(AirAlarmsTable.ChildCount - 1));
- while (FireAlarmsTable.ChildCount > fireAlarms.Length)
- FireAlarmsTable.RemoveChild(FireAlarmsTable.GetChild(FireAlarmsTable.ChildCount - 1));
- // Update all entries in each table
- for (int index = 0; index < _activeAlarms.Count(); index++)
- {
- var entry = _activeAlarms.ElementAt(index);
- UpdateUIEntry(entry, index, AlertsTable, console, focusData);
- }
- for (int index = 0; index < airAlarms.Count(); index++)
- {
- var entry = airAlarms.ElementAt(index);
- UpdateUIEntry(entry, index, AirAlarmsTable, console, focusData);
- }
- for (int index = 0; index < fireAlarms.Count(); index++)
- {
- var entry = fireAlarms.ElementAt(index);
- UpdateUIEntry(entry, index, FireAlarmsTable, console, focusData);
- }
- // If no alerts are active, display a message
- if (MasterTabContainer.CurrentTab == 0 && activeAlarmCount == 0)
- {
- var label = new RichTextLabel()
- {
- HorizontalExpand = true,
- VerticalExpand = true,
- HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Center,
- };
- label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", _statusTextColor.ToHexNoAlpha())));
- AlertsTable.AddChild(label);
- }
- // Update the alerts tab with the number of active alerts
- if (activeAlarmCount == 0)
- MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts"));
- else
- MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount)));
- // Update sensor regions
- NavMap.RegionOverlays.Clear();
- var prioritizedRegionOverlays = new Dictionary<NavMapRegionOverlay, int>();
- if (_owner != null &&
- _entManager.TryGetComponent<TransformComponent>(_owner, out var xform) &&
- _entManager.TryGetComponent<NavMapComponent>(xform.GridUid, out var navMap))
- {
- var regionOverlays = _navMapSystem.GetNavMapRegionOverlays(_owner.Value, navMap, AtmosAlertsComputerUiKey.Key);
- foreach (var (regionOwner, regionOverlay) in regionOverlays)
- {
- var alarmState = GetAlarmState(regionOwner);
- if (!TryGetSensorRegionColor(regionOwner, alarmState, out var regionColor))
- continue;
- regionOverlay.Color = regionColor;
- var priority = (_trackedEntity == regionOwner) ? 999 : (int)alarmState;
- prioritizedRegionOverlays.Add(regionOverlay, priority);
- }
- // Sort overlays according to their priority
- var sortedOverlays = prioritizedRegionOverlays.OrderBy(x => x.Value).Select(x => x.Key).ToList();
- NavMap.RegionOverlays = sortedOverlays;
- }
- // Auto-scroll re-enable
- if (_autoScrollAwaitsUpdate)
- {
- _autoScrollActive = true;
- _autoScrollAwaitsUpdate = false;
- }
- }
- private void AddTrackedEntityToNavMap(AtmosAlertsDeviceNavMapData metaData, AtmosAlarmType alarmState)
- {
- var data = GetBlipTexture(alarmState);
- if (data == null)
- return;
- var texture = data.Value.Item1;
- var color = data.Value.Item2;
- var coords = _entManager.GetCoordinates(metaData.NetCoordinates);
- if (_trackedEntity != null && _trackedEntity != metaData.NetEntity)
- color *= _untrackedEntColor;
- var selectable = true;
- var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color, _trackedEntity == metaData.NetEntity, selectable);
- NavMap.TrackedEntities[metaData.NetEntity] = blip;
- }
- private bool TryGetSensorRegionColor(NetEntity regionOwner, AtmosAlarmType alarmState, out Color color)
- {
- color = Color.White;
- var blip = GetBlipTexture(alarmState);
- if (blip == null)
- return false;
- // Color the region based on alarm state and entity tracking
- color = blip.Value.Item2 * _regionBaseColor;
- if (_trackedEntity != null && _trackedEntity != regionOwner)
- color *= _untrackedEntColor;
- return true;
- }
- private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null)
- {
- // Make new UI entry if required
- if (index >= table.ChildCount)
- {
- var newEntryContainer = new AtmosAlarmEntryContainer(entry.NetEntity, _entManager.GetCoordinates(entry.Coordinates));
- // On click
- newEntryContainer.FocusButton.OnButtonUp += args =>
- {
- if (_trackedEntity == newEntryContainer.NetEntity)
- {
- _trackedEntity = null;
- }
- else
- {
- _trackedEntity = newEntryContainer.NetEntity;
- if (newEntryContainer.Coordinates != null)
- NavMap.CenterToCoordinates(newEntryContainer.Coordinates.Value);
- }
- // Send message to console that the focus has changed
- SendFocusChangeMessageAction?.Invoke(_trackedEntity);
- // Update affected UI elements across all tables
- UpdateConsoleTable(console, AlertsTable, _trackedEntity);
- UpdateConsoleTable(console, AirAlarmsTable, _trackedEntity);
- UpdateConsoleTable(console, FireAlarmsTable, _trackedEntity);
- };
- // On toggling the silence check box
- newEntryContainer.SilenceCheckBox.OnToggled += _ => OnSilenceAlertsToggled(newEntryContainer.NetEntity, newEntryContainer.SilenceCheckBox.Pressed);
- // Add the entry to the current table
- table.AddChild(newEntryContainer);
- }
- // Update values and UI elements
- var tableChild = table.GetChild(index);
- if (tableChild is not AtmosAlarmEntryContainer)
- {
- table.RemoveChild(tableChild);
- UpdateUIEntry(entry, index, table, console, focusData);
- return;
- }
- var entryContainer = (AtmosAlarmEntryContainer)tableChild;
- entryContainer.UpdateEntry(entry, entry.NetEntity == _trackedEntity, focusData);
- if (_trackedEntity != entry.NetEntity)
- {
- var silenced = console.SilencedDevices;
- entryContainer.SilenceCheckBox.Pressed = (silenced.Contains(entry.NetEntity) || _deviceSilencingProgress.ContainsKey(entry.NetEntity));
- }
- entryContainer.SilenceAlarmProgressBar.Visible = (table == AlertsTable && _deviceSilencingProgress.ContainsKey(entry.NetEntity));
- }
- private void UpdateConsoleTable(AtmosAlertsComputerComponent console, Control table, NetEntity? currTrackedEntity)
- {
- foreach (var tableChild in table.Children)
- {
- if (tableChild is not AtmosAlarmEntryContainer)
- continue;
- var entryContainer = (AtmosAlarmEntryContainer)tableChild;
- if (entryContainer.NetEntity != currTrackedEntity)
- entryContainer.RemoveAsFocus();
- else if (entryContainer.NetEntity == currTrackedEntity)
- entryContainer.SetAsFocus();
- }
- }
- private void SetTrackedEntityFromNavMap(NetEntity? netEntity)
- {
- if (netEntity == null)
- return;
- if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner, out var console))
- return;
- _trackedEntity = netEntity;
- if (netEntity != null)
- {
- // Tab switching
- if (MasterTabContainer.CurrentTab != 0 || _activeAlarms?.Any(x => x.NetEntity == netEntity) == false)
- {
- var device = console.AtmosDevices.FirstOrNull(x => x.NetEntity == netEntity);
- switch (device?.Group)
- {
- case AtmosAlertsComputerGroup.AirAlarm:
- MasterTabContainer.CurrentTab = 1; break;
- case AtmosAlertsComputerGroup.FireAlarm:
- MasterTabContainer.CurrentTab = 2; break;
- }
- }
- // Get the scroll position of the selected entity on the selected button the UI
- ActivateAutoScrollToFocus();
- }
- // Send message to console that the focus has changed
- SendFocusChangeMessageAction?.Invoke(_trackedEntity);
- }
- protected override void FrameUpdate(FrameEventArgs args)
- {
- AutoScrollToFocus();
- // Device silencing update
- foreach ((var device, var remainingTime) in _deviceSilencingProgress)
- {
- var t = remainingTime - args.DeltaSeconds;
- if (t <= 0)
- {
- _deviceSilencingProgress.Remove(device);
- if (device == _trackedEntity)
- _trackedEntity = null;
- }
- else
- _deviceSilencingProgress[device] = t;
- }
- }
- private void ActivateAutoScrollToFocus()
- {
- _autoScrollActive = false;
- _autoScrollAwaitsUpdate = true;
- }
- private void AutoScrollToFocus()
- {
- if (!_autoScrollActive)
- return;
- var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
- if (scroll == null)
- return;
- if (!TryGetNextScrollPosition(out float? nextScrollPosition))
- return;
- scroll.VScrollTarget = nextScrollPosition.Value;
- if (MathHelper.CloseToPercent(scroll.VScroll, scroll.VScrollTarget))
- _autoScrollActive = false;
- }
- private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
- {
- nextScrollPosition = null;
- var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
- if (scroll == null)
- return false;
- var container = scroll.Children.ElementAt(0) as BoxContainer;
- if (container == null || container.Children.Count() == 0)
- return false;
- // Exit if the heights of the children haven't been initialized yet
- if (!container.Children.Any(x => x.Height > 0))
- return false;
- nextScrollPosition = 0;
- foreach (var control in container.Children)
- {
- if (control == null || control is not AtmosAlarmEntryContainer)
- continue;
- if (((AtmosAlarmEntryContainer)control).NetEntity == _trackedEntity)
- return true;
- nextScrollPosition += control.Height;
- }
- // Failed to find control
- nextScrollPosition = null;
- return false;
- }
- private AtmosAlarmType GetAlarmState(NetEntity netEntity)
- {
- var alarmState = _allAlarms?.FirstOrNull(x => x.NetEntity == netEntity)?.AlarmState;
- if (alarmState == null)
- return AtmosAlarmType.Invalid;
- return alarmState.Value;
- }
- private (SpriteSpecifier.Texture, Color)? GetBlipTexture(AtmosAlarmType alarmState)
- {
- (SpriteSpecifier.Texture, Color)? output = null;
- switch (alarmState)
- {
- case AtmosAlarmType.Invalid:
- output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), _inactiveColor); break;
- case AtmosAlarmType.Normal:
- output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), _goodColor); break;
- case AtmosAlarmType.Warning:
- output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), _warningColor); break;
- case AtmosAlarmType.Danger:
- output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), _dangerColor); break;
- }
- return output;
- }
- }
|