MechSystem.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. using System.Linq;
  2. using Content.Server.Atmos.EntitySystems;
  3. using Content.Server.Mech.Components;
  4. using Content.Server.Power.Components;
  5. using Content.Server.Power.EntitySystems;
  6. using Content.Shared.ActionBlocker;
  7. using Content.Shared.Damage;
  8. using Content.Shared.DoAfter;
  9. using Content.Shared.FixedPoint;
  10. using Content.Shared.Interaction;
  11. using Content.Shared.Mech;
  12. using Content.Shared.Mech.Components;
  13. using Content.Shared.Mech.EntitySystems;
  14. using Content.Shared.Movement.Events;
  15. using Content.Shared.Popups;
  16. using Content.Shared.Tools.Components;
  17. using Content.Shared.Verbs;
  18. using Content.Shared.Wires;
  19. using Content.Server.Body.Systems;
  20. using Content.Shared.Tools.Systems;
  21. using Robust.Server.Containers;
  22. using Robust.Server.GameObjects;
  23. using Robust.Shared.Containers;
  24. using Robust.Shared.Player;
  25. using Content.Shared.Whitelist;
  26. namespace Content.Server.Mech.Systems;
  27. /// <inheritdoc/>
  28. public sealed partial class MechSystem : SharedMechSystem
  29. {
  30. [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
  31. [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
  32. [Dependency] private readonly BatterySystem _battery = default!;
  33. [Dependency] private readonly ContainerSystem _container = default!;
  34. [Dependency] private readonly DamageableSystem _damageable = default!;
  35. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  36. [Dependency] private readonly SharedPopupSystem _popup = default!;
  37. [Dependency] private readonly UserInterfaceSystem _ui = default!;
  38. [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
  39. [Dependency] private readonly SharedToolSystem _toolSystem = default!;
  40. /// <inheritdoc/>
  41. public override void Initialize()
  42. {
  43. base.Initialize();
  44. SubscribeLocalEvent<MechComponent, InteractUsingEvent>(OnInteractUsing);
  45. SubscribeLocalEvent<MechComponent, EntInsertedIntoContainerMessage>(OnInsertBattery);
  46. SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
  47. SubscribeLocalEvent<MechComponent, GetVerbsEvent<AlternativeVerb>>(OnAlternativeVerb);
  48. SubscribeLocalEvent<MechComponent, MechOpenUiEvent>(OnOpenUi);
  49. SubscribeLocalEvent<MechComponent, RemoveBatteryEvent>(OnRemoveBattery);
  50. SubscribeLocalEvent<MechComponent, MechEntryEvent>(OnMechEntry);
  51. SubscribeLocalEvent<MechComponent, MechExitEvent>(OnMechExit);
  52. SubscribeLocalEvent<MechComponent, DamageChangedEvent>(OnDamageChanged);
  53. SubscribeLocalEvent<MechComponent, MechEquipmentRemoveMessage>(OnRemoveEquipmentMessage);
  54. SubscribeLocalEvent<MechComponent, UpdateCanMoveEvent>(OnMechCanMoveEvent);
  55. SubscribeLocalEvent<MechPilotComponent, ToolUserAttemptUseEvent>(OnToolUseAttempt);
  56. SubscribeLocalEvent<MechPilotComponent, InhaleLocationEvent>(OnInhale);
  57. SubscribeLocalEvent<MechPilotComponent, ExhaleLocationEvent>(OnExhale);
  58. SubscribeLocalEvent<MechPilotComponent, AtmosExposedGetAirEvent>(OnExpose);
  59. SubscribeLocalEvent<MechAirComponent, GetFilterAirEvent>(OnGetFilterAir);
  60. #region Equipment UI message relays
  61. SubscribeLocalEvent<MechComponent, MechGrabberEjectMessage>(ReceiveEquipmentUiMesssages);
  62. SubscribeLocalEvent<MechComponent, MechSoundboardPlayMessage>(ReceiveEquipmentUiMesssages);
  63. #endregion
  64. }
  65. private void OnMechCanMoveEvent(EntityUid uid, MechComponent component, UpdateCanMoveEvent args)
  66. {
  67. if (component.Broken || component.Integrity <= 0 || component.Energy <= 0)
  68. args.Cancel();
  69. }
  70. private void OnInteractUsing(EntityUid uid, MechComponent component, InteractUsingEvent args)
  71. {
  72. if (TryComp<WiresPanelComponent>(uid, out var panel) && !panel.Open)
  73. return;
  74. if (component.BatterySlot.ContainedEntity == null && TryComp<BatteryComponent>(args.Used, out var battery))
  75. {
  76. InsertBattery(uid, args.Used, component, battery);
  77. _actionBlocker.UpdateCanMove(uid);
  78. return;
  79. }
  80. if (_toolSystem.HasQuality(args.Used, "Prying") && component.BatterySlot.ContainedEntity != null)
  81. {
  82. var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.BatteryRemovalDelay,
  83. new RemoveBatteryEvent(), uid, target: uid, used: args.Target)
  84. {
  85. BreakOnMove = true
  86. };
  87. _doAfter.TryStartDoAfter(doAfterEventArgs);
  88. }
  89. }
  90. private void OnInsertBattery(EntityUid uid, MechComponent component, EntInsertedIntoContainerMessage args)
  91. {
  92. if (args.Container != component.BatterySlot || !TryComp<BatteryComponent>(args.Entity, out var battery))
  93. return;
  94. component.Energy = battery.CurrentCharge;
  95. component.MaxEnergy = battery.MaxCharge;
  96. Dirty(uid, component);
  97. _actionBlocker.UpdateCanMove(uid);
  98. }
  99. private void OnRemoveBattery(EntityUid uid, MechComponent component, RemoveBatteryEvent args)
  100. {
  101. if (args.Cancelled || args.Handled)
  102. return;
  103. RemoveBattery(uid, component);
  104. _actionBlocker.UpdateCanMove(uid);
  105. args.Handled = true;
  106. }
  107. private void OnMapInit(EntityUid uid, MechComponent component, MapInitEvent args)
  108. {
  109. var xform = Transform(uid);
  110. // TODO: this should use containerfill?
  111. foreach (var equipment in component.StartingEquipment)
  112. {
  113. var ent = Spawn(equipment, xform.Coordinates);
  114. InsertEquipment(uid, ent, component);
  115. }
  116. // TODO: this should just be damage and battery
  117. component.Integrity = component.MaxIntegrity;
  118. component.Energy = component.MaxEnergy;
  119. _actionBlocker.UpdateCanMove(uid);
  120. Dirty(uid, component);
  121. }
  122. private void OnRemoveEquipmentMessage(EntityUid uid, MechComponent component, MechEquipmentRemoveMessage args)
  123. {
  124. var equip = GetEntity(args.Equipment);
  125. if (!Exists(equip) || Deleted(equip))
  126. return;
  127. if (!component.EquipmentContainer.ContainedEntities.Contains(equip))
  128. return;
  129. RemoveEquipment(uid, equip, component);
  130. }
  131. private void OnOpenUi(EntityUid uid, MechComponent component, MechOpenUiEvent args)
  132. {
  133. args.Handled = true;
  134. ToggleMechUi(uid, component);
  135. }
  136. private void OnToolUseAttempt(EntityUid uid, MechPilotComponent component, ref ToolUserAttemptUseEvent args)
  137. {
  138. if (args.Target == component.Mech)
  139. args.Cancelled = true;
  140. }
  141. private void OnAlternativeVerb(EntityUid uid, MechComponent component, GetVerbsEvent<AlternativeVerb> args)
  142. {
  143. if (!args.CanAccess || !args.CanInteract || component.Broken)
  144. return;
  145. if (CanInsert(uid, args.User, component))
  146. {
  147. var enterVerb = new AlternativeVerb
  148. {
  149. Text = Loc.GetString("mech-verb-enter"),
  150. Act = () =>
  151. {
  152. var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.EntryDelay, new MechEntryEvent(), uid, target: uid)
  153. {
  154. BreakOnMove = true,
  155. };
  156. _doAfter.TryStartDoAfter(doAfterEventArgs);
  157. }
  158. };
  159. var openUiVerb = new AlternativeVerb //can't hijack someone else's mech
  160. {
  161. Act = () => ToggleMechUi(uid, component, args.User),
  162. Text = Loc.GetString("mech-ui-open-verb")
  163. };
  164. args.Verbs.Add(enterVerb);
  165. args.Verbs.Add(openUiVerb);
  166. }
  167. else if (!IsEmpty(component))
  168. {
  169. var ejectVerb = new AlternativeVerb
  170. {
  171. Text = Loc.GetString("mech-verb-exit"),
  172. Priority = 1, // Promote to top to make ejecting the ALT-click action
  173. Act = () =>
  174. {
  175. if (args.User == uid || args.User == component.PilotSlot.ContainedEntity)
  176. {
  177. TryEject(uid, component);
  178. return;
  179. }
  180. var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.ExitDelay, new MechExitEvent(), uid, target: uid)
  181. {
  182. BreakOnMove = true,
  183. };
  184. _popup.PopupEntity(Loc.GetString("mech-eject-pilot-alert", ("item", uid), ("user", args.User)), uid, PopupType.Large);
  185. _doAfter.TryStartDoAfter(doAfterEventArgs);
  186. }
  187. };
  188. args.Verbs.Add(ejectVerb);
  189. }
  190. }
  191. private void OnMechEntry(EntityUid uid, MechComponent component, MechEntryEvent args)
  192. {
  193. if (args.Cancelled || args.Handled)
  194. return;
  195. if (_whitelistSystem.IsWhitelistFail(component.PilotWhitelist, args.User))
  196. {
  197. _popup.PopupEntity(Loc.GetString("mech-no-enter", ("item", uid)), args.User);
  198. return;
  199. }
  200. TryInsert(uid, args.Args.User, component);
  201. _actionBlocker.UpdateCanMove(uid);
  202. args.Handled = true;
  203. }
  204. private void OnMechExit(EntityUid uid, MechComponent component, MechExitEvent args)
  205. {
  206. if (args.Cancelled || args.Handled)
  207. return;
  208. TryEject(uid, component);
  209. args.Handled = true;
  210. }
  211. private void OnDamageChanged(EntityUid uid, MechComponent component, DamageChangedEvent args)
  212. {
  213. var integrity = component.MaxIntegrity - args.Damageable.TotalDamage;
  214. SetIntegrity(uid, integrity, component);
  215. if (args.DamageIncreased &&
  216. args.DamageDelta != null &&
  217. component.PilotSlot.ContainedEntity != null)
  218. {
  219. var damage = args.DamageDelta * component.MechToPilotDamageMultiplier;
  220. _damageable.TryChangeDamage(component.PilotSlot.ContainedEntity, damage);
  221. }
  222. }
  223. private void ToggleMechUi(EntityUid uid, MechComponent? component = null, EntityUid? user = null)
  224. {
  225. if (!Resolve(uid, ref component))
  226. return;
  227. user ??= component.PilotSlot.ContainedEntity;
  228. if (user == null)
  229. return;
  230. if (!TryComp<ActorComponent>(user, out var actor))
  231. return;
  232. _ui.TryToggleUi(uid, MechUiKey.Key, actor.PlayerSession);
  233. UpdateUserInterface(uid, component);
  234. }
  235. private void ReceiveEquipmentUiMesssages<T>(EntityUid uid, MechComponent component, T args) where T : MechEquipmentUiMessage
  236. {
  237. var ev = new MechEquipmentUiMessageRelayEvent(args);
  238. var allEquipment = new List<EntityUid>(component.EquipmentContainer.ContainedEntities);
  239. var argEquip = GetEntity(args.Equipment);
  240. foreach (var equipment in allEquipment)
  241. {
  242. if (argEquip == equipment)
  243. RaiseLocalEvent(equipment, ev);
  244. }
  245. }
  246. public override void UpdateUserInterface(EntityUid uid, MechComponent? component = null)
  247. {
  248. if (!Resolve(uid, ref component))
  249. return;
  250. base.UpdateUserInterface(uid, component);
  251. var ev = new MechEquipmentUiStateReadyEvent();
  252. foreach (var ent in component.EquipmentContainer.ContainedEntities)
  253. {
  254. RaiseLocalEvent(ent, ev);
  255. }
  256. var state = new MechBoundUiState
  257. {
  258. EquipmentStates = ev.States
  259. };
  260. _ui.SetUiState(uid, MechUiKey.Key, state);
  261. }
  262. public override void BreakMech(EntityUid uid, MechComponent? component = null)
  263. {
  264. base.BreakMech(uid, component);
  265. _ui.CloseUi(uid, MechUiKey.Key);
  266. _actionBlocker.UpdateCanMove(uid);
  267. }
  268. public override bool TryChangeEnergy(EntityUid uid, FixedPoint2 delta, MechComponent? component = null)
  269. {
  270. if (!Resolve(uid, ref component))
  271. return false;
  272. if (!base.TryChangeEnergy(uid, delta, component))
  273. return false;
  274. var battery = component.BatterySlot.ContainedEntity;
  275. if (battery == null)
  276. return false;
  277. if (!TryComp<BatteryComponent>(battery, out var batteryComp))
  278. return false;
  279. _battery.SetCharge(battery!.Value, batteryComp.CurrentCharge + delta.Float(), batteryComp);
  280. if (batteryComp.CurrentCharge != component.Energy) //if there's a discrepency, we have to resync them
  281. {
  282. Log.Debug($"Battery charge was not equal to mech charge. Battery {batteryComp.CurrentCharge}. Mech {component.Energy}");
  283. component.Energy = batteryComp.CurrentCharge;
  284. Dirty(uid, component);
  285. }
  286. _actionBlocker.UpdateCanMove(uid);
  287. return true;
  288. }
  289. public void InsertBattery(EntityUid uid, EntityUid toInsert, MechComponent? component = null, BatteryComponent? battery = null)
  290. {
  291. if (!Resolve(uid, ref component, false))
  292. return;
  293. if (!Resolve(toInsert, ref battery, false))
  294. return;
  295. _container.Insert(toInsert, component.BatterySlot);
  296. component.Energy = battery.CurrentCharge;
  297. component.MaxEnergy = battery.MaxCharge;
  298. _actionBlocker.UpdateCanMove(uid);
  299. Dirty(uid, component);
  300. UpdateUserInterface(uid, component);
  301. }
  302. public void RemoveBattery(EntityUid uid, MechComponent? component = null)
  303. {
  304. if (!Resolve(uid, ref component))
  305. return;
  306. _container.EmptyContainer(component.BatterySlot);
  307. component.Energy = 0;
  308. component.MaxEnergy = 0;
  309. _actionBlocker.UpdateCanMove(uid);
  310. Dirty(uid, component);
  311. UpdateUserInterface(uid, component);
  312. }
  313. #region Atmos Handling
  314. private void OnInhale(EntityUid uid, MechPilotComponent component, InhaleLocationEvent args)
  315. {
  316. if (!TryComp<MechComponent>(component.Mech, out var mech) ||
  317. !TryComp<MechAirComponent>(component.Mech, out var mechAir))
  318. {
  319. return;
  320. }
  321. if (mech.Airtight)
  322. args.Gas = mechAir.Air;
  323. }
  324. private void OnExhale(EntityUid uid, MechPilotComponent component, ExhaleLocationEvent args)
  325. {
  326. if (!TryComp<MechComponent>(component.Mech, out var mech) ||
  327. !TryComp<MechAirComponent>(component.Mech, out var mechAir))
  328. {
  329. return;
  330. }
  331. if (mech.Airtight)
  332. args.Gas = mechAir.Air;
  333. }
  334. private void OnExpose(EntityUid uid, MechPilotComponent component, ref AtmosExposedGetAirEvent args)
  335. {
  336. if (args.Handled)
  337. return;
  338. if (!TryComp(component.Mech, out MechComponent? mech))
  339. return;
  340. if (mech.Airtight && TryComp(component.Mech, out MechAirComponent? air))
  341. {
  342. args.Handled = true;
  343. args.Gas = air.Air;
  344. return;
  345. }
  346. args.Gas = _atmosphere.GetContainingMixture(component.Mech, excite: args.Excite);
  347. args.Handled = true;
  348. }
  349. private void OnGetFilterAir(EntityUid uid, MechAirComponent comp, ref GetFilterAirEvent args)
  350. {
  351. if (args.Air != null)
  352. return;
  353. // only airtight mechs get internal air
  354. if (!TryComp<MechComponent>(uid, out var mech) || !mech.Airtight)
  355. return;
  356. args.Air = comp.Air;
  357. }
  358. #endregion
  359. }