1
0

DockingSystem.Shuttle.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. using System.Linq;
  2. using System.Numerics;
  3. using Content.Server.Shuttles.Components;
  4. using Robust.Shared.Map;
  5. using Robust.Shared.Map.Components;
  6. using Robust.Shared.Physics;
  7. using Robust.Shared.Physics.Collision.Shapes;
  8. using Robust.Shared.Physics.Components;
  9. namespace Content.Server.Shuttles.Systems;
  10. public sealed partial class DockingSystem
  11. {
  12. /*
  13. * Handles the shuttle side of FTL docking.
  14. */
  15. private const int DockRoundingDigits = 2;
  16. public Angle GetAngle(EntityUid uid, TransformComponent xform, EntityUid targetUid, TransformComponent targetXform)
  17. {
  18. var (shuttlePos, shuttleRot) = _transform.GetWorldPositionRotation(xform);
  19. var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetXform);
  20. var shuttleCOM = Robust.Shared.Physics.Transform.Mul(new Transform(shuttlePos, shuttleRot),
  21. _physicsQuery.GetComponent(uid).LocalCenter);
  22. var targetCOM = Robust.Shared.Physics.Transform.Mul(new Transform(targetPos, targetRot),
  23. _physicsQuery.GetComponent(targetUid).LocalCenter);
  24. var mapDiff = shuttleCOM - targetCOM;
  25. var angle = mapDiff.ToWorldAngle();
  26. angle -= targetRot;
  27. return angle;
  28. }
  29. /// <summary>
  30. /// Checks if 2 docks can be connected by moving the shuttle directly onto docks.
  31. /// </summary>
  32. private bool CanDock(
  33. DockingComponent shuttleDock,
  34. TransformComponent shuttleDockXform,
  35. DockingComponent gridDock,
  36. TransformComponent gridDockXform,
  37. Box2 shuttleAABB,
  38. Angle targetGridRotation,
  39. FixturesComponent shuttleFixtures,
  40. Entity<MapGridComponent> gridEntity,
  41. bool isMap,
  42. out Matrix3x2 matty,
  43. out Box2 shuttleDockedAABB,
  44. out Angle gridRotation)
  45. {
  46. shuttleDockedAABB = Box2.UnitCentered;
  47. gridRotation = Angle.Zero;
  48. matty = Matrix3x2.Identity;
  49. if (shuttleDock.Docked ||
  50. gridDock.Docked ||
  51. !shuttleDockXform.Anchored ||
  52. !gridDockXform.Anchored)
  53. {
  54. return false;
  55. }
  56. // First, get the station dock's position relative to the shuttle, this is where we rotate it around
  57. var stationDockPos = shuttleDockXform.LocalPosition +
  58. shuttleDockXform.LocalRotation.RotateVec(new Vector2(0f, -1f));
  59. // Need to invert the grid's angle.
  60. var shuttleDockAngle = shuttleDockXform.LocalRotation;
  61. var gridDockAngle = gridDockXform.LocalRotation.Opposite();
  62. var offsetAngle = gridDockAngle - shuttleDockAngle;
  63. var stationDockMatrix = Matrix3Helpers.CreateInverseTransform(stationDockPos, shuttleDockAngle);
  64. var gridXformMatrix = Matrix3Helpers.CreateTransform(gridDockXform.LocalPosition, gridDockAngle);
  65. matty = Matrix3x2.Multiply(stationDockMatrix, gridXformMatrix);
  66. if (!ValidSpawn(gridEntity, matty, offsetAngle, shuttleFixtures, isMap))
  67. return false;
  68. shuttleDockedAABB = matty.TransformBox(shuttleAABB);
  69. gridRotation = (targetGridRotation + offsetAngle).Reduced();
  70. return true;
  71. }
  72. /// <summary>
  73. /// Gets docking config between 2 specific docks.
  74. /// </summary>
  75. public DockingConfig? GetDockingConfig(
  76. EntityUid shuttleUid,
  77. EntityUid targetGrid,
  78. EntityUid shuttleDockUid,
  79. DockingComponent shuttleDock,
  80. EntityUid gridDockUid,
  81. DockingComponent gridDock)
  82. {
  83. var shuttleDocks = new List<Entity<DockingComponent>>(1)
  84. {
  85. (shuttleDockUid, shuttleDock)
  86. };
  87. var gridDocks = new List<Entity<DockingComponent>>(1)
  88. {
  89. (gridDockUid, gridDock)
  90. };
  91. return GetDockingConfigPrivate(shuttleUid, targetGrid, shuttleDocks, gridDocks);
  92. }
  93. /// <summary>
  94. /// Tries to get a valid docking configuration for the shuttle to the target grid.
  95. /// </summary>
  96. /// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
  97. public DockingConfig? GetDockingConfig(EntityUid shuttleUid, EntityUid targetGrid, string? priorityTag = null)
  98. {
  99. var gridDocks = GetDocks(targetGrid);
  100. var shuttleDocks = GetDocks(shuttleUid);
  101. return GetDockingConfigPrivate(shuttleUid, targetGrid, shuttleDocks, gridDocks, priorityTag);
  102. }
  103. /// <summary>
  104. /// Tries to get a docking config at the specified coordinates and angle.
  105. /// </summary>
  106. public DockingConfig? GetDockingConfigAt(EntityUid shuttleUid,
  107. EntityUid targetGrid,
  108. EntityCoordinates coordinates,
  109. Angle angle)
  110. {
  111. var gridDocks = GetDocks(targetGrid);
  112. var shuttleDocks = GetDocks(shuttleUid);
  113. var configs = GetDockingConfigs(shuttleUid, targetGrid, shuttleDocks, gridDocks);
  114. foreach (var config in configs)
  115. {
  116. if (config.Coordinates.Equals(coordinates) && config.Angle.EqualsApprox(angle, 0.15))
  117. {
  118. return config;
  119. }
  120. }
  121. return null;
  122. }
  123. /// <summary>
  124. /// Gets all docking configs between the 2 grids.
  125. /// </summary>
  126. private List<DockingConfig> GetDockingConfigs(
  127. EntityUid shuttleUid,
  128. EntityUid targetGrid,
  129. List<Entity<DockingComponent>> shuttleDocks,
  130. List<Entity<DockingComponent>> gridDocks)
  131. {
  132. var validDockConfigs = new List<DockingConfig>();
  133. if (gridDocks.Count <= 0)
  134. return validDockConfigs;
  135. var targetGridGrid = _gridQuery.GetComponent(targetGrid);
  136. var targetGridXform = _xformQuery.GetComponent(targetGrid);
  137. var targetGridAngle = _transform.GetWorldRotation(targetGridXform).Reduced();
  138. var shuttleFixturesComp = Comp<FixturesComponent>(shuttleUid);
  139. var shuttleAABB = _gridQuery.GetComponent(shuttleUid).LocalAABB;
  140. var isMap = HasComp<MapComponent>(targetGrid);
  141. var grids = new List<Entity<MapGridComponent>>();
  142. if (shuttleDocks.Count > 0)
  143. {
  144. // We'll try all combinations of shuttle docks and see which one is most suitable
  145. foreach (var (dockUid, shuttleDock) in shuttleDocks)
  146. {
  147. var shuttleDockXform = _xformQuery.GetComponent(dockUid);
  148. foreach (var (gridDockUid, gridDock) in gridDocks)
  149. {
  150. var gridXform = _xformQuery.GetComponent(gridDockUid);
  151. if (!CanDock(
  152. shuttleDock, shuttleDockXform,
  153. gridDock, gridXform,
  154. shuttleAABB,
  155. targetGridAngle,
  156. shuttleFixturesComp,
  157. (targetGrid, targetGridGrid),
  158. isMap,
  159. out var matty,
  160. out var dockedAABB,
  161. out var targetAngle))
  162. {
  163. continue;
  164. }
  165. // Can't just use the AABB as we want to get bounds as tight as possible.
  166. var gridPosition = new EntityCoordinates(targetGrid, Vector2.Transform(Vector2.Zero, matty));
  167. var spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, _transform.ToMapCoordinates(gridPosition).Position);
  168. // TODO: use tight bounds
  169. var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetAngle, spawnPosition.Position);
  170. // Check if there's no intersecting grids (AKA oh god it's docking at cargo).
  171. grids.Clear();
  172. _mapManager.FindGridsIntersecting(targetGridXform.MapID, dockedBounds, ref grids, includeMap: false);
  173. if (grids.Any(o => o.Owner != targetGrid && o.Owner != targetGridXform.MapUid))
  174. {
  175. continue;
  176. }
  177. // Alright well the spawn is valid now to check how many we can connect
  178. // Get the matrix for each shuttle dock and test it against the grid docks to see
  179. // if the connected position / direction matches.
  180. var dockedPorts = new List<(EntityUid DockAUid, EntityUid DockBUid, DockingComponent DockA, DockingComponent DockB)>()
  181. {
  182. (dockUid, gridDockUid, shuttleDock, gridDock),
  183. };
  184. dockedAABB = dockedAABB.Rounded(DockRoundingDigits);
  185. foreach (var (otherUid, other) in shuttleDocks)
  186. {
  187. if (other == shuttleDock)
  188. continue;
  189. foreach (var (otherGridUid, otherGrid) in gridDocks)
  190. {
  191. if (otherGrid == gridDock)
  192. continue;
  193. if (!CanDock(
  194. other,
  195. _xformQuery.GetComponent(otherUid),
  196. otherGrid,
  197. _xformQuery.GetComponent(otherGridUid),
  198. shuttleAABB,
  199. targetGridAngle,
  200. shuttleFixturesComp,
  201. (targetGrid, targetGridGrid),
  202. isMap,
  203. out _,
  204. out var otherdockedAABB,
  205. out var otherTargetAngle))
  206. {
  207. continue;
  208. }
  209. otherdockedAABB = otherdockedAABB.Rounded(DockRoundingDigits);
  210. // Different setup.
  211. if (!targetAngle.Equals(otherTargetAngle) ||
  212. !dockedAABB.Equals(otherdockedAABB))
  213. {
  214. continue;
  215. }
  216. dockedPorts.Add((otherUid, otherGridUid, other, otherGrid));
  217. }
  218. }
  219. validDockConfigs.Add(new DockingConfig()
  220. {
  221. Docks = dockedPorts,
  222. Coordinates = gridPosition,
  223. Area = dockedAABB,
  224. Angle = targetAngle,
  225. });
  226. }
  227. }
  228. }
  229. return validDockConfigs;
  230. }
  231. private DockingConfig? GetDockingConfigPrivate(
  232. EntityUid shuttleUid,
  233. EntityUid targetGrid,
  234. List<Entity<DockingComponent>> shuttleDocks,
  235. List<Entity<DockingComponent>> gridDocks,
  236. string? priorityTag = null)
  237. {
  238. var validDockConfigs = GetDockingConfigs(shuttleUid, targetGrid, shuttleDocks, gridDocks);
  239. if (validDockConfigs.Count <= 0)
  240. return null;
  241. var targetGridAngle = _transform.GetWorldRotation(targetGrid).Reduced();
  242. // Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
  243. validDockConfigs = validDockConfigs
  244. .OrderByDescending(x => IsConfigPriority(x, priorityTag))
  245. .ThenByDescending(x => x.Docks.Count)
  246. .ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();
  247. var location = validDockConfigs.First();
  248. location.TargetGrid = targetGrid;
  249. // TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.
  250. return location;
  251. }
  252. public bool IsConfigPriority(DockingConfig config, string? priorityTag)
  253. {
  254. return config.Docks.Any(docks =>
  255. TryComp<PriorityDockComponent>(docks.DockBUid, out var priority)
  256. && priority.Tag?.Equals(priorityTag) == true);
  257. }
  258. /// <summary>
  259. /// Checks whether the shuttle can warp to the specified position.
  260. /// </summary>
  261. private bool ValidSpawn(Entity<MapGridComponent> gridEntity, Matrix3x2 matty, Angle angle, FixturesComponent shuttleFixturesComp, bool isMap)
  262. {
  263. var transform = new Transform(Vector2.Transform(Vector2.Zero, matty), angle);
  264. // Because some docking bounds are tight af need to check each chunk individually
  265. foreach (var fix in shuttleFixturesComp.Fixtures.Values)
  266. {
  267. var polyShape = (PolygonShape)fix.Shape;
  268. var aabb = polyShape.ComputeAABB(transform, 0);
  269. aabb = aabb.Enlarged(-0.01f);
  270. // If it's a map check no hard collidable anchored entities overlap
  271. if (isMap)
  272. {
  273. var localTiles = _mapSystem.GetLocalTilesEnumerator(gridEntity.Owner, gridEntity.Comp, aabb);
  274. while (localTiles.MoveNext(out var tile))
  275. {
  276. var anchoredEnumerator = _mapSystem.GetAnchoredEntitiesEnumerator(gridEntity.Owner, gridEntity.Comp, tile.GridIndices);
  277. while (anchoredEnumerator.MoveNext(out var anc))
  278. {
  279. if (!_physicsQuery.TryGetComponent(anc, out var physics) ||
  280. !physics.CanCollide ||
  281. !physics.Hard)
  282. {
  283. continue;
  284. }
  285. return false;
  286. }
  287. }
  288. }
  289. // If it's not a map check it doesn't overlap the grid.
  290. else
  291. {
  292. if (_mapSystem.GetLocalTilesIntersecting(gridEntity.Owner, gridEntity.Comp, aabb).Any())
  293. return false;
  294. }
  295. }
  296. return true;
  297. }
  298. public List<Entity<DockingComponent>> GetDocks(EntityUid uid)
  299. {
  300. _dockingSet.Clear();
  301. _lookup.GetChildEntities(uid, _dockingSet);
  302. return _dockingSet.ToList();
  303. }
  304. }