SharedMoverController.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Numerics;
  3. using Content.Shared.Bed.Sleep;
  4. using Content.Shared.CCVar;
  5. using Content.Shared.Friction;
  6. using Content.Shared.Gravity;
  7. using Content.Shared.Inventory;
  8. using Content.Shared.Maps;
  9. using Content.Shared.Mobs.Systems;
  10. using Content.Shared.Movement.Components;
  11. using Content.Shared.Movement.Events;
  12. using Content.Shared.Tag;
  13. using Robust.Shared.Audio;
  14. using Robust.Shared.Audio.Systems;
  15. using Robust.Shared.Configuration;
  16. using Robust.Shared.Containers;
  17. using Robust.Shared.Map;
  18. using Robust.Shared.Map.Components;
  19. using Robust.Shared.Physics;
  20. using Robust.Shared.Physics.Components;
  21. using Robust.Shared.Physics.Controllers;
  22. using Robust.Shared.Physics.Systems;
  23. using Robust.Shared.Timing;
  24. using Robust.Shared.Utility;
  25. using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent;
  26. namespace Content.Shared.Movement.Systems;
  27. /// <summary>
  28. /// Handles player and NPC mob movement.
  29. /// NPCs are handled server-side only.
  30. /// </summary>
  31. public abstract partial class SharedMoverController : VirtualController
  32. {
  33. [Dependency] private readonly IConfigurationManager _configManager = default!;
  34. [Dependency] protected readonly IGameTiming Timing = default!;
  35. [Dependency] private readonly IMapManager _mapManager = default!;
  36. [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
  37. [Dependency] private readonly EntityLookupSystem _lookup = default!;
  38. [Dependency] private readonly InventorySystem _inventory = default!;
  39. [Dependency] private readonly MobStateSystem _mobState = default!;
  40. [Dependency] private readonly SharedAudioSystem _audio = default!;
  41. [Dependency] private readonly SharedContainerSystem _container = default!;
  42. [Dependency] private readonly SharedMapSystem _mapSystem = default!;
  43. [Dependency] private readonly SharedGravitySystem _gravity = default!;
  44. [Dependency] protected readonly SharedPhysicsSystem Physics = default!;
  45. [Dependency] private readonly SharedTransformSystem _transform = default!;
  46. [Dependency] private readonly TagSystem _tags = default!;
  47. protected EntityQuery<InputMoverComponent> MoverQuery;
  48. protected EntityQuery<MobMoverComponent> MobMoverQuery;
  49. protected EntityQuery<MovementRelayTargetComponent> RelayTargetQuery;
  50. protected EntityQuery<MovementSpeedModifierComponent> ModifierQuery;
  51. protected EntityQuery<PhysicsComponent> PhysicsQuery;
  52. protected EntityQuery<RelayInputMoverComponent> RelayQuery;
  53. protected EntityQuery<PullableComponent> PullableQuery;
  54. protected EntityQuery<TransformComponent> XformQuery;
  55. protected EntityQuery<CanMoveInAirComponent> CanMoveInAirQuery;
  56. protected EntityQuery<NoRotateOnMoveComponent> NoRotateQuery;
  57. protected EntityQuery<FootstepModifierComponent> FootstepModifierQuery;
  58. protected EntityQuery<MapGridComponent> MapGridQuery;
  59. /// <summary>
  60. /// <see cref="CCVars.StopSpeed"/>
  61. /// </summary>
  62. private float _stopSpeed;
  63. private bool _relativeMovement;
  64. /// <summary>
  65. /// Cache the mob movement calculation to re-use elsewhere.
  66. /// </summary>
  67. public Dictionary<EntityUid, bool> UsedMobMovement = new();
  68. public override void Initialize()
  69. {
  70. base.Initialize();
  71. MoverQuery = GetEntityQuery<InputMoverComponent>();
  72. MobMoverQuery = GetEntityQuery<MobMoverComponent>();
  73. ModifierQuery = GetEntityQuery<MovementSpeedModifierComponent>();
  74. RelayTargetQuery = GetEntityQuery<MovementRelayTargetComponent>();
  75. PhysicsQuery = GetEntityQuery<PhysicsComponent>();
  76. RelayQuery = GetEntityQuery<RelayInputMoverComponent>();
  77. PullableQuery = GetEntityQuery<PullableComponent>();
  78. XformQuery = GetEntityQuery<TransformComponent>();
  79. NoRotateQuery = GetEntityQuery<NoRotateOnMoveComponent>();
  80. CanMoveInAirQuery = GetEntityQuery<CanMoveInAirComponent>();
  81. FootstepModifierQuery = GetEntityQuery<FootstepModifierComponent>();
  82. MapGridQuery = GetEntityQuery<MapGridComponent>();
  83. InitializeInput();
  84. InitializeRelay();
  85. Subs.CVar(_configManager, CCVars.RelativeMovement, value => _relativeMovement = value, true);
  86. Subs.CVar(_configManager, CCVars.StopSpeed, value => _stopSpeed = value, true);
  87. UpdatesBefore.Add(typeof(TileFrictionController));
  88. }
  89. public override void Shutdown()
  90. {
  91. base.Shutdown();
  92. ShutdownInput();
  93. }
  94. public override void UpdateAfterSolve(bool prediction, float frameTime)
  95. {
  96. base.UpdateAfterSolve(prediction, frameTime);
  97. UsedMobMovement.Clear();
  98. }
  99. /// <summary>
  100. /// Movement while considering actionblockers, weightlessness, etc.
  101. /// </summary>
  102. protected void HandleMobMovement(
  103. EntityUid uid,
  104. InputMoverComponent mover,
  105. EntityUid physicsUid,
  106. PhysicsComponent physicsComponent,
  107. TransformComponent xform,
  108. float frameTime)
  109. {
  110. var canMove = mover.CanMove;
  111. if (RelayTargetQuery.TryGetComponent(uid, out var relayTarget))
  112. {
  113. if (_mobState.IsIncapacitated(relayTarget.Source) ||
  114. TryComp<SleepingComponent>(relayTarget.Source, out _) ||
  115. !MoverQuery.TryGetComponent(relayTarget.Source, out var relayedMover))
  116. {
  117. canMove = false;
  118. }
  119. else
  120. {
  121. mover.RelativeEntity = relayedMover.RelativeEntity;
  122. mover.RelativeRotation = relayedMover.RelativeRotation;
  123. mover.TargetRelativeRotation = relayedMover.TargetRelativeRotation;
  124. }
  125. }
  126. // Update relative movement
  127. if (mover.LerpTarget < Timing.CurTime)
  128. {
  129. if (TryUpdateRelative(mover, xform))
  130. {
  131. Dirty(uid, mover);
  132. }
  133. }
  134. LerpRotation(uid, mover, frameTime);
  135. if (!canMove
  136. || physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid)
  137. || PullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled)
  138. {
  139. UsedMobMovement[uid] = false;
  140. return;
  141. }
  142. UsedMobMovement[uid] = true;
  143. // Specifically don't use mover.Owner because that may be different to the actual physics body being moved.
  144. var weightless = _gravity.IsWeightless(physicsUid, physicsComponent, xform);
  145. var (walkDir, sprintDir) = GetVelocityInput(mover);
  146. var touching = false;
  147. // Handle wall-pushes.
  148. if (weightless)
  149. {
  150. if (xform.GridUid != null)
  151. touching = true;
  152. if (!touching)
  153. {
  154. var ev = new CanWeightlessMoveEvent(uid);
  155. RaiseLocalEvent(uid, ref ev, true);
  156. // No gravity: is our entity touching anything?
  157. touching = ev.CanMove;
  158. if (!touching && TryComp<MobMoverComponent>(uid, out var mobMover))
  159. touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsUid, physicsComponent);
  160. }
  161. }
  162. // Get current tile def for things like speed/friction mods
  163. ContentTileDefinition? tileDef = null;
  164. // Don't bother getting the tiledef here if we're weightless or in-air
  165. // since no tile-based modifiers should be applying in that situation
  166. if (MapGridQuery.TryComp(xform.GridUid, out var gridComp)
  167. && _mapSystem.TryGetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates, out var tile)
  168. && !(weightless || physicsComponent.BodyStatus == BodyStatus.InAir))
  169. {
  170. tileDef = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
  171. }
  172. // Regular movement.
  173. // Target velocity.
  174. // This is relative to the map / grid we're on.
  175. var moveSpeedComponent = ModifierQuery.CompOrNull(uid);
  176. var walkSpeed = moveSpeedComponent?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
  177. var sprintSpeed = moveSpeedComponent?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
  178. var total = walkDir * walkSpeed + sprintDir * sprintSpeed;
  179. var parentRotation = GetParentGridAngle(mover);
  180. var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total;
  181. DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), worldTotal.Length()));
  182. var velocity = physicsComponent.LinearVelocity;
  183. float friction;
  184. float weightlessModifier;
  185. float accel;
  186. if (weightless)
  187. {
  188. if (gridComp == null && !MapGridQuery.HasComp(xform.GridUid))
  189. friction = moveSpeedComponent?.OffGridFriction ?? MovementSpeedModifierComponent.DefaultOffGridFriction;
  190. else if (worldTotal != Vector2.Zero && touching)
  191. friction = moveSpeedComponent?.WeightlessFriction ?? MovementSpeedModifierComponent.DefaultWeightlessFriction;
  192. else
  193. friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? MovementSpeedModifierComponent.DefaultWeightlessFrictionNoInput;
  194. weightlessModifier = moveSpeedComponent?.WeightlessModifier ?? MovementSpeedModifierComponent.DefaultWeightlessModifier;
  195. accel = moveSpeedComponent?.WeightlessAcceleration ?? MovementSpeedModifierComponent.DefaultWeightlessAcceleration;
  196. }
  197. else
  198. {
  199. if (worldTotal != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null)
  200. {
  201. friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction;
  202. }
  203. else
  204. {
  205. friction = tileDef?.MobFrictionNoInput ?? moveSpeedComponent.FrictionNoInput ?? MovementSpeedModifierComponent.DefaultFrictionNoInput;
  206. }
  207. weightlessModifier = 1f;
  208. accel = tileDef?.MobAcceleration ?? moveSpeedComponent?.Acceleration ?? MovementSpeedModifierComponent.DefaultAcceleration;
  209. }
  210. var minimumFrictionSpeed = moveSpeedComponent?.MinimumFrictionSpeed ?? MovementSpeedModifierComponent.DefaultMinimumFrictionSpeed;
  211. Friction(minimumFrictionSpeed, frameTime, friction, ref velocity);
  212. if (worldTotal != Vector2.Zero)
  213. {
  214. if (!NoRotateQuery.HasComponent(uid))
  215. {
  216. // TODO apparently this results in a duplicate move event because "This should have its event run during
  217. // island solver"??. So maybe SetRotation needs an argument to avoid raising an event?
  218. var worldRot = _transform.GetWorldRotation(xform);
  219. _transform.SetLocalRotation(xform, xform.LocalRotation + worldTotal.ToWorldAngle() - worldRot);
  220. }
  221. if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) &&
  222. TryGetSound(weightless, uid, mover, mobMover, xform, out var sound, tileDef: tileDef))
  223. {
  224. var soundModifier = mover.Sprinting ? 3.5f : 1.5f;
  225. var audioParams = sound.Params
  226. .WithVolume(sound.Params.Volume + soundModifier)
  227. .WithVariation(sound.Params.Variation ?? mobMover.FootstepVariation);
  228. // If we're a relay target then predict the sound for all relays.
  229. if (relayTarget != null)
  230. {
  231. _audio.PlayPredicted(sound, uid, relayTarget.Source, audioParams);
  232. }
  233. else
  234. {
  235. _audio.PlayPredicted(sound, uid, uid, audioParams);
  236. }
  237. }
  238. }
  239. worldTotal *= weightlessModifier;
  240. if (!weightless || touching)
  241. Accelerate(ref velocity, in worldTotal, accel, frameTime);
  242. PhysicsSystem.SetLinearVelocity(physicsUid, velocity, body: physicsComponent);
  243. // Ensures that players do not spiiiiiiin
  244. PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent);
  245. }
  246. public void LerpRotation(EntityUid uid, InputMoverComponent mover, float frameTime)
  247. {
  248. var angleDiff = Angle.ShortestDistance(mover.RelativeRotation, mover.TargetRelativeRotation);
  249. // if we've just traversed then lerp to our target rotation.
  250. if (!angleDiff.EqualsApprox(Angle.Zero, 0.001))
  251. {
  252. var adjustment = angleDiff * 5f * frameTime;
  253. var minAdjustment = 0.01 * frameTime;
  254. if (angleDiff < 0)
  255. {
  256. adjustment = Math.Min(adjustment, -minAdjustment);
  257. adjustment = Math.Clamp(adjustment, angleDiff, -angleDiff);
  258. }
  259. else
  260. {
  261. adjustment = Math.Max(adjustment, minAdjustment);
  262. adjustment = Math.Clamp(adjustment, -angleDiff, angleDiff);
  263. }
  264. mover.RelativeRotation += adjustment;
  265. mover.RelativeRotation.FlipPositive();
  266. Dirty(uid, mover);
  267. }
  268. else if (!angleDiff.Equals(Angle.Zero))
  269. {
  270. mover.TargetRelativeRotation.FlipPositive();
  271. mover.RelativeRotation = mover.TargetRelativeRotation;
  272. Dirty(uid, mover);
  273. }
  274. }
  275. private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity)
  276. {
  277. var speed = velocity.Length();
  278. if (speed < minimumFrictionSpeed)
  279. return;
  280. var drop = 0f;
  281. var control = MathF.Max(_stopSpeed, speed);
  282. drop += control * friction * frameTime;
  283. var newSpeed = MathF.Max(0f, speed - drop);
  284. if (newSpeed.Equals(speed))
  285. return;
  286. newSpeed /= speed;
  287. velocity *= newSpeed;
  288. }
  289. private void Accelerate(ref Vector2 currentVelocity, in Vector2 velocity, float accel, float frameTime)
  290. {
  291. var wishDir = velocity != Vector2.Zero ? velocity.Normalized() : Vector2.Zero;
  292. var wishSpeed = velocity.Length();
  293. var currentSpeed = Vector2.Dot(currentVelocity, wishDir);
  294. var addSpeed = wishSpeed - currentSpeed;
  295. if (addSpeed <= 0f)
  296. return;
  297. var accelSpeed = accel * frameTime * wishSpeed;
  298. accelSpeed = MathF.Min(accelSpeed, addSpeed);
  299. currentVelocity += wishDir * accelSpeed;
  300. }
  301. public bool UseMobMovement(EntityUid uid)
  302. {
  303. return UsedMobMovement.TryGetValue(uid, out var used) && used;
  304. }
  305. /// <summary>
  306. /// Used for weightlessness to determine if we are near a wall.
  307. /// </summary>
  308. private bool IsAroundCollider(SharedPhysicsSystem broadPhaseSystem, TransformComponent transform, MobMoverComponent mover, EntityUid physicsUid, PhysicsComponent collider)
  309. {
  310. var enlargedAABB = _lookup.GetWorldAABB(physicsUid, transform).Enlarged(mover.GrabRangeVV);
  311. foreach (var otherCollider in broadPhaseSystem.GetCollidingEntities(transform.MapID, enlargedAABB))
  312. {
  313. if (otherCollider == collider)
  314. continue; // Don't try to push off of yourself!
  315. // Only allow pushing off of anchored things that have collision.
  316. if (otherCollider.BodyType != BodyType.Static ||
  317. !otherCollider.CanCollide ||
  318. ((collider.CollisionMask & otherCollider.CollisionLayer) == 0 &&
  319. (otherCollider.CollisionMask & collider.CollisionLayer) == 0) ||
  320. (TryComp(otherCollider.Owner, out PullableComponent? pullable) && pullable.BeingPulled))
  321. {
  322. continue;
  323. }
  324. return true;
  325. }
  326. return false;
  327. }
  328. protected abstract bool CanSound();
  329. private bool TryGetSound(
  330. bool weightless,
  331. EntityUid uid,
  332. InputMoverComponent mover,
  333. MobMoverComponent mobMover,
  334. TransformComponent xform,
  335. [NotNullWhen(true)] out SoundSpecifier? sound,
  336. ContentTileDefinition? tileDef = null)
  337. {
  338. sound = null;
  339. if (!CanSound() || !_tags.HasTag(uid, "FootstepSound"))
  340. return false;
  341. var coordinates = xform.Coordinates;
  342. var distanceNeeded = mover.Sprinting
  343. ? mobMover.StepSoundMoveDistanceRunning
  344. : mobMover.StepSoundMoveDistanceWalking;
  345. // Handle footsteps.
  346. if (!weightless)
  347. {
  348. // Can happen when teleporting between grids.
  349. if (!coordinates.TryDistance(EntityManager, mobMover.LastPosition, out var distance) ||
  350. distance > distanceNeeded)
  351. {
  352. mobMover.StepSoundDistance = distanceNeeded;
  353. }
  354. else
  355. {
  356. mobMover.StepSoundDistance += distance;
  357. }
  358. }
  359. else
  360. {
  361. // In space no one can hear you squeak
  362. return false;
  363. }
  364. mobMover.LastPosition = coordinates;
  365. if (mobMover.StepSoundDistance < distanceNeeded)
  366. return false;
  367. mobMover.StepSoundDistance -= distanceNeeded;
  368. if (FootstepModifierQuery.TryComp(uid, out var moverModifier))
  369. {
  370. sound = moverModifier.FootstepSoundCollection;
  371. return sound != null;
  372. }
  373. if (_inventory.TryGetSlotEntity(uid, "shoes", out var shoes) &&
  374. FootstepModifierQuery.TryComp(shoes, out var modifier))
  375. {
  376. sound = modifier.FootstepSoundCollection;
  377. return sound != null;
  378. }
  379. return TryGetFootstepSound(uid, xform, shoes != null, out sound, tileDef: tileDef);
  380. }
  381. private bool TryGetFootstepSound(
  382. EntityUid uid,
  383. TransformComponent xform,
  384. bool haveShoes,
  385. [NotNullWhen(true)] out SoundSpecifier? sound,
  386. ContentTileDefinition? tileDef = null)
  387. {
  388. sound = null;
  389. // Fallback to the map?
  390. if (!MapGridQuery.TryComp(xform.GridUid, out var grid))
  391. {
  392. if (FootstepModifierQuery.TryComp(xform.MapUid, out var modifier))
  393. {
  394. sound = modifier.FootstepSoundCollection;
  395. }
  396. return sound != null;
  397. }
  398. var position = grid.LocalToTile(xform.Coordinates);
  399. var soundEv = new GetFootstepSoundEvent(uid);
  400. // If the coordinates have a FootstepModifier component
  401. // i.e. component that emit sound on footsteps emit that sound
  402. var anchored = grid.GetAnchoredEntitiesEnumerator(position);
  403. while (anchored.MoveNext(out var maybeFootstep))
  404. {
  405. RaiseLocalEvent(maybeFootstep.Value, ref soundEv);
  406. if (soundEv.Sound != null)
  407. {
  408. sound = soundEv.Sound;
  409. return true;
  410. }
  411. if (FootstepModifierQuery.TryComp(maybeFootstep, out var footstep))
  412. {
  413. sound = footstep.FootstepSoundCollection;
  414. return sound != null;
  415. }
  416. }
  417. // Walking on a tile.
  418. // Tile def might have been passed in already from previous methods, so use that
  419. // if we have it
  420. if (tileDef == null && grid.TryGetTileRef(position, out var tileRef))
  421. {
  422. tileDef = (ContentTileDefinition) _tileDefinitionManager[tileRef.Tile.TypeId];
  423. }
  424. if (tileDef == null)
  425. return false;
  426. sound = haveShoes ? tileDef.FootstepSounds : tileDef.BarestepSounds;
  427. return sound != null;
  428. }
  429. }