RotateToFaceSystem.cs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. using System.Numerics;
  2. using Content.Shared.ActionBlocker;
  3. using Content.Shared.Buckle.Components;
  4. using Content.Shared.Rotatable;
  5. using JetBrains.Annotations;
  6. namespace Content.Shared.Interaction
  7. {
  8. /// <summary>
  9. /// Contains common code used to rotate a player to face a given target or direction.
  10. /// This interaction in itself is useful for various roleplay purposes.
  11. /// But it needs specialized code to handle chairs and such.
  12. /// Doesn't really fit with SharedInteractionSystem so it's not there.
  13. /// </summary>
  14. [UsedImplicitly]
  15. public sealed class RotateToFaceSystem : EntitySystem
  16. {
  17. [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
  18. [Dependency] private readonly SharedTransformSystem _transform = default!;
  19. /// <summary>
  20. /// Tries to rotate the entity towards the target rotation. Returns false if it needs to keep rotating.
  21. /// </summary>
  22. public bool TryRotateTo(EntityUid uid,
  23. Angle goalRotation,
  24. float frameTime,
  25. Angle tolerance,
  26. double rotationSpeed = float.MaxValue,
  27. TransformComponent? xform = null)
  28. {
  29. if (!Resolve(uid, ref xform))
  30. return true;
  31. // If we have a max rotation speed then do that.
  32. // We'll rotate even if we can't shoot, looks better.
  33. if (rotationSpeed < float.MaxValue)
  34. {
  35. var worldRot = _transform.GetWorldRotation(xform);
  36. var rotationDiff = Angle.ShortestDistance(worldRot, goalRotation).Theta;
  37. var maxRotate = rotationSpeed * frameTime;
  38. if (Math.Abs(rotationDiff) > maxRotate)
  39. {
  40. var goalTheta = worldRot + Math.Sign(rotationDiff) * maxRotate;
  41. TryFaceAngle(uid, goalTheta, xform);
  42. rotationDiff = (goalRotation - goalTheta);
  43. if (Math.Abs(rotationDiff) > tolerance)
  44. {
  45. return false;
  46. }
  47. return true;
  48. }
  49. TryFaceAngle(uid, goalRotation, xform);
  50. }
  51. else
  52. {
  53. TryFaceAngle(uid, goalRotation, xform);
  54. }
  55. return true;
  56. }
  57. public bool TryFaceCoordinates(EntityUid user, Vector2 coordinates, TransformComponent? xform = null)
  58. {
  59. if (!Resolve(user, ref xform))
  60. return false;
  61. var diff = coordinates - _transform.GetMapCoordinates(user, xform: xform).Position;
  62. if (diff.LengthSquared() <= 0.01f)
  63. return true;
  64. var diffAngle = Angle.FromWorldVec(diff);
  65. return TryFaceAngle(user, diffAngle);
  66. }
  67. public bool TryFaceAngle(EntityUid user, Angle diffAngle, TransformComponent? xform = null)
  68. {
  69. if (!_actionBlockerSystem.CanChangeDirection(user))
  70. return false;
  71. if (TryComp(user, out BuckleComponent? buckle) && buckle.BuckledTo is {} strap)
  72. {
  73. // What if a person is strapped to a borg?
  74. // I'm pretty sure this would allow them to be partially ratatouille'd
  75. // We're buckled to another object. Is that object rotatable?
  76. if (!TryComp<RotatableComponent>(strap, out var rotatable) || !rotatable.RotateWhileAnchored)
  77. return false;
  78. // Note the assumption that even if unanchored, user can only do spinnychair with an "independent wheel".
  79. // (Since the user being buckled to it holds it down with their weight.)
  80. // This is logically equivalent to RotateWhileAnchored.
  81. // Barstools and office chairs have independent wheels, while regular chairs don't.
  82. _transform.SetWorldRotation(Transform(strap), diffAngle);
  83. return true;
  84. }
  85. // user is not buckled in; apply to their transform
  86. if (!Resolve(user, ref xform))
  87. return false;
  88. _transform.SetWorldRotation(xform, diffAngle);
  89. return true;
  90. }
  91. }
  92. }