BorgSystem.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. using Content.Server.Actions;
  2. using Content.Server.Administration.Logs;
  3. using Content.Server.Administration.Managers;
  4. using Content.Server.DeviceNetwork.Systems;
  5. using Content.Server.Explosion.EntitySystems;
  6. using Content.Server.Hands.Systems;
  7. using Content.Server.PowerCell;
  8. using Content.Shared.Alert;
  9. using Content.Shared.Database;
  10. using Content.Shared.IdentityManagement;
  11. using Content.Shared.Interaction;
  12. using Content.Shared.Item.ItemToggle.Components;
  13. using Content.Shared.Mind;
  14. using Content.Shared.Mind.Components;
  15. using Content.Shared.Mobs;
  16. using Content.Shared.Mobs.Systems;
  17. using Content.Shared.Movement.Systems;
  18. using Content.Shared.Pointing;
  19. using Content.Shared.PowerCell;
  20. using Content.Shared.PowerCell.Components;
  21. using Content.Shared.Roles;
  22. using Content.Shared.Silicons.Borgs;
  23. using Content.Shared.Silicons.Borgs.Components;
  24. using Content.Shared.Throwing;
  25. using Content.Shared.Whitelist;
  26. using Content.Shared.Wires;
  27. using Robust.Server.GameObjects;
  28. using Robust.Shared.Containers;
  29. using Robust.Shared.Player;
  30. using Robust.Shared.Random;
  31. using Robust.Shared.Timing;
  32. namespace Content.Server.Silicons.Borgs;
  33. /// <inheritdoc/>
  34. public sealed partial class BorgSystem : SharedBorgSystem
  35. {
  36. [Dependency] private readonly IAdminLogManager _adminLog = default!;
  37. [Dependency] private readonly IBanManager _banManager = default!;
  38. [Dependency] private readonly IGameTiming _timing = default!;
  39. [Dependency] private readonly IRobustRandom _random = default!;
  40. [Dependency] private readonly ActionsSystem _actions = default!;
  41. [Dependency] private readonly AlertsSystem _alerts = default!;
  42. [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
  43. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  44. [Dependency] private readonly TriggerSystem _trigger = default!;
  45. [Dependency] private readonly HandsSystem _hands = default!;
  46. [Dependency] private readonly MetaDataSystem _metaData = default!;
  47. [Dependency] private readonly SharedMindSystem _mind = default!;
  48. [Dependency] private readonly MobStateSystem _mobState = default!;
  49. [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
  50. [Dependency] private readonly PowerCellSystem _powerCell = default!;
  51. [Dependency] private readonly ThrowingSystem _throwing = default!;
  52. [Dependency] private readonly UserInterfaceSystem _ui = default!;
  53. [Dependency] private readonly SharedContainerSystem _container = default!;
  54. [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
  55. /// <inheritdoc/>
  56. public override void Initialize()
  57. {
  58. base.Initialize();
  59. SubscribeLocalEvent<BorgChassisComponent, MapInitEvent>(OnMapInit);
  60. SubscribeLocalEvent<BorgChassisComponent, AfterInteractUsingEvent>(OnChassisInteractUsing);
  61. SubscribeLocalEvent<BorgChassisComponent, MindAddedMessage>(OnMindAdded);
  62. SubscribeLocalEvent<BorgChassisComponent, MindRemovedMessage>(OnMindRemoved);
  63. SubscribeLocalEvent<BorgChassisComponent, MobStateChangedEvent>(OnMobStateChanged);
  64. SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
  65. SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
  66. SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
  67. SubscribeLocalEvent<BorgChassisComponent, ItemToggledEvent>(OnToggled);
  68. SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
  69. SubscribeLocalEvent<BorgBrainComponent, PointAttemptEvent>(OnBrainPointAttempt);
  70. InitializeModules();
  71. InitializeMMI();
  72. InitializeUI();
  73. InitializeTransponder();
  74. }
  75. private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args)
  76. {
  77. UpdateBatteryAlert((uid, component));
  78. _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
  79. }
  80. private void OnChassisInteractUsing(EntityUid uid, BorgChassisComponent component, AfterInteractUsingEvent args)
  81. {
  82. if (!args.CanReach || args.Handled || uid == args.User)
  83. return;
  84. var used = args.Used;
  85. TryComp<BorgBrainComponent>(used, out var brain);
  86. TryComp<BorgModuleComponent>(used, out var module);
  87. if (TryComp<WiresPanelComponent>(uid, out var panel) && !panel.Open)
  88. {
  89. if (brain != null || module != null)
  90. {
  91. Popup.PopupEntity(Loc.GetString("borg-panel-not-open"), uid, args.User);
  92. }
  93. return;
  94. }
  95. if (component.BrainEntity == null && brain != null &&
  96. _whitelistSystem.IsWhitelistPassOrNull(component.BrainWhitelist, used))
  97. {
  98. if (_mind.TryGetMind(used, out _, out var mind) && mind.Session != null)
  99. {
  100. if (!CanPlayerBeBorged(mind.Session))
  101. {
  102. Popup.PopupEntity(Loc.GetString("borg-player-not-allowed"), used, args.User);
  103. return;
  104. }
  105. }
  106. _container.Insert(used, component.BrainContainer);
  107. _adminLog.Add(LogType.Action, LogImpact.Medium,
  108. $"{ToPrettyString(args.User):player} installed brain {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
  109. args.Handled = true;
  110. UpdateUI(uid, component);
  111. }
  112. if (module != null && CanInsertModule(uid, used, component, module, args.User))
  113. {
  114. InsertModule((uid, component), used);
  115. _adminLog.Add(LogType.Action, LogImpact.Low,
  116. $"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
  117. args.Handled = true;
  118. UpdateUI(uid, component);
  119. }
  120. }
  121. /// <summary>
  122. /// Inserts a new module into a borg, the same as if a player inserted it manually.
  123. /// </summary>
  124. /// <para>
  125. /// This does not run checks to see if the borg is actually allowed to be inserted, such as whitelists.
  126. /// </para>
  127. /// <param name="ent">The borg to insert into.</param>
  128. /// <param name="module">The module to insert.</param>
  129. public void InsertModule(Entity<BorgChassisComponent> ent, EntityUid module)
  130. {
  131. _container.Insert(module, ent.Comp.ModuleContainer);
  132. }
  133. // todo: consider transferring over the ghost role? managing that might suck.
  134. protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
  135. {
  136. base.OnInserted(uid, component, args);
  137. if (HasComp<BorgBrainComponent>(args.Entity) && _mind.TryGetMind(args.Entity, out var mindId, out var mind) && args.Container == component.BrainContainer)
  138. {
  139. _mind.TransferTo(mindId, uid, mind: mind);
  140. }
  141. }
  142. protected override void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
  143. {
  144. base.OnRemoved(uid, component, args);
  145. if (HasComp<BorgBrainComponent>(args.Entity) && _mind.TryGetMind(uid, out var mindId, out var mind) && args.Container == component.BrainContainer)
  146. {
  147. _mind.TransferTo(mindId, args.Entity, mind: mind);
  148. }
  149. }
  150. private void OnMindAdded(EntityUid uid, BorgChassisComponent component, MindAddedMessage args)
  151. {
  152. BorgActivate(uid, component);
  153. }
  154. private void OnMindRemoved(EntityUid uid, BorgChassisComponent component, MindRemovedMessage args)
  155. {
  156. BorgDeactivate(uid, component);
  157. }
  158. private void OnMobStateChanged(EntityUid uid, BorgChassisComponent component, MobStateChangedEvent args)
  159. {
  160. if (args.NewMobState == MobState.Alive)
  161. {
  162. if (_mind.TryGetMind(uid, out _, out _))
  163. _powerCell.SetDrawEnabled(uid, true);
  164. }
  165. else
  166. {
  167. _powerCell.SetDrawEnabled(uid, false);
  168. }
  169. }
  170. private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, PowerCellChangedEvent args)
  171. {
  172. UpdateBatteryAlert((uid, component));
  173. // if we aren't drawing and suddenly get enough power to draw again, reeanble.
  174. if (_powerCell.HasDrawCharge(uid))
  175. {
  176. Toggle.TryActivate(uid);
  177. }
  178. UpdateUI(uid, component);
  179. }
  180. private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
  181. {
  182. Toggle.TryDeactivate(uid);
  183. UpdateUI(uid, component);
  184. }
  185. private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetCharactedDeadIcEvent args)
  186. {
  187. args.Dead = true;
  188. }
  189. private void OnToggled(Entity<BorgChassisComponent> ent, ref ItemToggledEvent args)
  190. {
  191. var (uid, comp) = ent;
  192. if (args.Activated)
  193. InstallAllModules(uid, comp);
  194. else
  195. DisableAllModules(uid, comp);
  196. // only enable the powerdraw if there is a player in the chassis
  197. var drawing = _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(ent);
  198. _powerCell.SetDrawEnabled(uid, drawing);
  199. UpdateUI(uid, comp);
  200. _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
  201. }
  202. private void OnBrainMindAdded(EntityUid uid, BorgBrainComponent component, MindAddedMessage args)
  203. {
  204. if (!Container.TryGetOuterContainer(uid, Transform(uid), out var container))
  205. return;
  206. var containerEnt = container.Owner;
  207. if (!TryComp<BorgChassisComponent>(containerEnt, out var chassisComponent) ||
  208. container.ID != chassisComponent.BrainContainerId)
  209. return;
  210. if (!_mind.TryGetMind(uid, out var mindId, out var mind) || mind.Session == null)
  211. return;
  212. if (!CanPlayerBeBorged(mind.Session))
  213. {
  214. Popup.PopupEntity(Loc.GetString("borg-player-not-allowed-eject"), uid);
  215. Container.RemoveEntity(containerEnt, uid);
  216. _throwing.TryThrow(uid, _random.NextVector2() * 5, 5f);
  217. return;
  218. }
  219. _mind.TransferTo(mindId, containerEnt, mind: mind);
  220. }
  221. private void OnBrainPointAttempt(EntityUid uid, BorgBrainComponent component, PointAttemptEvent args)
  222. {
  223. args.Cancel();
  224. }
  225. private void UpdateBatteryAlert(Entity<BorgChassisComponent> ent, PowerCellSlotComponent? slotComponent = null)
  226. {
  227. if (!_powerCell.TryGetBatteryFromSlot(ent, out var battery, slotComponent))
  228. {
  229. _alerts.ClearAlert(ent, ent.Comp.BatteryAlert);
  230. _alerts.ShowAlert(ent, ent.Comp.NoBatteryAlert);
  231. return;
  232. }
  233. var chargePercent = (short) MathF.Round(battery.CurrentCharge / battery.MaxCharge * 10f);
  234. // we make sure 0 only shows if they have absolutely no battery.
  235. // also account for floating point imprecision
  236. if (chargePercent == 0 && _powerCell.HasDrawCharge(ent, cell: slotComponent))
  237. {
  238. chargePercent = 1;
  239. }
  240. _alerts.ClearAlert(ent, ent.Comp.NoBatteryAlert);
  241. _alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent);
  242. }
  243. /// <summary>
  244. /// Activates a borg when a player occupies it
  245. /// </summary>
  246. public void BorgActivate(EntityUid uid, BorgChassisComponent component)
  247. {
  248. Popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(uid, EntityManager))), uid);
  249. if (_powerCell.HasDrawCharge(uid))
  250. {
  251. Toggle.TryActivate(uid);
  252. _powerCell.SetDrawEnabled(uid, _mobState.IsAlive(uid));
  253. }
  254. _appearance.SetData(uid, BorgVisuals.HasPlayer, true);
  255. }
  256. /// <summary>
  257. /// Deactivates a borg when a player leaves it.
  258. /// </summary>
  259. public void BorgDeactivate(EntityUid uid, BorgChassisComponent component)
  260. {
  261. Popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(uid, EntityManager))), uid);
  262. Toggle.TryDeactivate(uid);
  263. _powerCell.SetDrawEnabled(uid, false);
  264. _appearance.SetData(uid, BorgVisuals.HasPlayer, false);
  265. }
  266. /// <summary>
  267. /// Checks that a player has fulfilled the requirements for the borg job.
  268. /// If they don't have enough hours, they cannot be placed into a chassis.
  269. /// </summary>
  270. public bool CanPlayerBeBorged(ICommonSession session)
  271. {
  272. return true;
  273. }
  274. }