1
0

MapScreen.xaml.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. using System.Linq;
  2. using System.Numerics;
  3. using Content.Client.Shuttles.Systems;
  4. using Content.Shared.Shuttles.BUIStates;
  5. using Content.Shared.Shuttles.Components;
  6. using Content.Shared.Shuttles.Systems;
  7. using Content.Shared.Shuttles.UI.MapObjects;
  8. using Content.Shared.Timing;
  9. using Robust.Client.AutoGenerated;
  10. using Robust.Client.Graphics;
  11. using Robust.Client.UserInterface;
  12. using Robust.Client.UserInterface.Controls;
  13. using Robust.Client.UserInterface.XAML;
  14. using Robust.Shared.Audio;
  15. using Robust.Shared.Audio.Systems;
  16. using Robust.Shared.Map;
  17. using Robust.Shared.Map.Components;
  18. using Robust.Shared.Physics.Components;
  19. using Robust.Shared.Player;
  20. using Robust.Shared.Random;
  21. using Robust.Shared.Timing;
  22. using Robust.Shared.Utility;
  23. namespace Content.Client.Shuttles.UI;
  24. [GenerateTypedNameReferences]
  25. public sealed partial class MapScreen : BoxContainer
  26. {
  27. [Dependency] private readonly IEntityManager _entManager = default!;
  28. [Dependency] private readonly IGameTiming _timing = default!;
  29. [Dependency] private readonly IMapManager _mapManager = default!;
  30. [Dependency] private readonly IRobustRandom _random = default!;
  31. private readonly SharedAudioSystem _audio;
  32. private readonly SharedMapSystem _maps;
  33. private readonly ShuttleSystem _shuttles;
  34. private readonly SharedTransformSystem _xformSystem;
  35. private EntityUid? _console;
  36. private EntityUid? _shuttleEntity;
  37. private FTLState _state;
  38. private StartEndTime _ftlTime;
  39. private List<ShuttleBeaconObject> _beacons = new();
  40. private List<ShuttleExclusionObject> _exclusions = new();
  41. private TimeSpan _nextPing;
  42. private TimeSpan _pingCooldown = TimeSpan.FromSeconds(3);
  43. private TimeSpan _nextMapDequeue;
  44. private float _minMapDequeue = 0.05f;
  45. private float _maxMapDequeue = 0.25f;
  46. private StyleBoxFlat _ftlStyle;
  47. public event Action<MapCoordinates, Angle>? RequestFTL;
  48. public event Action<NetEntity, Angle>? RequestBeaconFTL;
  49. private readonly Dictionary<MapId, BoxContainer> _mapHeadings = new();
  50. private readonly Dictionary<MapId, List<IMapObject>> _mapObjects = new();
  51. private readonly List<(MapId mapId, IMapObject mapobj)> _pendingMapObjects = new();
  52. /// <summary>
  53. /// Store the names of map object controls for re-sorting later.
  54. /// </summary>
  55. private Dictionary<Control, string> _mapObjectControls = new();
  56. private List<Control> _sortChildren = new();
  57. public MapScreen()
  58. {
  59. RobustXamlLoader.Load(this);
  60. IoCManager.InjectDependencies(this);
  61. _audio = _entManager.System<SharedAudioSystem>();
  62. _maps = _entManager.System<SharedMapSystem>();
  63. _shuttles = _entManager.System<ShuttleSystem>();
  64. _xformSystem = _entManager.System<SharedTransformSystem>();
  65. MapRebuildButton.OnPressed += MapRebuildPressed;
  66. OnVisibilityChanged += OnVisChange;
  67. MapFTLButton.OnToggled += FtlPreviewToggled;
  68. _ftlStyle = new StyleBoxFlat(Color.LimeGreen);
  69. FTLBar.ForegroundStyleBoxOverride = _ftlStyle;
  70. // Just pass it on up.
  71. MapRadar.RequestFTL += (coords, angle) =>
  72. {
  73. RequestFTL?.Invoke(coords, angle);
  74. };
  75. MapRadar.RequestBeaconFTL += (ent, angle) =>
  76. {
  77. RequestBeaconFTL?.Invoke(ent, angle);
  78. };
  79. MapBeaconsButton.OnToggled += args =>
  80. {
  81. MapRadar.ShowBeacons = args.Pressed;
  82. };
  83. }
  84. public void UpdateState(ShuttleMapInterfaceState state)
  85. {
  86. // Only network the accumulator due to ping making the thing fonky.
  87. // This should work better with predicting network states as they come in.
  88. _beacons = state.Destinations;
  89. _exclusions = state.Exclusions;
  90. _state = state.FTLState;
  91. _ftlTime = state.FTLTime;
  92. MapRadar.InFtl = true;
  93. MapFTLState.Text = Loc.GetString($"shuttle-console-ftl-state-{_state.ToString()}");
  94. switch (_state)
  95. {
  96. case FTLState.Available:
  97. SetFTLAllowed(true);
  98. _ftlStyle.BackgroundColor = Color.FromHex("#80C71F");
  99. MapRadar.InFtl = false;
  100. break;
  101. case FTLState.Starting:
  102. SetFTLAllowed(false);
  103. _ftlStyle.BackgroundColor = Color.FromHex("#169C9C");
  104. break;
  105. case FTLState.Travelling:
  106. SetFTLAllowed(false);
  107. _ftlStyle.BackgroundColor = Color.FromHex("#8932B8");
  108. break;
  109. case FTLState.Arriving:
  110. SetFTLAllowed(false);
  111. _ftlStyle.BackgroundColor = Color.FromHex("#F9801D");
  112. break;
  113. case FTLState.Cooldown:
  114. SetFTLAllowed(false);
  115. // Scroll to the FTL spot
  116. if (_entManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform))
  117. {
  118. var targetOffset = _maps.GetGridPosition(_shuttleEntity.Value);
  119. MapRadar.SetMap(shuttleXform.MapID, targetOffset, recentering: true);
  120. }
  121. _ftlStyle.BackgroundColor = Color.FromHex("#B02E26");
  122. MapRadar.InFtl = false;
  123. break;
  124. // Fallback in case no FTL state or the likes.
  125. default:
  126. SetFTLAllowed(false);
  127. _ftlStyle.BackgroundColor = Color.FromHex("#B02E26");
  128. MapRadar.InFtl = false;
  129. break;
  130. }
  131. if (IsFTLBlocked())
  132. {
  133. MapRebuildButton.Disabled = true;
  134. ClearMapObjects();
  135. }
  136. }
  137. private void SetFTLAllowed(bool value)
  138. {
  139. if (value)
  140. {
  141. MapFTLButton.Disabled = false;
  142. }
  143. else
  144. {
  145. // Unselect FTL
  146. MapFTLButton.Pressed = false;
  147. MapRadar.FtlMode = false;
  148. MapFTLButton.Disabled = true;
  149. }
  150. }
  151. private void FtlPreviewToggled(BaseButton.ButtonToggledEventArgs obj)
  152. {
  153. MapRadar.FtlMode = obj.Pressed;
  154. }
  155. public void SetConsole(EntityUid? console)
  156. {
  157. _console = console;
  158. }
  159. public void SetShuttle(EntityUid? shuttle)
  160. {
  161. _shuttleEntity = shuttle;
  162. MapRadar.SetShuttle(shuttle);
  163. }
  164. private void OnVisChange(Control obj)
  165. {
  166. if (!obj.Visible)
  167. return;
  168. // Centre map screen to the shuttle.
  169. if (_shuttleEntity != null)
  170. {
  171. var mapPos = _xformSystem.GetMapCoordinates(_shuttleEntity.Value);
  172. MapRadar.SetMap(mapPos.MapId, mapPos.Position);
  173. }
  174. }
  175. /// <summary>
  176. /// Does a sonar-like effect on the map.
  177. /// </summary>
  178. public void PingMap()
  179. {
  180. if (_console != null)
  181. {
  182. _audio.PlayEntity(new SoundPathSpecifier("/Audio/Effects/Shuttle/radar_ping.ogg"), Filter.Local(), _console.Value, true);
  183. }
  184. RebuildMapObjects();
  185. BumpMapDequeue();
  186. _nextPing = _timing.CurTime + _pingCooldown;
  187. MapRebuildButton.Disabled = true;
  188. }
  189. private void BumpMapDequeue()
  190. {
  191. _nextMapDequeue = _timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(_minMapDequeue, _maxMapDequeue));
  192. }
  193. private void MapRebuildPressed(BaseButton.ButtonEventArgs obj)
  194. {
  195. PingMap();
  196. }
  197. /// <summary>
  198. /// Clears all sector objects across all maps (e.g. if we start FTLing or need to re-ping).
  199. /// </summary>
  200. private void ClearMapObjects()
  201. {
  202. _mapObjectControls.Clear();
  203. HyperspaceDestinations.DisposeAllChildren();
  204. _pendingMapObjects.Clear();
  205. _mapObjects.Clear();
  206. _mapHeadings.Clear();
  207. }
  208. /// <summary>
  209. /// Gets all map objects at time of ping and adds them to pending to be added over time.
  210. /// </summary>
  211. private void RebuildMapObjects()
  212. {
  213. ClearMapObjects();
  214. if (_shuttleEntity == null)
  215. return;
  216. var mapComps = _entManager.AllEntityQueryEnumerator<MapComponent, TransformComponent, MetaDataComponent>();
  217. MapId ourMap = MapId.Nullspace;
  218. if (_entManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform))
  219. {
  220. ourMap = shuttleXform.MapID;
  221. }
  222. while (mapComps.MoveNext(out var mapUid, out var mapComp, out var mapXform, out var mapMetadata))
  223. {
  224. if (_console != null && !_shuttles.CanFTLTo(_shuttleEntity.Value, mapComp.MapId, _console.Value))
  225. {
  226. continue;
  227. }
  228. var mapName = mapMetadata.EntityName;
  229. if (string.IsNullOrEmpty(mapName))
  230. {
  231. mapName = Loc.GetString("shuttle-console-unknown");
  232. }
  233. var heading = new CollapsibleHeading(mapName);
  234. heading.MinHeight = 32f;
  235. heading.AddStyleClass(ContainerButton.StyleClassButton);
  236. heading.HorizontalAlignment = HAlignment.Stretch;
  237. heading.Label.HorizontalAlignment = HAlignment.Center;
  238. heading.Label.HorizontalExpand = true;
  239. heading.HorizontalExpand = true;
  240. var gridContents = new BoxContainer()
  241. {
  242. Orientation = LayoutOrientation.Vertical,
  243. VerticalExpand = true,
  244. };
  245. var body = new CollapsibleBody()
  246. {
  247. HorizontalAlignment = HAlignment.Stretch,
  248. VerticalAlignment = VAlignment.Top,
  249. HorizontalExpand = true,
  250. Children =
  251. {
  252. gridContents
  253. }
  254. };
  255. var mapButton = new Collapsible(heading, body);
  256. heading.OnToggled += args =>
  257. {
  258. if (args.Pressed)
  259. {
  260. HideOtherCollapsibles(mapButton);
  261. }
  262. };
  263. _mapHeadings.Add(mapComp.MapId, gridContents);
  264. foreach (var grid in _mapManager.GetAllGrids(mapComp.MapId))
  265. {
  266. _entManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
  267. var gridObj = new GridMapObject()
  268. {
  269. Name = _entManager.GetComponent<MetaDataComponent>(grid.Owner).EntityName,
  270. Entity = grid.Owner,
  271. HideButton = iffComp != null && (iffComp.Flags & IFFFlags.HideLabel) != 0x0,
  272. };
  273. // Always show our shuttle immediately
  274. if (grid.Owner == _shuttleEntity)
  275. {
  276. AddMapObject(mapComp.MapId, gridObj);
  277. }
  278. // If we can show it then add it to pending.
  279. else if (!_shuttles.IsBeaconMap(mapUid) && (iffComp == null ||
  280. (iffComp.Flags & IFFFlags.Hide) == 0x0) &&
  281. !gridObj.HideButton)
  282. {
  283. _pendingMapObjects.Add((mapComp.MapId, gridObj));
  284. }
  285. }
  286. foreach (var (beacon, _) in _shuttles.GetExclusions(mapComp.MapId, _exclusions))
  287. {
  288. if (beacon.HideButton)
  289. continue;
  290. _pendingMapObjects.Add((mapComp.MapId, beacon));
  291. }
  292. foreach (var (beacon, _) in _shuttles.GetBeacons(mapComp.MapId, _beacons))
  293. {
  294. if (beacon.HideButton)
  295. continue;
  296. _pendingMapObjects.Add((mapComp.MapId, beacon));
  297. }
  298. HyperspaceDestinations.AddChild(mapButton);
  299. // Zoom in to our map
  300. if (mapComp.MapId == MapRadar.ViewingMap)
  301. {
  302. mapButton.BodyVisible = true;
  303. }
  304. }
  305. // Need to sort from furthest way to nearest (as we will pop from the end of the list first).
  306. // Also prioritise those on our map first.
  307. var shuttlePos = _xformSystem.GetWorldPosition(_shuttleEntity.Value);
  308. _pendingMapObjects.Sort((x, y) =>
  309. {
  310. if (x.mapId == ourMap && y.mapId != ourMap)
  311. return 1;
  312. if (y.mapId == ourMap && x.mapId != ourMap)
  313. return -1;
  314. var yMapPos = _shuttles.GetMapCoordinates(y.mapobj);
  315. var xMapPos = _shuttles.GetMapCoordinates(x.mapobj);
  316. return (yMapPos.Position - shuttlePos).Length().CompareTo((xMapPos.Position - shuttlePos).Length());
  317. });
  318. }
  319. /// <summary>
  320. /// Hides other maps upon the specified collapsible being selected (AKA hacky collapsible groups).
  321. /// </summary>
  322. private void HideOtherCollapsibles(Collapsible collapsible)
  323. {
  324. foreach (var child in HyperspaceDestinations.Children)
  325. {
  326. if (child is not Collapsible childCollapse || childCollapse == collapsible)
  327. continue;
  328. childCollapse.BodyVisible = false;
  329. }
  330. }
  331. /// <summary>
  332. /// Returns true if we shouldn't be able to select the FTL button.
  333. /// </summary>
  334. private bool IsFTLBlocked()
  335. {
  336. switch (_state)
  337. {
  338. case FTLState.Available:
  339. return false;
  340. default:
  341. return true;
  342. }
  343. }
  344. private void OnMapObjectPress(IMapObject mapObject)
  345. {
  346. if (IsFTLBlocked())
  347. return;
  348. var coordinates = _shuttles.GetMapCoordinates(mapObject);
  349. // If it's our map then scroll, otherwise just set position there.
  350. MapRadar.SetMap(coordinates.MapId, coordinates.Position, recentering: true);
  351. }
  352. public void SetMap(MapId mapId, Vector2 position)
  353. {
  354. MapRadar.SetMap(mapId, position);
  355. MapRadar.Offset = position;
  356. }
  357. /// <summary>
  358. /// Adds a map object to the specified sector map.
  359. /// </summary>
  360. private void AddMapObject(MapId mapId, IMapObject mapObj)
  361. {
  362. var existing = _mapObjects.GetOrNew(mapId);
  363. existing.Add(mapObj);
  364. var gridContents = _mapHeadings[mapId];
  365. var gridButton = new Button()
  366. {
  367. Text = mapObj.Name,
  368. HorizontalExpand = true,
  369. };
  370. var gridContainer = new BoxContainer()
  371. {
  372. Children =
  373. {
  374. new Control()
  375. {
  376. MinWidth = 32f,
  377. },
  378. gridButton
  379. }
  380. };
  381. _mapObjectControls.Add(gridContainer, mapObj.Name);
  382. gridContents.AddChild(gridContainer);
  383. gridButton.OnPressed += args =>
  384. {
  385. OnMapObjectPress(mapObj);
  386. };
  387. if (gridContents.ChildCount > 1)
  388. {
  389. // Re-sort the children
  390. _sortChildren.Clear();
  391. foreach (var child in gridContents.Children)
  392. {
  393. DebugTools.Assert(_mapObjectControls.ContainsKey(child));
  394. _sortChildren.Add(child);
  395. }
  396. foreach (var child in _sortChildren)
  397. {
  398. child.Orphan();
  399. }
  400. _sortChildren.Sort((x, y) =>
  401. {
  402. var xText = _mapObjectControls[x];
  403. var yText = _mapObjectControls[y];
  404. return string.Compare(xText, yText, StringComparison.CurrentCultureIgnoreCase);
  405. });
  406. foreach (var control in _sortChildren)
  407. {
  408. gridContents.AddChild(control);
  409. }
  410. }
  411. }
  412. protected override void FrameUpdate(FrameEventArgs args)
  413. {
  414. base.FrameUpdate(args);
  415. var curTime = _timing.CurTime;
  416. if (_nextMapDequeue < curTime && _pendingMapObjects.Count > 0)
  417. {
  418. var mapObj = _pendingMapObjects[^1];
  419. _pendingMapObjects.RemoveAt(_pendingMapObjects.Count - 1);
  420. AddMapObject(mapObj.mapId, mapObj.mapobj);
  421. BumpMapDequeue();
  422. }
  423. if (!IsFTLBlocked() && _nextPing < curTime)
  424. {
  425. MapRebuildButton.Disabled = false;
  426. }
  427. var progress = _ftlTime.ProgressAt(curTime);
  428. FTLBar.Value = float.IsFinite(progress) ? progress : 1;
  429. }
  430. protected override void Draw(DrawingHandleScreen handle)
  431. {
  432. MapRadar.SetMapObjects(_mapObjects);
  433. base.Draw(handle);
  434. }
  435. public void Startup()
  436. {
  437. if (_entManager.TryGetComponent(_shuttleEntity, out TransformComponent? shuttleXform))
  438. {
  439. SetMap(shuttleXform.MapID, _maps.GetGridPosition((_shuttleEntity.Value, null, shuttleXform)));
  440. }
  441. }
  442. }