ThrownItemSystem.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. using System.Linq;
  2. using Content.Shared.Administration.Logs;
  3. using Content.Shared.Database;
  4. using Content.Shared.Gravity;
  5. using Content.Shared.Physics;
  6. using Content.Shared.Movement.Pulling.Events;
  7. using Robust.Shared.Physics;
  8. using Robust.Shared.Physics.Components;
  9. using Robust.Shared.Physics.Events;
  10. using Robust.Shared.Physics.Systems;
  11. using Robust.Shared.Timing;
  12. namespace Content.Shared.Throwing
  13. {
  14. /// <summary>
  15. /// Handles throwing landing and collisions.
  16. /// </summary>
  17. public sealed class ThrownItemSystem : EntitySystem
  18. {
  19. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  20. [Dependency] private readonly IGameTiming _gameTiming = default!;
  21. [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
  22. [Dependency] private readonly FixtureSystem _fixtures = default!;
  23. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  24. [Dependency] private readonly SharedGravitySystem _gravity = default!;
  25. private const string ThrowingFixture = "throw-fixture";
  26. public override void Initialize()
  27. {
  28. base.Initialize();
  29. SubscribeLocalEvent<ThrownItemComponent, MapInitEvent>(OnMapInit);
  30. SubscribeLocalEvent<ThrownItemComponent, PhysicsSleepEvent>(OnSleep);
  31. SubscribeLocalEvent<ThrownItemComponent, StartCollideEvent>(HandleCollision);
  32. SubscribeLocalEvent<ThrownItemComponent, PreventCollideEvent>(PreventCollision);
  33. SubscribeLocalEvent<ThrownItemComponent, ThrownEvent>(ThrowItem);
  34. SubscribeLocalEvent<PullStartedMessage>(HandlePullStarted);
  35. }
  36. private void OnMapInit(EntityUid uid, ThrownItemComponent component, MapInitEvent args)
  37. {
  38. component.ThrownTime ??= _gameTiming.CurTime;
  39. }
  40. private void ThrowItem(EntityUid uid, ThrownItemComponent component, ref ThrownEvent @event)
  41. {
  42. if (!EntityManager.TryGetComponent(uid, out FixturesComponent? fixturesComponent) ||
  43. fixturesComponent.Fixtures.Count != 1 ||
  44. !TryComp<PhysicsComponent>(uid, out var body))
  45. {
  46. return;
  47. }
  48. var fixture = fixturesComponent.Fixtures.Values.First();
  49. var shape = fixture.Shape;
  50. _fixtures.TryCreateFixture(uid, shape, ThrowingFixture, hard: false, collisionMask: (int) CollisionGroup.ThrownItem, manager: fixturesComponent, body: body);
  51. }
  52. private void HandleCollision(EntityUid uid, ThrownItemComponent component, ref StartCollideEvent args)
  53. {
  54. if (!args.OtherFixture.Hard)
  55. return;
  56. if (args.OtherEntity == component.Thrower)
  57. return;
  58. ThrowCollideInteraction(component, args.OurEntity, args.OtherEntity);
  59. }
  60. private void PreventCollision(EntityUid uid, ThrownItemComponent component, ref PreventCollideEvent args)
  61. {
  62. if (args.OtherEntity == component.Thrower)
  63. {
  64. args.Cancelled = true;
  65. }
  66. }
  67. private void OnSleep(EntityUid uid, ThrownItemComponent thrownItem, ref PhysicsSleepEvent @event)
  68. {
  69. StopThrow(uid, thrownItem);
  70. }
  71. private void HandlePullStarted(PullStartedMessage message)
  72. {
  73. // TODO: this isn't directed so things have to be done the bad way
  74. if (EntityManager.TryGetComponent(message.PulledUid, out ThrownItemComponent? thrownItemComponent))
  75. StopThrow(message.PulledUid, thrownItemComponent);
  76. }
  77. public void StopThrow(EntityUid uid, ThrownItemComponent thrownItemComponent)
  78. {
  79. if (TryComp<PhysicsComponent>(uid, out var physics))
  80. {
  81. _physics.SetBodyStatus(uid, physics, BodyStatus.OnGround);
  82. if (physics.Awake)
  83. _broadphase.RegenerateContacts((uid, physics));
  84. }
  85. if (EntityManager.TryGetComponent(uid, out FixturesComponent? manager))
  86. {
  87. var fixture = _fixtures.GetFixtureOrNull(uid, ThrowingFixture, manager: manager);
  88. if (fixture != null)
  89. {
  90. _fixtures.DestroyFixture(uid, ThrowingFixture, fixture, manager: manager);
  91. }
  92. }
  93. EntityManager.EventBus.RaiseLocalEvent(uid, new StopThrowEvent { User = thrownItemComponent.Thrower }, true);
  94. EntityManager.RemoveComponent<ThrownItemComponent>(uid);
  95. }
  96. public void LandComponent(EntityUid uid, ThrownItemComponent thrownItem, PhysicsComponent physics, bool playSound)
  97. {
  98. if (thrownItem.Landed || thrownItem.Deleted || _gravity.IsWeightless(uid) || Deleted(uid))
  99. return;
  100. thrownItem.Landed = true;
  101. // Assume it's uninteresting if it has no thrower. For now anyway.
  102. if (thrownItem.Thrower is not null)
  103. _adminLogger.Add(LogType.Landed, LogImpact.Low, $"{ToPrettyString(uid):entity} thrown by {ToPrettyString(thrownItem.Thrower.Value):thrower} landed.");
  104. _broadphase.RegenerateContacts((uid, physics));
  105. var landEvent = new LandEvent(thrownItem.Thrower, playSound);
  106. RaiseLocalEvent(uid, ref landEvent);
  107. }
  108. /// <summary>
  109. /// Raises collision events on the thrown and target entities.
  110. /// </summary>
  111. public void ThrowCollideInteraction(ThrownItemComponent component, EntityUid thrown, EntityUid target)
  112. {
  113. if (component.Thrower is not null)
  114. _adminLogger.Add(LogType.ThrowHit, LogImpact.Low,
  115. $"{ToPrettyString(thrown):thrown} thrown by {ToPrettyString(component.Thrower.Value):thrower} hit {ToPrettyString(target):target}.");
  116. RaiseLocalEvent(target, new ThrowHitByEvent(thrown, target, component), true);
  117. RaiseLocalEvent(thrown, new ThrowDoHitEvent(thrown, target, component), true);
  118. }
  119. public override void Update(float frameTime)
  120. {
  121. base.Update(frameTime);
  122. // TODO predicted throwing - remove this check
  123. // We don't want to predict landing or stopping, since throwing isn't actually predicted.
  124. // If we do, the landing/stop will occur prematurely on the client.
  125. if (_gameTiming.InPrediction)
  126. return;
  127. var query = EntityQueryEnumerator<ThrownItemComponent, PhysicsComponent>();
  128. while (query.MoveNext(out var uid, out var thrown, out var physics))
  129. {
  130. if (thrown.LandTime <= _gameTiming.CurTime)
  131. {
  132. LandComponent(uid, thrown, physics, thrown.PlayLandSound);
  133. }
  134. var stopThrowTime = thrown.LandTime ?? thrown.ThrownTime;
  135. if (stopThrowTime <= _gameTiming.CurTime)
  136. {
  137. StopThrow(uid, thrown);
  138. }
  139. }
  140. }
  141. }
  142. }