1
0

MoverController.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. using System.Numerics;
  2. using System.Runtime.CompilerServices;
  3. using Content.Server.Shuttles.Components;
  4. using Content.Server.Shuttles.Systems;
  5. using Content.Shared.Movement.Components;
  6. using Content.Shared.Movement.Systems;
  7. using Content.Shared.Shuttles.Components;
  8. using Content.Shared.Shuttles.Systems;
  9. using Robust.Shared.Physics.Components;
  10. using Robust.Shared.Player;
  11. using DroneConsoleComponent = Content.Server.Shuttles.DroneConsoleComponent;
  12. using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
  13. using Robust.Shared.Map.Components;
  14. namespace Content.Server.Physics.Controllers;
  15. public sealed class MoverController : SharedMoverController
  16. {
  17. [Dependency] private readonly ThrusterSystem _thruster = default!;
  18. [Dependency] private readonly SharedTransformSystem _xformSystem = default!;
  19. private Dictionary<EntityUid, (ShuttleComponent, List<(EntityUid, PilotComponent, InputMoverComponent, TransformComponent)>)> _shuttlePilots = new();
  20. public override void Initialize()
  21. {
  22. base.Initialize();
  23. SubscribeLocalEvent<RelayInputMoverComponent, PlayerAttachedEvent>(OnRelayPlayerAttached);
  24. SubscribeLocalEvent<RelayInputMoverComponent, PlayerDetachedEvent>(OnRelayPlayerDetached);
  25. SubscribeLocalEvent<InputMoverComponent, PlayerAttachedEvent>(OnPlayerAttached);
  26. SubscribeLocalEvent<InputMoverComponent, PlayerDetachedEvent>(OnPlayerDetached);
  27. }
  28. private void OnRelayPlayerAttached(Entity<RelayInputMoverComponent> entity, ref PlayerAttachedEvent args)
  29. {
  30. if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
  31. SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
  32. }
  33. private void OnRelayPlayerDetached(Entity<RelayInputMoverComponent> entity, ref PlayerDetachedEvent args)
  34. {
  35. if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
  36. SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
  37. }
  38. private void OnPlayerAttached(Entity<InputMoverComponent> entity, ref PlayerAttachedEvent args)
  39. {
  40. SetMoveInput(entity, MoveButtons.None);
  41. }
  42. private void OnPlayerDetached(Entity<InputMoverComponent> entity, ref PlayerDetachedEvent args)
  43. {
  44. SetMoveInput(entity, MoveButtons.None);
  45. }
  46. protected override bool CanSound()
  47. {
  48. return true;
  49. }
  50. public override void UpdateBeforeSolve(bool prediction, float frameTime)
  51. {
  52. base.UpdateBeforeSolve(prediction, frameTime);
  53. var inputQueryEnumerator = AllEntityQuery<InputMoverComponent>();
  54. while (inputQueryEnumerator.MoveNext(out var uid, out var mover))
  55. {
  56. var physicsUid = uid;
  57. if (RelayQuery.HasComponent(uid))
  58. continue;
  59. if (!XformQuery.TryGetComponent(uid, out var xform))
  60. {
  61. continue;
  62. }
  63. PhysicsComponent? body;
  64. var xformMover = xform;
  65. if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid))
  66. {
  67. if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) ||
  68. !XformQuery.TryGetComponent(xform.ParentUid, out xformMover))
  69. {
  70. continue;
  71. }
  72. physicsUid = xform.ParentUid;
  73. }
  74. else if (!PhysicsQuery.TryGetComponent(uid, out body))
  75. {
  76. continue;
  77. }
  78. HandleMobMovement(uid,
  79. mover,
  80. physicsUid,
  81. body,
  82. xformMover,
  83. frameTime);
  84. }
  85. HandleShuttleMovement(frameTime);
  86. }
  87. public (Vector2 Strafe, float Rotation, float Brakes) GetPilotVelocityInput(PilotComponent component)
  88. {
  89. if (!Timing.InSimulation)
  90. {
  91. // Outside of simulation we'll be running client predicted movement per-frame.
  92. // So return a full-length vector as if it's a full tick.
  93. // Physics system will have the correct time step anyways.
  94. ResetSubtick(component);
  95. ApplyTick(component, 1f);
  96. return (component.CurTickStrafeMovement, component.CurTickRotationMovement, component.CurTickBraking);
  97. }
  98. float remainingFraction;
  99. if (Timing.CurTick > component.LastInputTick)
  100. {
  101. component.CurTickStrafeMovement = Vector2.Zero;
  102. component.CurTickRotationMovement = 0f;
  103. component.CurTickBraking = 0f;
  104. remainingFraction = 1;
  105. }
  106. else
  107. {
  108. remainingFraction = (ushort.MaxValue - component.LastInputSubTick) / (float) ushort.MaxValue;
  109. }
  110. ApplyTick(component, remainingFraction);
  111. // Logger.Info($"{curDir}{walk}{sprint}");
  112. return (component.CurTickStrafeMovement, component.CurTickRotationMovement, component.CurTickBraking);
  113. }
  114. private void ResetSubtick(PilotComponent component)
  115. {
  116. if (Timing.CurTick <= component.LastInputTick) return;
  117. component.CurTickStrafeMovement = Vector2.Zero;
  118. component.CurTickRotationMovement = 0f;
  119. component.CurTickBraking = 0f;
  120. component.LastInputTick = Timing.CurTick;
  121. component.LastInputSubTick = 0;
  122. }
  123. protected override void HandleShuttleInput(EntityUid uid, ShuttleButtons button, ushort subTick, bool state)
  124. {
  125. if (!TryComp<PilotComponent>(uid, out var pilot) || pilot.Console == null)
  126. return;
  127. ResetSubtick(pilot);
  128. if (subTick >= pilot.LastInputSubTick)
  129. {
  130. var fraction = (subTick - pilot.LastInputSubTick) / (float) ushort.MaxValue;
  131. ApplyTick(pilot, fraction);
  132. pilot.LastInputSubTick = subTick;
  133. }
  134. var buttons = pilot.HeldButtons;
  135. if (state)
  136. {
  137. buttons |= button;
  138. }
  139. else
  140. {
  141. buttons &= ~button;
  142. }
  143. pilot.HeldButtons = buttons;
  144. }
  145. private static void ApplyTick(PilotComponent component, float fraction)
  146. {
  147. var x = 0;
  148. var y = 0;
  149. var rot = 0;
  150. int brake;
  151. if ((component.HeldButtons & ShuttleButtons.StrafeLeft) != 0x0)
  152. {
  153. x -= 1;
  154. }
  155. if ((component.HeldButtons & ShuttleButtons.StrafeRight) != 0x0)
  156. {
  157. x += 1;
  158. }
  159. component.CurTickStrafeMovement.X += x * fraction;
  160. if ((component.HeldButtons & ShuttleButtons.StrafeUp) != 0x0)
  161. {
  162. y += 1;
  163. }
  164. if ((component.HeldButtons & ShuttleButtons.StrafeDown) != 0x0)
  165. {
  166. y -= 1;
  167. }
  168. component.CurTickStrafeMovement.Y += y * fraction;
  169. if ((component.HeldButtons & ShuttleButtons.RotateLeft) != 0x0)
  170. {
  171. rot -= 1;
  172. }
  173. if ((component.HeldButtons & ShuttleButtons.RotateRight) != 0x0)
  174. {
  175. rot += 1;
  176. }
  177. component.CurTickRotationMovement += rot * fraction;
  178. if ((component.HeldButtons & ShuttleButtons.Brake) != 0x0)
  179. {
  180. brake = 1;
  181. }
  182. else
  183. {
  184. brake = 0;
  185. }
  186. component.CurTickBraking += brake * fraction;
  187. }
  188. /// <summary>
  189. /// Helper function to extrapolate max velocity for a given Vector2 (really, its angle) and shuttle.
  190. /// </summary>
  191. private Vector2 ObtainMaxVel(Vector2 vel, ShuttleComponent shuttle)
  192. {
  193. if (vel.Length() == 0f)
  194. return Vector2.Zero;
  195. // this math could PROBABLY be simplified for performance
  196. // probably
  197. // __________________________________
  198. // / / __ __ \2 / __ __ \2
  199. // O = I : _ / |I * | 1/H | | + |I * | 0 | |
  200. // V \ |_ 0 _| / \ |_1/V_| /
  201. var horizIndex = vel.X > 0 ? 1 : 3; // east else west
  202. var vertIndex = vel.Y > 0 ? 2 : 0; // north else south
  203. var horizComp = vel.X != 0 ? MathF.Pow(Vector2.Dot(vel, new (shuttle.LinearThrust[horizIndex] / shuttle.LinearThrust[horizIndex], 0f)), 2) : 0;
  204. var vertComp = vel.Y != 0 ? MathF.Pow(Vector2.Dot(vel, new (0f, shuttle.LinearThrust[vertIndex] / shuttle.LinearThrust[vertIndex])), 2) : 0;
  205. return shuttle.BaseMaxLinearVelocity * vel * MathF.ReciprocalSqrtEstimate(horizComp + vertComp);
  206. }
  207. private void HandleShuttleMovement(float frameTime)
  208. {
  209. var newPilots = new Dictionary<EntityUid, (ShuttleComponent Shuttle, List<(EntityUid PilotUid, PilotComponent Pilot, InputMoverComponent Mover, TransformComponent ConsoleXform)>)>();
  210. // We just mark off their movement and the shuttle itself does its own movement
  211. var activePilotQuery = EntityQueryEnumerator<PilotComponent, InputMoverComponent>();
  212. var shuttleQuery = GetEntityQuery<ShuttleComponent>();
  213. while (activePilotQuery.MoveNext(out var uid, out var pilot, out var mover))
  214. {
  215. var consoleEnt = pilot.Console;
  216. // TODO: This is terrible. Just make a new mover and also make it remote piloting + device networks
  217. if (TryComp<DroneConsoleComponent>(consoleEnt, out var cargoConsole))
  218. {
  219. consoleEnt = cargoConsole.Entity;
  220. }
  221. if (!TryComp(consoleEnt, out TransformComponent? xform)) continue;
  222. var gridId = xform.GridUid;
  223. // This tries to see if the grid is a shuttle and if the console should work.
  224. if (!TryComp<MapGridComponent>(gridId, out var _) ||
  225. !shuttleQuery.TryGetComponent(gridId, out var shuttleComponent) ||
  226. !shuttleComponent.Enabled)
  227. continue;
  228. if (!newPilots.TryGetValue(gridId!.Value, out var pilots))
  229. {
  230. pilots = (shuttleComponent, new List<(EntityUid, PilotComponent, InputMoverComponent, TransformComponent)>());
  231. newPilots[gridId.Value] = pilots;
  232. }
  233. pilots.Item2.Add((uid, pilot, mover, xform));
  234. }
  235. // Reset inputs for non-piloted shuttles.
  236. foreach (var (shuttleUid, (shuttle, _)) in _shuttlePilots)
  237. {
  238. if (newPilots.ContainsKey(shuttleUid) || CanPilot(shuttleUid))
  239. continue;
  240. _thruster.DisableLinearThrusters(shuttle);
  241. }
  242. _shuttlePilots = newPilots;
  243. // Collate all of the linear / angular velocites for a shuttle
  244. // then do the movement input once for it.
  245. var xformQuery = GetEntityQuery<TransformComponent>();
  246. foreach (var (shuttleUid, (shuttle, pilots)) in _shuttlePilots)
  247. {
  248. if (Paused(shuttleUid) || CanPilot(shuttleUid) || !TryComp<PhysicsComponent>(shuttleUid, out var body))
  249. continue;
  250. var shuttleNorthAngle = _xformSystem.GetWorldRotation(shuttleUid, xformQuery);
  251. // Collate movement linear and angular inputs together
  252. var linearInput = Vector2.Zero;
  253. var brakeInput = 0f;
  254. var angularInput = 0f;
  255. var linearCount = 0;
  256. var brakeCount = 0;
  257. var angularCount = 0;
  258. foreach (var (pilotUid, pilot, _, consoleXform) in pilots)
  259. {
  260. var (strafe, rotation, brakes) = GetPilotVelocityInput(pilot);
  261. if (brakes > 0f)
  262. {
  263. brakeInput += brakes;
  264. brakeCount++;
  265. }
  266. if (strafe.Length() > 0f)
  267. {
  268. var offsetRotation = consoleXform.LocalRotation;
  269. linearInput += offsetRotation.RotateVec(strafe);
  270. linearCount++;
  271. }
  272. if (rotation != 0f)
  273. {
  274. angularInput += rotation;
  275. angularCount++;
  276. }
  277. }
  278. // Don't slow down the shuttle if there's someone just looking at the console
  279. linearInput /= Math.Max(1, linearCount);
  280. angularInput /= Math.Max(1, angularCount);
  281. brakeInput /= Math.Max(1, brakeCount);
  282. // Handle shuttle movement
  283. if (brakeInput > 0f)
  284. {
  285. if (body.LinearVelocity.Length() > 0f)
  286. {
  287. // Minimum brake velocity for a direction to show its thrust appearance.
  288. const float appearanceThreshold = 0.1f;
  289. // Get velocity relative to the shuttle so we know which thrusters to fire
  290. var shuttleVelocity = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
  291. var force = Vector2.Zero;
  292. if (shuttleVelocity.X < 0f)
  293. {
  294. _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.West);
  295. if (shuttleVelocity.X < -appearanceThreshold)
  296. _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.East);
  297. var index = (int) Math.Log2((int) DirectionFlag.East);
  298. force.X += shuttle.LinearThrust[index];
  299. }
  300. else if (shuttleVelocity.X > 0f)
  301. {
  302. _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.East);
  303. if (shuttleVelocity.X > appearanceThreshold)
  304. _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.West);
  305. var index = (int) Math.Log2((int) DirectionFlag.West);
  306. force.X -= shuttle.LinearThrust[index];
  307. }
  308. if (shuttleVelocity.Y < 0f)
  309. {
  310. _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.South);
  311. if (shuttleVelocity.Y < -appearanceThreshold)
  312. _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.North);
  313. var index = (int) Math.Log2((int) DirectionFlag.North);
  314. force.Y += shuttle.LinearThrust[index];
  315. }
  316. else if (shuttleVelocity.Y > 0f)
  317. {
  318. _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.North);
  319. if (shuttleVelocity.Y > appearanceThreshold)
  320. _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South);
  321. var index = (int) Math.Log2((int) DirectionFlag.South);
  322. force.Y -= shuttle.LinearThrust[index];
  323. }
  324. var impulse = force * brakeInput * ShuttleComponent.BrakeCoefficient;
  325. impulse = shuttleNorthAngle.RotateVec(impulse);
  326. var forceMul = frameTime * body.InvMass;
  327. var maxVelocity = (-body.LinearVelocity).Length() / forceMul;
  328. // Don't overshoot
  329. if (impulse.Length() > maxVelocity)
  330. impulse = impulse.Normalized() * maxVelocity;
  331. PhysicsSystem.ApplyForce(shuttleUid, impulse, body: body);
  332. }
  333. else
  334. {
  335. _thruster.DisableLinearThrusters(shuttle);
  336. }
  337. if (body.AngularVelocity != 0f)
  338. {
  339. var torque = shuttle.AngularThrust * brakeInput * (body.AngularVelocity > 0f ? -1f : 1f) * ShuttleComponent.BrakeCoefficient;
  340. var torqueMul = body.InvI * frameTime;
  341. if (body.AngularVelocity > 0f)
  342. {
  343. torque = MathF.Max(-body.AngularVelocity / torqueMul, torque);
  344. }
  345. else
  346. {
  347. torque = MathF.Min(-body.AngularVelocity / torqueMul, torque);
  348. }
  349. if (!torque.Equals(0f))
  350. {
  351. PhysicsSystem.ApplyTorque(shuttleUid, torque, body: body);
  352. _thruster.SetAngularThrust(shuttle, true);
  353. }
  354. }
  355. else
  356. {
  357. _thruster.SetAngularThrust(shuttle, false);
  358. }
  359. }
  360. if (linearInput.Length().Equals(0f))
  361. {
  362. PhysicsSystem.SetSleepingAllowed(shuttleUid, body, true);
  363. if (brakeInput.Equals(0f))
  364. _thruster.DisableLinearThrusters(shuttle);
  365. }
  366. else
  367. {
  368. PhysicsSystem.SetSleepingAllowed(shuttleUid, body, false);
  369. var angle = linearInput.ToWorldAngle();
  370. var linearDir = angle.GetDir();
  371. var dockFlag = linearDir.AsFlag();
  372. var totalForce = Vector2.Zero;
  373. // Won't just do cardinal directions.
  374. foreach (DirectionFlag dir in Enum.GetValues(typeof(DirectionFlag)))
  375. {
  376. // Brain no worky but I just want cardinals
  377. switch (dir)
  378. {
  379. case DirectionFlag.South:
  380. case DirectionFlag.East:
  381. case DirectionFlag.North:
  382. case DirectionFlag.West:
  383. break;
  384. default:
  385. continue;
  386. }
  387. if ((dir & dockFlag) == 0x0)
  388. {
  389. _thruster.DisableLinearThrustDirection(shuttle, dir);
  390. continue;
  391. }
  392. var force = Vector2.Zero;
  393. var index = (int) Math.Log2((int) dir);
  394. var thrust = shuttle.LinearThrust[index];
  395. switch (dir)
  396. {
  397. case DirectionFlag.North:
  398. force.Y += thrust;
  399. break;
  400. case DirectionFlag.South:
  401. force.Y -= thrust;
  402. break;
  403. case DirectionFlag.East:
  404. force.X += thrust;
  405. break;
  406. case DirectionFlag.West:
  407. force.X -= thrust;
  408. break;
  409. default:
  410. throw new ArgumentOutOfRangeException($"Attempted to apply thrust to shuttle {shuttleUid} along invalid dir {dir}.");
  411. }
  412. _thruster.EnableLinearThrustDirection(shuttle, dir);
  413. var impulse = force * linearInput.Length();
  414. totalForce += impulse;
  415. }
  416. var forceMul = frameTime * body.InvMass;
  417. var localVel = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
  418. var maxVelocity = ObtainMaxVel(localVel, shuttle); // max for current travel dir
  419. var maxWishVelocity = ObtainMaxVel(totalForce, shuttle);
  420. var properAccel = (maxWishVelocity - localVel) / forceMul;
  421. var finalForce = Vector2Dot(totalForce, properAccel.Normalized()) * properAccel.Normalized();
  422. if (localVel.Length() >= maxVelocity.Length() && Vector2.Dot(totalForce, localVel) > 0f)
  423. finalForce = Vector2.Zero; // burn would be faster if used as such
  424. if (finalForce.Length() > properAccel.Length())
  425. finalForce = properAccel; // don't overshoot
  426. //Log.Info($"shuttle: maxVelocity {maxVelocity} totalForce {totalForce} finalForce {finalForce} forceMul {forceMul} properAccel {properAccel}");
  427. finalForce = shuttleNorthAngle.RotateVec(finalForce);
  428. if (finalForce.Length() > 0f)
  429. PhysicsSystem.ApplyForce(shuttleUid, finalForce, body: body);
  430. }
  431. if (MathHelper.CloseTo(angularInput, 0f))
  432. {
  433. PhysicsSystem.SetSleepingAllowed(shuttleUid, body, true);
  434. if (brakeInput <= 0f)
  435. _thruster.SetAngularThrust(shuttle, false);
  436. }
  437. else
  438. {
  439. PhysicsSystem.SetSleepingAllowed(shuttleUid, body, false);
  440. var torque = shuttle.AngularThrust * -angularInput;
  441. // Need to cap the velocity if 1 tick of input brings us over cap so we don't continuously
  442. // edge onto the cap over and over.
  443. var torqueMul = body.InvI * frameTime;
  444. torque = Math.Clamp(torque,
  445. (-ShuttleComponent.MaxAngularVelocity - body.AngularVelocity) / torqueMul,
  446. (ShuttleComponent.MaxAngularVelocity - body.AngularVelocity) / torqueMul);
  447. if (!torque.Equals(0f))
  448. {
  449. PhysicsSystem.ApplyTorque(shuttleUid, torque, body: body);
  450. _thruster.SetAngularThrust(shuttle, true);
  451. }
  452. }
  453. }
  454. }
  455. // .NET 8 seem to miscompile usage of Vector2.Dot above. This manual outline fixes it pending an upstream fix.
  456. // See PR #24008
  457. [MethodImpl(MethodImplOptions.NoInlining)]
  458. public static float Vector2Dot(Vector2 value1, Vector2 value2)
  459. {
  460. return Vector2.Dot(value1, value2);
  461. }
  462. private bool CanPilot(EntityUid shuttleUid)
  463. {
  464. return TryComp<FTLComponent>(shuttleUid, out var ftl)
  465. && (ftl.State & (FTLState.Starting | FTLState.Travelling | FTLState.Arriving)) != 0x0
  466. || HasComp<PreventPilotComponent>(shuttleUid);
  467. }
  468. }