1
0

SharedMechSystem.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. using System.Linq;
  2. using Content.Shared.Access.Components;
  3. using Content.Shared.ActionBlocker;
  4. using Content.Shared.Actions;
  5. using Content.Shared.Destructible;
  6. using Content.Shared.DoAfter;
  7. using Content.Shared.DragDrop;
  8. using Content.Shared.FixedPoint;
  9. using Content.Shared.Interaction;
  10. using Content.Shared.Interaction.Components;
  11. using Content.Shared.Interaction.Events;
  12. using Content.Shared.Mech.Components;
  13. using Content.Shared.Mech.Equipment.Components;
  14. using Content.Shared.Movement.Components;
  15. using Content.Shared.Movement.Systems;
  16. using Content.Shared.Popups;
  17. using Content.Shared.Weapons.Melee;
  18. using Content.Shared.Whitelist;
  19. using Robust.Shared.Containers;
  20. using Robust.Shared.Network;
  21. using Robust.Shared.Serialization;
  22. using Robust.Shared.Timing;
  23. namespace Content.Shared.Mech.EntitySystems;
  24. /// <summary>
  25. /// Handles all of the interactions, UI handling, and items shennanigans for <see cref="MechComponent"/>
  26. /// </summary>
  27. public abstract class SharedMechSystem : EntitySystem
  28. {
  29. [Dependency] private readonly IGameTiming _timing = default!;
  30. [Dependency] private readonly INetManager _net = default!;
  31. [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
  32. [Dependency] private readonly SharedActionsSystem _actions = default!;
  33. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  34. [Dependency] private readonly SharedContainerSystem _container = default!;
  35. [Dependency] private readonly SharedInteractionSystem _interaction = default!;
  36. [Dependency] private readonly SharedMoverController _mover = default!;
  37. [Dependency] private readonly SharedPopupSystem _popup = default!;
  38. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  39. [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
  40. /// <inheritdoc/>
  41. public override void Initialize()
  42. {
  43. SubscribeLocalEvent<MechComponent, MechToggleEquipmentEvent>(OnToggleEquipmentAction);
  44. SubscribeLocalEvent<MechComponent, MechEjectPilotEvent>(OnEjectPilotEvent);
  45. SubscribeLocalEvent<MechComponent, UserActivateInWorldEvent>(RelayInteractionEvent);
  46. SubscribeLocalEvent<MechComponent, ComponentStartup>(OnStartup);
  47. SubscribeLocalEvent<MechComponent, DestructionEventArgs>(OnDestruction);
  48. SubscribeLocalEvent<MechComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
  49. SubscribeLocalEvent<MechComponent, DragDropTargetEvent>(OnDragDrop);
  50. SubscribeLocalEvent<MechComponent, CanDropTargetEvent>(OnCanDragDrop);
  51. SubscribeLocalEvent<MechPilotComponent, GetMeleeWeaponEvent>(OnGetMeleeWeapon);
  52. SubscribeLocalEvent<MechPilotComponent, CanAttackFromContainerEvent>(OnCanAttackFromContainer);
  53. SubscribeLocalEvent<MechPilotComponent, AttackAttemptEvent>(OnAttackAttempt);
  54. }
  55. private void OnToggleEquipmentAction(EntityUid uid, MechComponent component, MechToggleEquipmentEvent args)
  56. {
  57. if (args.Handled)
  58. return;
  59. args.Handled = true;
  60. CycleEquipment(uid);
  61. }
  62. private void OnEjectPilotEvent(EntityUid uid, MechComponent component, MechEjectPilotEvent args)
  63. {
  64. if (args.Handled)
  65. return;
  66. args.Handled = true;
  67. TryEject(uid, component);
  68. }
  69. private void RelayInteractionEvent(EntityUid uid, MechComponent component, UserActivateInWorldEvent args)
  70. {
  71. var pilot = component.PilotSlot.ContainedEntity;
  72. if (pilot == null)
  73. return;
  74. // TODO why is this being blocked?
  75. if (!_timing.IsFirstTimePredicted)
  76. return;
  77. if (component.CurrentSelectedEquipment != null)
  78. {
  79. RaiseLocalEvent(component.CurrentSelectedEquipment.Value, args);
  80. }
  81. }
  82. private void OnStartup(EntityUid uid, MechComponent component, ComponentStartup args)
  83. {
  84. component.PilotSlot = _container.EnsureContainer<ContainerSlot>(uid, component.PilotSlotId);
  85. component.EquipmentContainer = _container.EnsureContainer<Container>(uid, component.EquipmentContainerId);
  86. component.BatterySlot = _container.EnsureContainer<ContainerSlot>(uid, component.BatterySlotId);
  87. UpdateAppearance(uid, component);
  88. }
  89. private void OnDestruction(EntityUid uid, MechComponent component, DestructionEventArgs args)
  90. {
  91. BreakMech(uid, component);
  92. }
  93. private void OnGetAdditionalAccess(EntityUid uid, MechComponent component, ref GetAdditionalAccessEvent args)
  94. {
  95. var pilot = component.PilotSlot.ContainedEntity;
  96. if (pilot == null)
  97. return;
  98. args.Entities.Add(pilot.Value);
  99. }
  100. private void SetupUser(EntityUid mech, EntityUid pilot, MechComponent? component = null)
  101. {
  102. if (!Resolve(mech, ref component))
  103. return;
  104. var rider = EnsureComp<MechPilotComponent>(pilot);
  105. // Warning: this bypasses most normal interaction blocking components on the user, like drone laws and the like.
  106. var irelay = EnsureComp<InteractionRelayComponent>(pilot);
  107. _mover.SetRelay(pilot, mech);
  108. _interaction.SetRelay(pilot, mech, irelay);
  109. rider.Mech = mech;
  110. Dirty(pilot, rider);
  111. if (_net.IsClient)
  112. return;
  113. _actions.AddAction(pilot, ref component.MechCycleActionEntity, component.MechCycleAction, mech);
  114. _actions.AddAction(pilot, ref component.MechUiActionEntity, component.MechUiAction, mech);
  115. _actions.AddAction(pilot, ref component.MechEjectActionEntity, component.MechEjectAction, mech);
  116. }
  117. private void RemoveUser(EntityUid mech, EntityUid pilot)
  118. {
  119. if (!RemComp<MechPilotComponent>(pilot))
  120. return;
  121. RemComp<RelayInputMoverComponent>(pilot);
  122. RemComp<InteractionRelayComponent>(pilot);
  123. _actions.RemoveProvidedActions(pilot, mech);
  124. }
  125. /// <summary>
  126. /// Destroys the mech, removing the user and ejecting all installed equipment.
  127. /// </summary>
  128. /// <param name="uid"></param>
  129. /// <param name="component"></param>
  130. public virtual void BreakMech(EntityUid uid, MechComponent? component = null)
  131. {
  132. if (!Resolve(uid, ref component))
  133. return;
  134. TryEject(uid, component);
  135. var equipment = new List<EntityUid>(component.EquipmentContainer.ContainedEntities);
  136. foreach (var ent in equipment)
  137. {
  138. RemoveEquipment(uid, ent, component, forced: true);
  139. }
  140. component.Broken = true;
  141. UpdateAppearance(uid, component);
  142. }
  143. /// <summary>
  144. /// Cycles through the currently selected equipment.
  145. /// </summary>
  146. /// <param name="uid"></param>
  147. /// <param name="component"></param>
  148. public void CycleEquipment(EntityUid uid, MechComponent? component = null)
  149. {
  150. if (!Resolve(uid, ref component))
  151. return;
  152. var allEquipment = component.EquipmentContainer.ContainedEntities.ToList();
  153. var equipmentIndex = -1;
  154. if (component.CurrentSelectedEquipment != null)
  155. {
  156. bool StartIndex(EntityUid u) => u == component.CurrentSelectedEquipment;
  157. equipmentIndex = allEquipment.FindIndex(StartIndex);
  158. }
  159. equipmentIndex++;
  160. component.CurrentSelectedEquipment = equipmentIndex >= allEquipment.Count
  161. ? null
  162. : allEquipment[equipmentIndex];
  163. var popupString = component.CurrentSelectedEquipment != null
  164. ? Loc.GetString("mech-equipment-select-popup", ("item", component.CurrentSelectedEquipment))
  165. : Loc.GetString("mech-equipment-select-none-popup");
  166. if (_net.IsServer)
  167. _popup.PopupEntity(popupString, uid);
  168. Dirty(uid, component);
  169. }
  170. /// <summary>
  171. /// Inserts an equipment item into the mech.
  172. /// </summary>
  173. /// <param name="uid"></param>
  174. /// <param name="toInsert"></param>
  175. /// <param name="component"></param>
  176. /// <param name="equipmentComponent"></param>
  177. public void InsertEquipment(EntityUid uid, EntityUid toInsert, MechComponent? component = null,
  178. MechEquipmentComponent? equipmentComponent = null)
  179. {
  180. if (!Resolve(uid, ref component))
  181. return;
  182. if (!Resolve(toInsert, ref equipmentComponent))
  183. return;
  184. if (component.EquipmentContainer.ContainedEntities.Count >= component.MaxEquipmentAmount)
  185. return;
  186. if (_whitelistSystem.IsWhitelistFail(component.EquipmentWhitelist, toInsert))
  187. return;
  188. equipmentComponent.EquipmentOwner = uid;
  189. _container.Insert(toInsert, component.EquipmentContainer);
  190. var ev = new MechEquipmentInsertedEvent(uid);
  191. RaiseLocalEvent(toInsert, ref ev);
  192. UpdateUserInterface(uid, component);
  193. }
  194. /// <summary>
  195. /// Removes an equipment item from a mech.
  196. /// </summary>
  197. /// <param name="uid"></param>
  198. /// <param name="toRemove"></param>
  199. /// <param name="component"></param>
  200. /// <param name="equipmentComponent"></param>
  201. /// <param name="forced">Whether or not the removal can be cancelled</param>
  202. public void RemoveEquipment(EntityUid uid, EntityUid toRemove, MechComponent? component = null,
  203. MechEquipmentComponent? equipmentComponent = null, bool forced = false)
  204. {
  205. if (!Resolve(uid, ref component))
  206. return;
  207. if (!Resolve(toRemove, ref equipmentComponent))
  208. return;
  209. if (!forced)
  210. {
  211. var attemptev = new AttemptRemoveMechEquipmentEvent();
  212. RaiseLocalEvent(toRemove, ref attemptev);
  213. if (attemptev.Cancelled)
  214. return;
  215. }
  216. var ev = new MechEquipmentRemovedEvent(uid);
  217. RaiseLocalEvent(toRemove, ref ev);
  218. if (component.CurrentSelectedEquipment == toRemove)
  219. CycleEquipment(uid, component);
  220. equipmentComponent.EquipmentOwner = null;
  221. _container.Remove(toRemove, component.EquipmentContainer);
  222. UpdateUserInterface(uid, component);
  223. }
  224. /// <summary>
  225. /// Attempts to change the amount of energy in the mech.
  226. /// </summary>
  227. /// <param name="uid">The mech itself</param>
  228. /// <param name="delta">The change in energy</param>
  229. /// <param name="component"></param>
  230. /// <returns>If the energy was successfully changed.</returns>
  231. public virtual bool TryChangeEnergy(EntityUid uid, FixedPoint2 delta, MechComponent? component = null)
  232. {
  233. if (!Resolve(uid, ref component))
  234. return false;
  235. if (component.Energy + delta < 0)
  236. return false;
  237. component.Energy = FixedPoint2.Clamp(component.Energy + delta, 0, component.MaxEnergy);
  238. Dirty(uid, component);
  239. UpdateUserInterface(uid, component);
  240. return true;
  241. }
  242. /// <summary>
  243. /// Sets the integrity of the mech.
  244. /// </summary>
  245. /// <param name="uid">The mech itself</param>
  246. /// <param name="value">The value the integrity will be set at</param>
  247. /// <param name="component"></param>
  248. public void SetIntegrity(EntityUid uid, FixedPoint2 value, MechComponent? component = null)
  249. {
  250. if (!Resolve(uid, ref component))
  251. return;
  252. component.Integrity = FixedPoint2.Clamp(value, 0, component.MaxIntegrity);
  253. if (component.Integrity <= 0)
  254. {
  255. BreakMech(uid, component);
  256. }
  257. else if (component.Broken)
  258. {
  259. component.Broken = false;
  260. UpdateAppearance(uid, component);
  261. }
  262. Dirty(uid, component);
  263. UpdateUserInterface(uid, component);
  264. }
  265. /// <summary>
  266. /// Checks if the pilot is present
  267. /// </summary>
  268. /// <param name="component"></param>
  269. /// <returns>Whether or not the pilot is present</returns>
  270. public bool IsEmpty(MechComponent component)
  271. {
  272. return component.PilotSlot.ContainedEntity == null;
  273. }
  274. /// <summary>
  275. /// Checks if an entity can be inserted into the mech.
  276. /// </summary>
  277. /// <param name="uid"></param>
  278. /// <param name="toInsert"></param>
  279. /// <param name="component"></param>
  280. /// <returns></returns>
  281. public bool CanInsert(EntityUid uid, EntityUid toInsert, MechComponent? component = null)
  282. {
  283. if (!Resolve(uid, ref component))
  284. return false;
  285. return IsEmpty(component) && _actionBlocker.CanMove(toInsert);
  286. }
  287. /// <summary>
  288. /// Updates the user interface
  289. /// </summary>
  290. /// <remarks>
  291. /// This is defined here so that UI updates can be accessed from shared.
  292. /// </remarks>
  293. public virtual void UpdateUserInterface(EntityUid uid, MechComponent? component = null)
  294. {
  295. }
  296. /// <summary>
  297. /// Attempts to insert a pilot into the mech.
  298. /// </summary>
  299. /// <param name="uid"></param>
  300. /// <param name="toInsert"></param>
  301. /// <param name="component"></param>
  302. /// <returns>Whether or not the entity was inserted</returns>
  303. public bool TryInsert(EntityUid uid, EntityUid? toInsert, MechComponent? component = null)
  304. {
  305. if (!Resolve(uid, ref component))
  306. return false;
  307. if (toInsert == null || component.PilotSlot.ContainedEntity == toInsert)
  308. return false;
  309. if (!CanInsert(uid, toInsert.Value, component))
  310. return false;
  311. SetupUser(uid, toInsert.Value);
  312. _container.Insert(toInsert.Value, component.PilotSlot);
  313. UpdateAppearance(uid, component);
  314. return true;
  315. }
  316. /// <summary>
  317. /// Attempts to eject the current pilot from the mech
  318. /// </summary>
  319. /// <param name="uid"></param>
  320. /// <param name="component"></param>
  321. /// <returns>Whether or not the pilot was ejected.</returns>
  322. public bool TryEject(EntityUid uid, MechComponent? component = null)
  323. {
  324. if (!Resolve(uid, ref component))
  325. return false;
  326. if (component.PilotSlot.ContainedEntity == null)
  327. return false;
  328. var pilot = component.PilotSlot.ContainedEntity.Value;
  329. RemoveUser(uid, pilot);
  330. _container.RemoveEntity(uid, pilot);
  331. UpdateAppearance(uid, component);
  332. return true;
  333. }
  334. private void OnGetMeleeWeapon(EntityUid uid, MechPilotComponent component, GetMeleeWeaponEvent args)
  335. {
  336. if (args.Handled)
  337. return;
  338. if (!TryComp<MechComponent>(component.Mech, out var mech))
  339. return;
  340. var weapon = mech.CurrentSelectedEquipment ?? component.Mech;
  341. args.Weapon = weapon;
  342. args.Handled = true;
  343. }
  344. private void OnCanAttackFromContainer(EntityUid uid, MechPilotComponent component, CanAttackFromContainerEvent args)
  345. {
  346. args.CanAttack = true;
  347. }
  348. private void OnAttackAttempt(EntityUid uid, MechPilotComponent component, AttackAttemptEvent args)
  349. {
  350. if (args.Target == component.Mech)
  351. args.Cancel();
  352. }
  353. private void UpdateAppearance(EntityUid uid, MechComponent? component = null,
  354. AppearanceComponent? appearance = null)
  355. {
  356. if (!Resolve(uid, ref component, ref appearance, false))
  357. return;
  358. _appearance.SetData(uid, MechVisuals.Open, IsEmpty(component), appearance);
  359. _appearance.SetData(uid, MechVisuals.Broken, component.Broken, appearance);
  360. }
  361. private void OnDragDrop(EntityUid uid, MechComponent component, ref DragDropTargetEvent args)
  362. {
  363. if (args.Handled)
  364. return;
  365. args.Handled = true;
  366. var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Dragged, component.EntryDelay, new MechEntryEvent(), uid, target: uid)
  367. {
  368. BreakOnMove = true,
  369. };
  370. _doAfter.TryStartDoAfter(doAfterEventArgs);
  371. }
  372. private void OnCanDragDrop(EntityUid uid, MechComponent component, ref CanDropTargetEvent args)
  373. {
  374. args.Handled = true;
  375. args.CanDrop |= !component.Broken && CanInsert(uid, args.Dragged, component);
  376. }
  377. }
  378. /// <summary>
  379. /// Event raised when the battery is successfully removed from the mech,
  380. /// on both success and failure
  381. /// </summary>
  382. [Serializable, NetSerializable]
  383. public sealed partial class RemoveBatteryEvent : SimpleDoAfterEvent
  384. {
  385. }
  386. /// <summary>
  387. /// Event raised when a person removes someone from a mech,
  388. /// on both success and failure
  389. /// </summary>
  390. [Serializable, NetSerializable]
  391. public sealed partial class MechExitEvent : SimpleDoAfterEvent
  392. {
  393. }
  394. /// <summary>
  395. /// Event raised when a person enters a mech, on both success and failure
  396. /// </summary>
  397. [Serializable, NetSerializable]
  398. public sealed partial class MechEntryEvent : SimpleDoAfterEvent
  399. {
  400. }