| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- using System.Linq;
- using System.Numerics;
- using Content.Client.Shuttles.Systems;
- using Content.Shared.Shuttles.BUIStates;
- using Content.Shared.Shuttles.Components;
- using Content.Shared.Shuttles.Systems;
- using Content.Shared.Shuttles.UI.MapObjects;
- using Content.Shared.Timing;
- using Robust.Client.AutoGenerated;
- using Robust.Client.Graphics;
- using Robust.Client.UserInterface;
- using Robust.Client.UserInterface.Controls;
- using Robust.Client.UserInterface.XAML;
- using Robust.Shared.Audio;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Map;
- using Robust.Shared.Map.Components;
- using Robust.Shared.Physics.Components;
- using Robust.Shared.Player;
- using Robust.Shared.Random;
- using Robust.Shared.Timing;
- using Robust.Shared.Utility;
- namespace Content.Client.Shuttles.UI;
- [GenerateTypedNameReferences]
- public sealed partial class MapScreen : BoxContainer
- {
- [Dependency] private readonly IEntityManager _entManager = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- private readonly SharedAudioSystem _audio;
- private readonly SharedMapSystem _maps;
- private readonly ShuttleSystem _shuttles;
- private readonly SharedTransformSystem _xformSystem;
- private EntityUid? _console;
- private EntityUid? _shuttleEntity;
- private FTLState _state;
- private StartEndTime _ftlTime;
- private List<ShuttleBeaconObject> _beacons = new();
- private List<ShuttleExclusionObject> _exclusions = new();
- private TimeSpan _nextPing;
- private TimeSpan _pingCooldown = TimeSpan.FromSeconds(3);
- private TimeSpan _nextMapDequeue;
- private float _minMapDequeue = 0.05f;
- private float _maxMapDequeue = 0.25f;
- private StyleBoxFlat _ftlStyle;
- public event Action<MapCoordinates, Angle>? RequestFTL;
- public event Action<NetEntity, Angle>? RequestBeaconFTL;
- private readonly Dictionary<MapId, BoxContainer> _mapHeadings = new();
- private readonly Dictionary<MapId, List<IMapObject>> _mapObjects = new();
- private readonly List<(MapId mapId, IMapObject mapobj)> _pendingMapObjects = new();
- /// <summary>
- /// Store the names of map object controls for re-sorting later.
- /// </summary>
- private Dictionary<Control, string> _mapObjectControls = new();
- private List<Control> _sortChildren = new();
- public MapScreen()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
- _audio = _entManager.System<SharedAudioSystem>();
- _maps = _entManager.System<SharedMapSystem>();
- _shuttles = _entManager.System<ShuttleSystem>();
- _xformSystem = _entManager.System<SharedTransformSystem>();
- MapRebuildButton.OnPressed += MapRebuildPressed;
- OnVisibilityChanged += OnVisChange;
- MapFTLButton.OnToggled += FtlPreviewToggled;
- _ftlStyle = new StyleBoxFlat(Color.LimeGreen);
- FTLBar.ForegroundStyleBoxOverride = _ftlStyle;
- // Just pass it on up.
- MapRadar.RequestFTL += (coords, angle) =>
- {
- RequestFTL?.Invoke(coords, angle);
- };
- MapRadar.RequestBeaconFTL += (ent, angle) =>
- {
- RequestBeaconFTL?.Invoke(ent, angle);
- };
- MapBeaconsButton.OnToggled += args =>
- {
- MapRadar.ShowBeacons = args.Pressed;
- };
- }
- public void UpdateState(ShuttleMapInterfaceState state)
- {
- // Only network the accumulator due to ping making the thing fonky.
- // This should work better with predicting network states as they come in.
- _beacons = state.Destinations;
- _exclusions = state.Exclusions;
- _state = state.FTLState;
- _ftlTime = state.FTLTime;
- MapRadar.InFtl = true;
- MapFTLState.Text = Loc.GetString($"shuttle-console-ftl-state-{_state.ToString()}");
- switch (_state)
- {
- case FTLState.Available:
- SetFTLAllowed(true);
- _ftlStyle.BackgroundColor = Color.FromHex("#80C71F");
- MapRadar.InFtl = false;
- break;
- case FTLState.Starting:
- SetFTLAllowed(false);
- _ftlStyle.BackgroundColor = Color.FromHex("#169C9C");
- break;
- case FTLState.Travelling:
- SetFTLAllowed(false);
- _ftlStyle.BackgroundColor = Color.FromHex("#8932B8");
- break;
- case FTLState.Arriving:
- SetFTLAllowed(false);
- _ftlStyle.BackgroundColor = Color.FromHex("#F9801D");
- break;
- case FTLState.Cooldown:
- SetFTLAllowed(false);
- // Scroll to the FTL spot
- if (_entManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform))
- {
- var targetOffset = _maps.GetGridPosition(_shuttleEntity.Value);
- MapRadar.SetMap(shuttleXform.MapID, targetOffset, recentering: true);
- }
- _ftlStyle.BackgroundColor = Color.FromHex("#B02E26");
- MapRadar.InFtl = false;
- break;
- // Fallback in case no FTL state or the likes.
- default:
- SetFTLAllowed(false);
- _ftlStyle.BackgroundColor = Color.FromHex("#B02E26");
- MapRadar.InFtl = false;
- break;
- }
- if (IsFTLBlocked())
- {
- MapRebuildButton.Disabled = true;
- ClearMapObjects();
- }
- }
- private void SetFTLAllowed(bool value)
- {
- if (value)
- {
- MapFTLButton.Disabled = false;
- }
- else
- {
- // Unselect FTL
- MapFTLButton.Pressed = false;
- MapRadar.FtlMode = false;
- MapFTLButton.Disabled = true;
- }
- }
- private void FtlPreviewToggled(BaseButton.ButtonToggledEventArgs obj)
- {
- MapRadar.FtlMode = obj.Pressed;
- }
- public void SetConsole(EntityUid? console)
- {
- _console = console;
- }
- public void SetShuttle(EntityUid? shuttle)
- {
- _shuttleEntity = shuttle;
- MapRadar.SetShuttle(shuttle);
- }
- private void OnVisChange(Control obj)
- {
- if (!obj.Visible)
- return;
- // Centre map screen to the shuttle.
- if (_shuttleEntity != null)
- {
- var mapPos = _xformSystem.GetMapCoordinates(_shuttleEntity.Value);
- MapRadar.SetMap(mapPos.MapId, mapPos.Position);
- }
- }
- /// <summary>
- /// Does a sonar-like effect on the map.
- /// </summary>
- public void PingMap()
- {
- if (_console != null)
- {
- _audio.PlayEntity(new SoundPathSpecifier("/Audio/Effects/Shuttle/radar_ping.ogg"), Filter.Local(), _console.Value, true);
- }
- RebuildMapObjects();
- BumpMapDequeue();
- _nextPing = _timing.CurTime + _pingCooldown;
- MapRebuildButton.Disabled = true;
- }
- private void BumpMapDequeue()
- {
- _nextMapDequeue = _timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(_minMapDequeue, _maxMapDequeue));
- }
- private void MapRebuildPressed(BaseButton.ButtonEventArgs obj)
- {
- PingMap();
- }
- /// <summary>
- /// Clears all sector objects across all maps (e.g. if we start FTLing or need to re-ping).
- /// </summary>
- private void ClearMapObjects()
- {
- _mapObjectControls.Clear();
- HyperspaceDestinations.DisposeAllChildren();
- _pendingMapObjects.Clear();
- _mapObjects.Clear();
- _mapHeadings.Clear();
- }
- /// <summary>
- /// Gets all map objects at time of ping and adds them to pending to be added over time.
- /// </summary>
- private void RebuildMapObjects()
- {
- ClearMapObjects();
- if (_shuttleEntity == null)
- return;
- var mapComps = _entManager.AllEntityQueryEnumerator<MapComponent, TransformComponent, MetaDataComponent>();
- MapId ourMap = MapId.Nullspace;
- if (_entManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform))
- {
- ourMap = shuttleXform.MapID;
- }
- while (mapComps.MoveNext(out var mapUid, out var mapComp, out var mapXform, out var mapMetadata))
- {
- if (_console != null && !_shuttles.CanFTLTo(_shuttleEntity.Value, mapComp.MapId, _console.Value))
- {
- continue;
- }
- var mapName = mapMetadata.EntityName;
- if (string.IsNullOrEmpty(mapName))
- {
- mapName = Loc.GetString("shuttle-console-unknown");
- }
- var heading = new CollapsibleHeading(mapName);
- heading.MinHeight = 32f;
- heading.AddStyleClass(ContainerButton.StyleClassButton);
- heading.HorizontalAlignment = HAlignment.Stretch;
- heading.Label.HorizontalAlignment = HAlignment.Center;
- heading.Label.HorizontalExpand = true;
- heading.HorizontalExpand = true;
- var gridContents = new BoxContainer()
- {
- Orientation = LayoutOrientation.Vertical,
- VerticalExpand = true,
- };
- var body = new CollapsibleBody()
- {
- HorizontalAlignment = HAlignment.Stretch,
- VerticalAlignment = VAlignment.Top,
- HorizontalExpand = true,
- Children =
- {
- gridContents
- }
- };
- var mapButton = new Collapsible(heading, body);
- heading.OnToggled += args =>
- {
- if (args.Pressed)
- {
- HideOtherCollapsibles(mapButton);
- }
- };
- _mapHeadings.Add(mapComp.MapId, gridContents);
- foreach (var grid in _mapManager.GetAllGrids(mapComp.MapId))
- {
- _entManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
- var gridObj = new GridMapObject()
- {
- Name = _entManager.GetComponent<MetaDataComponent>(grid.Owner).EntityName,
- Entity = grid.Owner,
- HideButton = iffComp != null && (iffComp.Flags & IFFFlags.HideLabel) != 0x0,
- };
- // Always show our shuttle immediately
- if (grid.Owner == _shuttleEntity)
- {
- AddMapObject(mapComp.MapId, gridObj);
- }
- // If we can show it then add it to pending.
- else if (!_shuttles.IsBeaconMap(mapUid) && (iffComp == null ||
- (iffComp.Flags & IFFFlags.Hide) == 0x0) &&
- !gridObj.HideButton)
- {
- _pendingMapObjects.Add((mapComp.MapId, gridObj));
- }
- }
- foreach (var (beacon, _) in _shuttles.GetExclusions(mapComp.MapId, _exclusions))
- {
- if (beacon.HideButton)
- continue;
- _pendingMapObjects.Add((mapComp.MapId, beacon));
- }
- foreach (var (beacon, _) in _shuttles.GetBeacons(mapComp.MapId, _beacons))
- {
- if (beacon.HideButton)
- continue;
- _pendingMapObjects.Add((mapComp.MapId, beacon));
- }
- HyperspaceDestinations.AddChild(mapButton);
- // Zoom in to our map
- if (mapComp.MapId == MapRadar.ViewingMap)
- {
- mapButton.BodyVisible = true;
- }
- }
- // Need to sort from furthest way to nearest (as we will pop from the end of the list first).
- // Also prioritise those on our map first.
- var shuttlePos = _xformSystem.GetWorldPosition(_shuttleEntity.Value);
- _pendingMapObjects.Sort((x, y) =>
- {
- if (x.mapId == ourMap && y.mapId != ourMap)
- return 1;
- if (y.mapId == ourMap && x.mapId != ourMap)
- return -1;
- var yMapPos = _shuttles.GetMapCoordinates(y.mapobj);
- var xMapPos = _shuttles.GetMapCoordinates(x.mapobj);
- return (yMapPos.Position - shuttlePos).Length().CompareTo((xMapPos.Position - shuttlePos).Length());
- });
- }
- /// <summary>
- /// Hides other maps upon the specified collapsible being selected (AKA hacky collapsible groups).
- /// </summary>
- private void HideOtherCollapsibles(Collapsible collapsible)
- {
- foreach (var child in HyperspaceDestinations.Children)
- {
- if (child is not Collapsible childCollapse || childCollapse == collapsible)
- continue;
- childCollapse.BodyVisible = false;
- }
- }
- /// <summary>
- /// Returns true if we shouldn't be able to select the FTL button.
- /// </summary>
- private bool IsFTLBlocked()
- {
- switch (_state)
- {
- case FTLState.Available:
- return false;
- default:
- return true;
- }
- }
- private void OnMapObjectPress(IMapObject mapObject)
- {
- if (IsFTLBlocked())
- return;
- var coordinates = _shuttles.GetMapCoordinates(mapObject);
- // If it's our map then scroll, otherwise just set position there.
- MapRadar.SetMap(coordinates.MapId, coordinates.Position, recentering: true);
- }
- public void SetMap(MapId mapId, Vector2 position)
- {
- MapRadar.SetMap(mapId, position);
- MapRadar.Offset = position;
- }
- /// <summary>
- /// Adds a map object to the specified sector map.
- /// </summary>
- private void AddMapObject(MapId mapId, IMapObject mapObj)
- {
- var existing = _mapObjects.GetOrNew(mapId);
- existing.Add(mapObj);
- var gridContents = _mapHeadings[mapId];
- var gridButton = new Button()
- {
- Text = mapObj.Name,
- HorizontalExpand = true,
- };
- var gridContainer = new BoxContainer()
- {
- Children =
- {
- new Control()
- {
- MinWidth = 32f,
- },
- gridButton
- }
- };
- _mapObjectControls.Add(gridContainer, mapObj.Name);
- gridContents.AddChild(gridContainer);
- gridButton.OnPressed += args =>
- {
- OnMapObjectPress(mapObj);
- };
- if (gridContents.ChildCount > 1)
- {
- // Re-sort the children
- _sortChildren.Clear();
- foreach (var child in gridContents.Children)
- {
- DebugTools.Assert(_mapObjectControls.ContainsKey(child));
- _sortChildren.Add(child);
- }
- foreach (var child in _sortChildren)
- {
- child.Orphan();
- }
- _sortChildren.Sort((x, y) =>
- {
- var xText = _mapObjectControls[x];
- var yText = _mapObjectControls[y];
- return string.Compare(xText, yText, StringComparison.CurrentCultureIgnoreCase);
- });
- foreach (var control in _sortChildren)
- {
- gridContents.AddChild(control);
- }
- }
- }
- protected override void FrameUpdate(FrameEventArgs args)
- {
- base.FrameUpdate(args);
- var curTime = _timing.CurTime;
- if (_nextMapDequeue < curTime && _pendingMapObjects.Count > 0)
- {
- var mapObj = _pendingMapObjects[^1];
- _pendingMapObjects.RemoveAt(_pendingMapObjects.Count - 1);
- AddMapObject(mapObj.mapId, mapObj.mapobj);
- BumpMapDequeue();
- }
- if (!IsFTLBlocked() && _nextPing < curTime)
- {
- MapRebuildButton.Disabled = false;
- }
- var progress = _ftlTime.ProgressAt(curTime);
- FTLBar.Value = float.IsFinite(progress) ? progress : 1;
- }
- protected override void Draw(DrawingHandleScreen handle)
- {
- MapRadar.SetMapObjects(_mapObjects);
- base.Draw(handle);
- }
- public void Startup()
- {
- if (_entManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform))
- {
- SetMap(shuttleXform.MapID, _maps.GetGridPosition((_shuttleEntity.Value, null, shuttleXform)));
- }
- }
- }
|