1
0

DockingSystem.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. using System.Numerics;
  2. using Content.Server.Doors.Systems;
  3. using Content.Server.NPC.Pathfinding;
  4. using Content.Server.Shuttles.Components;
  5. using Content.Server.Shuttles.Events;
  6. using Content.Shared.Doors;
  7. using Content.Shared.Doors.Components;
  8. using Content.Shared.Popups;
  9. using Content.Shared.Shuttles.Components;
  10. using Content.Shared.Shuttles.Events;
  11. using Content.Shared.Shuttles.Systems;
  12. using Robust.Shared.Map;
  13. using Robust.Shared.Map.Components;
  14. using Robust.Shared.Physics;
  15. using Robust.Shared.Physics.Collision.Shapes;
  16. using Robust.Shared.Physics.Components;
  17. using Robust.Shared.Physics.Dynamics.Joints;
  18. using Robust.Shared.Physics.Systems;
  19. using Robust.Shared.Utility;
  20. namespace Content.Server.Shuttles.Systems
  21. {
  22. public sealed partial class DockingSystem : SharedDockingSystem
  23. {
  24. [Dependency] private readonly IMapManager _mapManager = default!;
  25. [Dependency] private readonly SharedMapSystem _mapSystem = default!;
  26. [Dependency] private readonly DoorSystem _doorSystem = default!;
  27. [Dependency] private readonly EntityLookupSystem _lookup = default!;
  28. [Dependency] private readonly PathfindingSystem _pathfinding = default!;
  29. [Dependency] private readonly ShuttleConsoleSystem _console = default!;
  30. [Dependency] private readonly SharedJointSystem _jointSystem = default!;
  31. [Dependency] private readonly SharedPopupSystem _popup = default!;
  32. [Dependency] private readonly SharedTransformSystem _transform = default!;
  33. private const string DockingJoint = "docking";
  34. private EntityQuery<MapGridComponent> _gridQuery;
  35. private EntityQuery<PhysicsComponent> _physicsQuery;
  36. private EntityQuery<TransformComponent> _xformQuery;
  37. private readonly HashSet<Entity<DockingComponent>> _dockingSet = new();
  38. private readonly HashSet<Entity<DockingComponent, DoorBoltComponent>> _dockingBoltSet = new();
  39. public override void Initialize()
  40. {
  41. base.Initialize();
  42. _gridQuery = GetEntityQuery<MapGridComponent>();
  43. _physicsQuery = GetEntityQuery<PhysicsComponent>();
  44. _xformQuery = GetEntityQuery<TransformComponent>();
  45. SubscribeLocalEvent<DockingComponent, ComponentStartup>(OnStartup);
  46. SubscribeLocalEvent<DockingComponent, ComponentShutdown>(OnShutdown);
  47. SubscribeLocalEvent<DockingComponent, AnchorStateChangedEvent>(OnAnchorChange);
  48. SubscribeLocalEvent<DockingComponent, ReAnchorEvent>(OnDockingReAnchor);
  49. SubscribeLocalEvent<DockingComponent, BeforeDoorAutoCloseEvent>(OnAutoClose);
  50. // Yes this isn't in shuttle console; it may be used by other systems technically.
  51. // in which case I would also add their subs here.
  52. SubscribeLocalEvent<ShuttleConsoleComponent, DockRequestMessage>(OnRequestDock);
  53. SubscribeLocalEvent<ShuttleConsoleComponent, UndockRequestMessage>(OnRequestUndock);
  54. }
  55. public void UndockDocks(EntityUid gridUid)
  56. {
  57. _dockingSet.Clear();
  58. _lookup.GetChildEntities(gridUid, _dockingSet);
  59. foreach (var dock in _dockingSet)
  60. {
  61. Undock(dock);
  62. }
  63. }
  64. public void SetDockBolts(EntityUid gridUid, bool enabled)
  65. {
  66. _dockingBoltSet.Clear();
  67. _lookup.GetChildEntities(gridUid, _dockingBoltSet);
  68. foreach (var entity in _dockingBoltSet)
  69. {
  70. _doorSystem.TryClose(entity);
  71. _doorSystem.SetBoltsDown((entity.Owner, entity.Comp2), enabled);
  72. }
  73. }
  74. private void OnAutoClose(EntityUid uid, DockingComponent component, BeforeDoorAutoCloseEvent args)
  75. {
  76. // We'll just pin the door open when docked.
  77. if (component.Docked)
  78. args.Cancel();
  79. }
  80. private void OnShutdown(EntityUid uid, DockingComponent component, ComponentShutdown args)
  81. {
  82. if (component.DockedWith == null ||
  83. EntityManager.GetComponent<MetaDataComponent>(uid).EntityLifeStage > EntityLifeStage.MapInitialized)
  84. {
  85. return;
  86. }
  87. var gridUid = Transform(uid).GridUid;
  88. if (gridUid != null && !Terminating(gridUid.Value))
  89. {
  90. _console.RefreshShuttleConsoles();
  91. }
  92. Cleanup(uid, component);
  93. }
  94. private void Cleanup(EntityUid dockAUid, DockingComponent dockA)
  95. {
  96. _pathfinding.RemovePortal(dockA.PathfindHandle);
  97. if (dockA.DockJoint != null)
  98. _jointSystem.RemoveJoint(dockA.DockJoint);
  99. var dockBUid = dockA.DockedWith;
  100. if (dockBUid == null ||
  101. !TryComp(dockBUid, out DockingComponent? dockB))
  102. {
  103. DebugTools.Assert(false);
  104. Log.Error($"Tried to cleanup {dockAUid} but not docked?");
  105. dockA.DockedWith = null;
  106. return;
  107. }
  108. dockB.DockedWith = null;
  109. dockB.DockJoint = null;
  110. dockB.DockJointId = null;
  111. dockA.DockJoint = null;
  112. dockA.DockedWith = null;
  113. dockA.DockJointId = null;
  114. // If these grids are ever null then need to look at fixing ordering for unanchored events elsewhere.
  115. var gridAUid = EntityManager.GetComponent<TransformComponent>(dockAUid).GridUid;
  116. var gridBUid = EntityManager.GetComponent<TransformComponent>(dockBUid.Value).GridUid;
  117. var msg = new UndockEvent
  118. {
  119. DockA = dockA,
  120. DockB = dockB,
  121. GridAUid = gridAUid!.Value,
  122. GridBUid = gridBUid!.Value,
  123. };
  124. RaiseLocalEvent(dockAUid, msg);
  125. RaiseLocalEvent(dockBUid.Value, msg);
  126. RaiseLocalEvent(msg);
  127. }
  128. private void OnStartup(Entity<DockingComponent> entity, ref ComponentStartup args)
  129. {
  130. var uid = entity.Owner;
  131. var component = entity.Comp;
  132. // Use startup so transform already initialized
  133. if (!EntityManager.GetComponent<TransformComponent>(uid).Anchored)
  134. return;
  135. // This little gem is for docking deserialization
  136. if (component.DockedWith != null)
  137. {
  138. // They're still initialising so we'll just wait for both to be ready.
  139. if (MetaData(component.DockedWith.Value).EntityLifeStage < EntityLifeStage.Initialized)
  140. return;
  141. var otherDock = EntityManager.GetComponent<DockingComponent>(component.DockedWith.Value);
  142. DebugTools.Assert(otherDock.DockedWith != null);
  143. Dock((uid, component), (component.DockedWith.Value, otherDock));
  144. DebugTools.Assert(component.Docked && otherDock.Docked);
  145. }
  146. }
  147. private void OnAnchorChange(Entity<DockingComponent> entity, ref AnchorStateChangedEvent args)
  148. {
  149. if (!args.Anchored)
  150. {
  151. Undock(entity);
  152. }
  153. }
  154. private void OnDockingReAnchor(Entity<DockingComponent> entity, ref ReAnchorEvent args)
  155. {
  156. var uid = entity.Owner;
  157. var component = entity.Comp;
  158. if (!component.Docked)
  159. return;
  160. var otherDock = component.DockedWith;
  161. var other = Comp<DockingComponent>(otherDock!.Value);
  162. Undock(entity);
  163. Dock((uid, component), (otherDock.Value, other));
  164. _console.RefreshShuttleConsoles();
  165. }
  166. /// <summary>
  167. /// Docks 2 ports together and assumes it is valid.
  168. /// </summary>
  169. public void Dock(Entity<DockingComponent> dockA, Entity<DockingComponent> dockB)
  170. {
  171. var dockAUid = dockA.Owner;
  172. var dockBUid = dockB.Owner;
  173. if (dockBUid.GetHashCode() < dockAUid.GetHashCode())
  174. {
  175. (dockA, dockB) = (dockB, dockA);
  176. (dockAUid, dockBUid) = (dockBUid, dockAUid);
  177. }
  178. Log.Debug($"Docking between {dockAUid} and {dockBUid}");
  179. // https://gamedev.stackexchange.com/questions/98772/b2distancejoint-with-frequency-equal-to-0-vs-b2weldjoint
  180. // We could also potentially use a prismatic joint? Depending if we want clamps that can extend or whatever
  181. var dockAXform = EntityManager.GetComponent<TransformComponent>(dockAUid);
  182. var dockBXform = EntityManager.GetComponent<TransformComponent>(dockBUid);
  183. DebugTools.Assert(dockAXform.GridUid != null);
  184. DebugTools.Assert(dockBXform.GridUid != null);
  185. var gridA = dockAXform.GridUid!.Value;
  186. var gridB = dockBXform.GridUid!.Value;
  187. // May not be possible if map or the likes.
  188. if (HasComp<PhysicsComponent>(gridA) &&
  189. HasComp<PhysicsComponent>(gridB))
  190. {
  191. SharedJointSystem.LinearStiffness(
  192. 2f,
  193. 0.7f,
  194. EntityManager.GetComponent<PhysicsComponent>(gridA).Mass,
  195. EntityManager.GetComponent<PhysicsComponent>(gridB).Mass,
  196. out var stiffness,
  197. out var damping);
  198. // These need playing around with
  199. // Could also potentially have collideconnected false and stiffness 0 but it was a bit more suss???
  200. WeldJoint joint;
  201. // Pre-existing joint so use that.
  202. if (dockA.Comp.DockJointId != null)
  203. {
  204. DebugTools.Assert(dockB.Comp.DockJointId == dockA.Comp.DockJointId);
  205. joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, dockA.Comp.DockJointId);
  206. }
  207. else
  208. {
  209. joint = _jointSystem.GetOrCreateWeldJoint(gridA, gridB, DockingJoint + dockAUid);
  210. }
  211. var gridAXform = EntityManager.GetComponent<TransformComponent>(gridA);
  212. var gridBXform = EntityManager.GetComponent<TransformComponent>(gridB);
  213. var anchorA = dockAXform.LocalPosition + dockAXform.LocalRotation.ToWorldVec() / 2f;
  214. var anchorB = dockBXform.LocalPosition + dockBXform.LocalRotation.ToWorldVec() / 2f;
  215. joint.LocalAnchorA = anchorA;
  216. joint.LocalAnchorB = anchorB;
  217. joint.ReferenceAngle = (float)(_transform.GetWorldRotation(gridBXform) - _transform.GetWorldRotation(gridAXform));
  218. joint.CollideConnected = true;
  219. joint.Stiffness = stiffness;
  220. joint.Damping = damping;
  221. dockA.Comp.DockJoint = joint;
  222. dockA.Comp.DockJointId = joint.ID;
  223. dockB.Comp.DockJoint = joint;
  224. dockB.Comp.DockJointId = joint.ID;
  225. }
  226. dockA.Comp.DockedWith = dockBUid;
  227. dockB.Comp.DockedWith = dockAUid;
  228. if (TryComp(dockAUid, out DoorComponent? doorA))
  229. {
  230. if (_doorSystem.TryOpen(dockAUid, doorA))
  231. {
  232. if (TryComp<DoorBoltComponent>(dockAUid, out var airlockA))
  233. {
  234. _doorSystem.SetBoltsDown((dockAUid, airlockA), true);
  235. }
  236. }
  237. doorA.ChangeAirtight = false;
  238. }
  239. if (TryComp(dockBUid, out DoorComponent? doorB))
  240. {
  241. if (_doorSystem.TryOpen(dockBUid, doorB))
  242. {
  243. if (TryComp<DoorBoltComponent>(dockBUid, out var airlockB))
  244. {
  245. _doorSystem.SetBoltsDown((dockBUid, airlockB), true);
  246. }
  247. }
  248. doorB.ChangeAirtight = false;
  249. }
  250. if (_pathfinding.TryCreatePortal(dockAXform.Coordinates, dockBXform.Coordinates, out var handle))
  251. {
  252. dockA.Comp.PathfindHandle = handle;
  253. dockB.Comp.PathfindHandle = handle;
  254. }
  255. var msg = new DockEvent
  256. {
  257. DockA = dockA,
  258. DockB = dockB,
  259. GridAUid = gridA,
  260. GridBUid = gridB,
  261. };
  262. _console.RefreshShuttleConsoles();
  263. RaiseLocalEvent(dockAUid, msg);
  264. RaiseLocalEvent(dockBUid, msg);
  265. RaiseLocalEvent(msg);
  266. }
  267. /// <summary>
  268. /// Attempts to dock 2 ports together and will return early if it's not possible.
  269. /// </summary>
  270. private void TryDock(Entity<DockingComponent> dockA, Entity<DockingComponent> dockB)
  271. {
  272. if (!CanDock(dockA, dockB))
  273. return;
  274. Dock(dockA, dockB);
  275. }
  276. public void Undock(Entity<DockingComponent> dock)
  277. {
  278. if (dock.Comp.DockedWith == null)
  279. return;
  280. OnUndock(dock.Owner);
  281. OnUndock(dock.Comp.DockedWith.Value);
  282. Cleanup(dock.Owner, dock);
  283. _console.RefreshShuttleConsoles();
  284. }
  285. private void OnUndock(EntityUid dockUid)
  286. {
  287. if (TerminatingOrDeleted(dockUid))
  288. return;
  289. if (TryComp<DoorBoltComponent>(dockUid, out var airlock))
  290. _doorSystem.SetBoltsDown((dockUid, airlock), false);
  291. if (TryComp(dockUid, out DoorComponent? door) && _doorSystem.TryClose(dockUid, door))
  292. door.ChangeAirtight = true;
  293. }
  294. private void OnRequestUndock(EntityUid uid, ShuttleConsoleComponent component, UndockRequestMessage args)
  295. {
  296. if (!TryGetEntity(args.DockEntity, out var dockEnt) ||
  297. !TryComp(dockEnt, out DockingComponent? dockComp))
  298. {
  299. _popup.PopupCursor(Loc.GetString("shuttle-console-undock-fail"));
  300. return;
  301. }
  302. var dock = (dockEnt.Value, dockComp);
  303. if (!CanUndock(dock))
  304. {
  305. _popup.PopupCursor(Loc.GetString("shuttle-console-undock-fail"));
  306. return;
  307. }
  308. Undock(dock);
  309. }
  310. private void OnRequestDock(EntityUid uid, ShuttleConsoleComponent component, DockRequestMessage args)
  311. {
  312. var console = _console.GetDroneConsole(uid);
  313. if (console == null)
  314. {
  315. _popup.PopupCursor(Loc.GetString("shuttle-console-dock-fail"));
  316. return;
  317. }
  318. var shuttleUid = Transform(console.Value).GridUid;
  319. if (!CanShuttleDock(shuttleUid))
  320. {
  321. _popup.PopupCursor(Loc.GetString("shuttle-console-dock-fail"));
  322. return;
  323. }
  324. if (!TryGetEntity(args.DockEntity, out var ourDock) ||
  325. !TryGetEntity(args.TargetDockEntity, out var targetDock) ||
  326. !TryComp(ourDock, out DockingComponent? ourDockComp) ||
  327. !TryComp(targetDock, out DockingComponent? targetDockComp))
  328. {
  329. _popup.PopupCursor(Loc.GetString("shuttle-console-dock-fail"));
  330. return;
  331. }
  332. // Cheating?
  333. if (!TryComp(ourDock, out TransformComponent? xformA) ||
  334. xformA.GridUid != shuttleUid)
  335. {
  336. _popup.PopupCursor(Loc.GetString("shuttle-console-dock-fail"));
  337. return;
  338. }
  339. // TODO: Move the CanDock stuff to the port state and also validate that stuff
  340. // Also need to check preventpilot + enabled / dockedwith
  341. if (!CanDock((ourDock.Value, ourDockComp), (targetDock.Value, targetDockComp)))
  342. {
  343. _popup.PopupCursor(Loc.GetString("shuttle-console-dock-fail"));
  344. return;
  345. }
  346. Dock((ourDock.Value, ourDockComp), (targetDock.Value, targetDockComp));
  347. }
  348. public bool CanUndock(Entity<DockingComponent?> dock)
  349. {
  350. if (!Resolve(dock, ref dock.Comp) ||
  351. !dock.Comp.Docked)
  352. {
  353. return false;
  354. }
  355. return true;
  356. }
  357. /// <summary>
  358. /// Returns true if both docks can connect. Does not consider whether the shuttle allows it.
  359. /// </summary>
  360. public bool CanDock(Entity<DockingComponent> dockA, Entity<DockingComponent> dockB)
  361. {
  362. if (dockA.Comp.DockedWith != null ||
  363. dockB.Comp.DockedWith != null)
  364. {
  365. return false;
  366. }
  367. var xformA = Transform(dockA);
  368. var xformB = Transform(dockB);
  369. if (!xformA.Anchored || !xformB.Anchored)
  370. return false;
  371. var (worldPosA, worldRotA) = XformSystem.GetWorldPositionRotation(xformA);
  372. var (worldPosB, worldRotB) = XformSystem.GetWorldPositionRotation(xformB);
  373. return CanDock(new MapCoordinates(worldPosA, xformA.MapID), worldRotA,
  374. new MapCoordinates(worldPosB, xformB.MapID), worldRotB);
  375. }
  376. }
  377. }