PilotedClothingSystem.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. using Content.Shared.Clothing.Components;
  2. using Content.Shared.Inventory.Events;
  3. using Content.Shared.Movement.Components;
  4. using Content.Shared.Movement.Systems;
  5. using Content.Shared.Storage;
  6. using Content.Shared.Whitelist;
  7. using Robust.Shared.Containers;
  8. using Robust.Shared.Timing;
  9. namespace Content.Shared.Clothing.EntitySystems;
  10. public sealed partial class PilotedClothingSystem : EntitySystem
  11. {
  12. [Dependency] private readonly IGameTiming _timing = default!;
  13. [Dependency] private readonly SharedMoverController _moverController = default!;
  14. [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
  15. public override void Initialize()
  16. {
  17. base.Initialize();
  18. SubscribeLocalEvent<PilotedClothingComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
  19. SubscribeLocalEvent<PilotedClothingComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
  20. SubscribeLocalEvent<PilotedClothingComponent, GotEquippedEvent>(OnEquipped);
  21. SubscribeLocalEvent<PilotedClothingComponent, GotUnequippedEvent>(OnUnequipped);
  22. }
  23. private void OnEntInserted(Entity<PilotedClothingComponent> entity, ref EntInsertedIntoContainerMessage args)
  24. {
  25. // Make sure the entity was actually inserted into storage and not a different container.
  26. if (!TryComp(entity, out StorageComponent? storage) || args.Container != storage.Container)
  27. return;
  28. // Check potential pilot against whitelist, if one exists.
  29. if (_whitelist.IsWhitelistFail(entity.Comp.PilotWhitelist, args.Entity))
  30. return;
  31. entity.Comp.Pilot = args.Entity;
  32. Dirty(entity);
  33. // Attempt to setup control link, if Pilot and Wearer are both present.
  34. StartPiloting(entity);
  35. }
  36. private void OnEntRemoved(Entity<PilotedClothingComponent> entity, ref EntRemovedFromContainerMessage args)
  37. {
  38. // Make sure the removed entity is actually the pilot.
  39. if (args.Entity != entity.Comp.Pilot)
  40. return;
  41. StopPiloting(entity);
  42. entity.Comp.Pilot = null;
  43. Dirty(entity);
  44. }
  45. private void OnEquipped(Entity<PilotedClothingComponent> entity, ref GotEquippedEvent args)
  46. {
  47. if (!TryComp(entity, out ClothingComponent? clothing))
  48. return;
  49. // Make sure the clothing item was equipped to the right slot, and not just held in a hand.
  50. var isCorrectSlot = (clothing.Slots & args.SlotFlags) != Inventory.SlotFlags.NONE;
  51. if (!isCorrectSlot)
  52. return;
  53. entity.Comp.Wearer = args.Equipee;
  54. Dirty(entity);
  55. // Attempt to setup control link, if Pilot and Wearer are both present.
  56. StartPiloting(entity);
  57. }
  58. private void OnUnequipped(Entity<PilotedClothingComponent> entity, ref GotUnequippedEvent args)
  59. {
  60. StopPiloting(entity);
  61. entity.Comp.Wearer = null;
  62. Dirty(entity);
  63. }
  64. /// <summary>
  65. /// Attempts to establish movement/interaction relay connection(s) from Pilot to Wearer.
  66. /// If either is missing, fails and returns false.
  67. /// </summary>
  68. private bool StartPiloting(Entity<PilotedClothingComponent> entity)
  69. {
  70. // Make sure we have both a Pilot and a Wearer
  71. if (entity.Comp.Pilot == null || entity.Comp.Wearer == null)
  72. return false;
  73. if (!_timing.IsFirstTimePredicted)
  74. return false;
  75. var pilotEnt = entity.Comp.Pilot.Value;
  76. var wearerEnt = entity.Comp.Wearer.Value;
  77. // Add component to block prediction of wearer
  78. EnsureComp<PilotedByClothingComponent>(wearerEnt);
  79. if (entity.Comp.RelayMovement)
  80. {
  81. // Establish movement input relay.
  82. _moverController.SetRelay(pilotEnt, wearerEnt);
  83. }
  84. var pilotEv = new StartedPilotingClothingEvent(entity, wearerEnt);
  85. RaiseLocalEvent(pilotEnt, ref pilotEv);
  86. var wearerEv = new StartingBeingPilotedByClothing(entity, pilotEnt);
  87. RaiseLocalEvent(wearerEnt, ref wearerEv);
  88. return true;
  89. }
  90. /// <summary>
  91. /// Removes components from the Pilot and Wearer to stop the control relay.
  92. /// Returns false if a connection does not already exist.
  93. /// </summary>
  94. private bool StopPiloting(Entity<PilotedClothingComponent> entity)
  95. {
  96. if (entity.Comp.Pilot == null || entity.Comp.Wearer == null)
  97. return false;
  98. // Clean up components on the Pilot
  99. var pilotEnt = entity.Comp.Pilot.Value;
  100. RemCompDeferred<RelayInputMoverComponent>(pilotEnt);
  101. // Clean up components on the Wearer
  102. var wearerEnt = entity.Comp.Wearer.Value;
  103. RemCompDeferred<MovementRelayTargetComponent>(wearerEnt);
  104. RemCompDeferred<PilotedByClothingComponent>(wearerEnt);
  105. // Raise an event on the Pilot
  106. var pilotEv = new StoppedPilotingClothingEvent(entity, wearerEnt);
  107. RaiseLocalEvent(pilotEnt, ref pilotEv);
  108. // Raise an event on the Wearer
  109. var wearerEv = new StoppedBeingPilotedByClothing(entity, pilotEnt);
  110. RaiseLocalEvent(wearerEnt, ref wearerEv);
  111. return true;
  112. }
  113. }
  114. /// <summary>
  115. /// Raised on the Pilot when they gain control of the Wearer.
  116. /// </summary>
  117. [ByRefEvent]
  118. public record struct StartedPilotingClothingEvent(EntityUid Clothing, EntityUid Wearer);
  119. /// <summary>
  120. /// Raised on the Pilot when they lose control of the Wearer,
  121. /// due to the Pilot exiting the clothing or the clothing being unequipped by the Wearer.
  122. /// </summary>
  123. [ByRefEvent]
  124. public record struct StoppedPilotingClothingEvent(EntityUid Clothing, EntityUid Wearer);
  125. /// <summary>
  126. /// Raised on the Wearer when the Pilot gains control of them.
  127. /// </summary>
  128. [ByRefEvent]
  129. public record struct StartingBeingPilotedByClothing(EntityUid Clothing, EntityUid Pilot);
  130. /// <summary>
  131. /// Raised on the Wearer when the Pilot loses control of them
  132. /// due to the Pilot exiting the clothing or the clothing being unequipped by the Wearer.
  133. /// </summary>
  134. [ByRefEvent]
  135. public record struct StoppedBeingPilotedByClothing(EntityUid Clothing, EntityUid Pilot);