TileFrictionController.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. using System.Numerics;
  2. using Content.Shared.CCVar;
  3. using Content.Shared.Gravity;
  4. using Content.Shared.Movement.Events;
  5. using Content.Shared.Movement.Pulling.Components;
  6. using Content.Shared.Movement.Systems;
  7. using JetBrains.Annotations;
  8. using Robust.Shared.Configuration;
  9. using Robust.Shared.Map;
  10. using Robust.Shared.Map.Components;
  11. using Robust.Shared.Physics.Components;
  12. using Robust.Shared.Physics.Controllers;
  13. using Robust.Shared.Physics.Dynamics;
  14. using Robust.Shared.Physics.Systems;
  15. namespace Content.Shared.Friction
  16. {
  17. public sealed class TileFrictionController : VirtualController
  18. {
  19. [Dependency] private readonly IConfigurationManager _configManager = default!;
  20. [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
  21. [Dependency] private readonly SharedGravitySystem _gravity = default!;
  22. [Dependency] private readonly SharedMoverController _mover = default!;
  23. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  24. [Dependency] private readonly SharedMapSystem _map = default!;
  25. private EntityQuery<TileFrictionModifierComponent> _frictionQuery;
  26. private EntityQuery<TransformComponent> _xformQuery;
  27. private EntityQuery<PullerComponent> _pullerQuery;
  28. private EntityQuery<PullableComponent> _pullableQuery;
  29. private EntityQuery<MapGridComponent> _gridQuery;
  30. private float _stopSpeed;
  31. private float _frictionModifier;
  32. public const float DefaultFriction = 0.3f;
  33. public override void Initialize()
  34. {
  35. base.Initialize();
  36. Subs.CVar(_configManager, CCVars.TileFrictionModifier, value => _frictionModifier = value, true);
  37. Subs.CVar(_configManager, CCVars.StopSpeed, value => _stopSpeed = value, true);
  38. _frictionQuery = GetEntityQuery<TileFrictionModifierComponent>();
  39. _xformQuery = GetEntityQuery<TransformComponent>();
  40. _pullerQuery = GetEntityQuery<PullerComponent>();
  41. _pullableQuery = GetEntityQuery<PullableComponent>();
  42. _gridQuery = GetEntityQuery<MapGridComponent>();
  43. }
  44. public override void UpdateBeforeMapSolve(bool prediction, PhysicsMapComponent mapComponent, float frameTime)
  45. {
  46. base.UpdateBeforeMapSolve(prediction, mapComponent, frameTime);
  47. foreach (var body in mapComponent.AwakeBodies)
  48. {
  49. var uid = body.Owner;
  50. // Only apply friction when it's not a mob (or the mob doesn't have control)
  51. if (prediction && !body.Predict ||
  52. body.BodyStatus == BodyStatus.InAir ||
  53. _mover.UseMobMovement(uid))
  54. {
  55. continue;
  56. }
  57. if (body.LinearVelocity.Equals(Vector2.Zero) && body.AngularVelocity.Equals(0f))
  58. continue;
  59. if (!_xformQuery.TryGetComponent(uid, out var xform))
  60. {
  61. Log.Error($"Unable to get transform for {ToPrettyString(uid)} in tilefrictioncontroller");
  62. continue;
  63. }
  64. var surfaceFriction = GetTileFriction(uid, body, xform);
  65. var bodyModifier = 1f;
  66. if (_frictionQuery.TryGetComponent(uid, out var frictionComp))
  67. {
  68. bodyModifier = frictionComp.Modifier;
  69. }
  70. var ev = new TileFrictionEvent(bodyModifier);
  71. RaiseLocalEvent(uid, ref ev);
  72. bodyModifier = ev.Modifier;
  73. // If we're sandwiched between 2 pullers reduce friction
  74. // Might be better to make this dynamic and check how many are in the pull chain?
  75. // Either way should be much faster for now.
  76. if (_pullerQuery.TryGetComponent(uid, out var puller) && puller.Pulling != null &&
  77. _pullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled)
  78. {
  79. bodyModifier *= 0.2f;
  80. }
  81. var friction = _frictionModifier * surfaceFriction * bodyModifier;
  82. ReduceLinearVelocity(uid, prediction, body, friction, frameTime);
  83. ReduceAngularVelocity(uid, prediction, body, friction, frameTime);
  84. }
  85. }
  86. private void ReduceLinearVelocity(EntityUid uid, bool prediction, PhysicsComponent body, float friction, float frameTime)
  87. {
  88. var speed = body.LinearVelocity.Length();
  89. if (speed <= 0.0f)
  90. return;
  91. // This is the *actual* amount that speed will drop by, we just do some multiplication around it to be easier.
  92. var drop = 0.0f;
  93. float control;
  94. if (friction > 0.0f)
  95. {
  96. // TBH I can't really tell if this makes a difference.
  97. if (!prediction)
  98. {
  99. control = speed < _stopSpeed ? _stopSpeed : speed;
  100. }
  101. else
  102. {
  103. control = speed;
  104. }
  105. drop += control * friction * frameTime;
  106. }
  107. var newSpeed = MathF.Max(0.0f, speed - drop);
  108. newSpeed /= speed;
  109. _physics.SetLinearVelocity(uid, body.LinearVelocity * newSpeed, body: body);
  110. }
  111. private void ReduceAngularVelocity(EntityUid uid, bool prediction, PhysicsComponent body, float friction, float frameTime)
  112. {
  113. var speed = MathF.Abs(body.AngularVelocity);
  114. if (speed <= 0.0f)
  115. return;
  116. // This is the *actual* amount that speed will drop by, we just do some multiplication around it to be easier.
  117. var drop = 0.0f;
  118. float control;
  119. if (friction > 0.0f)
  120. {
  121. // TBH I can't really tell if this makes a difference.
  122. if (!prediction)
  123. {
  124. control = speed < _stopSpeed ? _stopSpeed : speed;
  125. }
  126. else
  127. {
  128. control = speed;
  129. }
  130. drop += control * friction * frameTime;
  131. }
  132. var newSpeed = MathF.Max(0.0f, speed - drop);
  133. newSpeed /= speed;
  134. _physics.SetAngularVelocity(uid, body.AngularVelocity * newSpeed, body: body);
  135. }
  136. [Pure]
  137. private float GetTileFriction(
  138. EntityUid uid,
  139. PhysicsComponent body,
  140. TransformComponent xform)
  141. {
  142. // TODO: Make IsWeightless event-based; we already have grid traversals tracked so just raise events
  143. if (_gravity.IsWeightless(uid, body, xform))
  144. return 0.0f;
  145. if (!xform.Coordinates.IsValid(EntityManager))
  146. return 0.0f;
  147. // If not on a grid then return the map's friction.
  148. if (!_gridQuery.TryGetComponent(xform.GridUid, out var grid))
  149. {
  150. return _frictionQuery.TryGetComponent(xform.MapUid, out var friction)
  151. ? friction.Modifier
  152. : DefaultFriction;
  153. }
  154. var tile = _map.GetTileRef(xform.GridUid.Value, grid, xform.Coordinates);
  155. // If it's a map but on an empty tile then just assume it has gravity.
  156. if (tile.Tile.IsEmpty &&
  157. HasComp<MapComponent>(xform.GridUid) &&
  158. (!TryComp<GravityComponent>(xform.GridUid, out var gravity) || gravity.Enabled))
  159. {
  160. return DefaultFriction;
  161. }
  162. // If there's an anchored ent that modifies friction then fallback to that instead.
  163. var anc = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices);
  164. while (anc.MoveNext(out var tileEnt))
  165. {
  166. if (_frictionQuery.TryGetComponent(tileEnt, out var friction))
  167. return friction.Modifier;
  168. }
  169. var tileDef = _tileDefinitionManager[tile.Tile.TypeId];
  170. return tileDef.Friction;
  171. }
  172. public void SetModifier(EntityUid entityUid, float value, TileFrictionModifierComponent? friction = null)
  173. {
  174. if (!Resolve(entityUid, ref friction) || value.Equals(friction.Modifier))
  175. return;
  176. friction.Modifier = value;
  177. Dirty(entityUid, friction);
  178. }
  179. }
  180. }