1
0

GuardianSystem.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. using Content.Server.Body.Systems;
  2. using Content.Server.Popups;
  3. using Content.Shared.Actions;
  4. using Content.Shared.Audio;
  5. using Content.Shared.Damage;
  6. using Content.Shared.DoAfter;
  7. using Content.Shared.Examine;
  8. using Content.Shared.Guardian;
  9. using Content.Shared.Hands.Components;
  10. using Content.Shared.Hands.EntitySystems;
  11. using Content.Shared.Interaction;
  12. using Content.Shared.Interaction.Events;
  13. using Content.Shared.Mobs;
  14. using Content.Shared.Popups;
  15. using Robust.Server.GameObjects;
  16. using Robust.Shared.Audio;
  17. using Robust.Shared.Audio.Systems;
  18. using Robust.Shared.Containers;
  19. using Robust.Shared.Player;
  20. using Robust.Shared.Utility;
  21. namespace Content.Server.Guardian
  22. {
  23. /// <summary>
  24. /// A guardian has a host it's attached to that it fights for. A fighting spirit.
  25. /// </summary>
  26. public sealed class GuardianSystem : EntitySystem
  27. {
  28. [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
  29. [Dependency] private readonly PopupSystem _popupSystem = default!;
  30. [Dependency] private readonly DamageableSystem _damageSystem = default!;
  31. [Dependency] private readonly SharedActionsSystem _actionSystem = default!;
  32. [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
  33. [Dependency] private readonly SharedAudioSystem _audio = default!;
  34. [Dependency] private readonly BodySystem _bodySystem = default!;
  35. [Dependency] private readonly SharedContainerSystem _container = default!;
  36. [Dependency] private readonly SharedTransformSystem _transform = default!;
  37. public override void Initialize()
  38. {
  39. base.Initialize();
  40. SubscribeLocalEvent<GuardianCreatorComponent, UseInHandEvent>(OnCreatorUse);
  41. SubscribeLocalEvent<GuardianCreatorComponent, AfterInteractEvent>(OnCreatorInteract);
  42. SubscribeLocalEvent<GuardianCreatorComponent, ExaminedEvent>(OnCreatorExamine);
  43. SubscribeLocalEvent<GuardianCreatorComponent, GuardianCreatorDoAfterEvent>(OnDoAfter);
  44. SubscribeLocalEvent<GuardianComponent, ComponentShutdown>(OnGuardianShutdown);
  45. SubscribeLocalEvent<GuardianComponent, MoveEvent>(OnGuardianMove);
  46. SubscribeLocalEvent<GuardianComponent, DamageChangedEvent>(OnGuardianDamaged);
  47. SubscribeLocalEvent<GuardianComponent, PlayerAttachedEvent>(OnGuardianPlayerAttached);
  48. SubscribeLocalEvent<GuardianComponent, PlayerDetachedEvent>(OnGuardianPlayerDetached);
  49. SubscribeLocalEvent<GuardianHostComponent, ComponentInit>(OnHostInit);
  50. SubscribeLocalEvent<GuardianHostComponent, MoveEvent>(OnHostMove);
  51. SubscribeLocalEvent<GuardianHostComponent, MobStateChangedEvent>(OnHostStateChange);
  52. SubscribeLocalEvent<GuardianHostComponent, ComponentShutdown>(OnHostShutdown);
  53. SubscribeLocalEvent<GuardianHostComponent, GuardianToggleActionEvent>(OnPerformAction);
  54. SubscribeLocalEvent<GuardianComponent, AttackAttemptEvent>(OnGuardianAttackAttempt);
  55. }
  56. private void OnGuardianShutdown(EntityUid uid, GuardianComponent component, ComponentShutdown args)
  57. {
  58. var host = component.Host;
  59. component.Host = null;
  60. if (!TryComp(host, out GuardianHostComponent? hostComponent))
  61. return;
  62. _container.Remove(uid, hostComponent.GuardianContainer);
  63. hostComponent.HostedGuardian = null;
  64. QueueDel(hostComponent.ActionEntity);
  65. hostComponent.ActionEntity = null;
  66. }
  67. private void OnPerformAction(EntityUid uid, GuardianHostComponent component, GuardianToggleActionEvent args)
  68. {
  69. if (args.Handled)
  70. return;
  71. if (_container.IsEntityInContainer(uid))
  72. {
  73. _popupSystem.PopupEntity(Loc.GetString("guardian-inside-container"), uid, uid);
  74. return;
  75. }
  76. if (component.HostedGuardian != null)
  77. ToggleGuardian(uid, component);
  78. args.Handled = true;
  79. }
  80. private void OnGuardianPlayerDetached(EntityUid uid, GuardianComponent component, PlayerDetachedEvent args)
  81. {
  82. var host = component.Host;
  83. if (!TryComp<GuardianHostComponent>(host, out var hostComponent) || TerminatingOrDeleted(host.Value))
  84. {
  85. QueueDel(uid);
  86. return;
  87. }
  88. RetractGuardian(host.Value, hostComponent, uid, component);
  89. }
  90. private void OnGuardianPlayerAttached(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args)
  91. {
  92. var host = component.Host;
  93. if (!HasComp<GuardianHostComponent>(host))
  94. {
  95. QueueDel(uid);
  96. return;
  97. }
  98. _popupSystem.PopupEntity(Loc.GetString("guardian-available"), host.Value, host.Value);
  99. }
  100. private void OnHostInit(EntityUid uid, GuardianHostComponent component, ComponentInit args)
  101. {
  102. component.GuardianContainer = _container.EnsureContainer<ContainerSlot>(uid, "GuardianContainer");
  103. //_actionSystem.AddAction(uid, ref component.ActionEntity, component.Action);
  104. }
  105. private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args)
  106. {
  107. if (component.HostedGuardian is not { } guardian)
  108. return;
  109. // Ensure held items are dropped before deleting guardian.
  110. if (HasComp<HandsComponent>(guardian))
  111. _bodySystem.GibBody(component.HostedGuardian.Value);
  112. QueueDel(guardian);
  113. QueueDel(component.ActionEntity);
  114. component.ActionEntity = null;
  115. }
  116. private void OnGuardianAttackAttempt(EntityUid uid, GuardianComponent component, AttackAttemptEvent args)
  117. {
  118. if (args.Cancelled || args.Target != component.Host)
  119. return;
  120. // why is this server side code? This should be in shared
  121. _popupSystem.PopupCursor(Loc.GetString("guardian-attack-host"), uid, PopupType.LargeCaution);
  122. args.Cancel();
  123. }
  124. public void ToggleGuardian(EntityUid user, GuardianHostComponent hostComponent)
  125. {
  126. if (!TryComp<GuardianComponent>(hostComponent.HostedGuardian, out var guardianComponent))
  127. return;
  128. if (guardianComponent.GuardianLoose)
  129. RetractGuardian(user, hostComponent, hostComponent.HostedGuardian.Value, guardianComponent);
  130. else
  131. ReleaseGuardian(user, hostComponent, hostComponent.HostedGuardian.Value, guardianComponent);
  132. }
  133. /// <summary>
  134. /// Adds the guardian host component to the user and spawns the guardian inside said component
  135. /// </summary>
  136. private void OnCreatorUse(EntityUid uid, GuardianCreatorComponent component, UseInHandEvent args)
  137. {
  138. if (args.Handled)
  139. return;
  140. args.Handled = true;
  141. UseCreator(args.User, args.User, uid, component);
  142. }
  143. private void OnCreatorInteract(EntityUid uid, GuardianCreatorComponent component, AfterInteractEvent args)
  144. {
  145. if (args.Handled || args.Target == null || !args.CanReach)
  146. return;
  147. args.Handled = true;
  148. UseCreator(args.User, args.Target.Value, uid, component);
  149. }
  150. private void UseCreator(EntityUid user, EntityUid target, EntityUid injector, GuardianCreatorComponent component)
  151. {
  152. if (component.Used)
  153. {
  154. _popupSystem.PopupEntity(Loc.GetString("guardian-activator-empty-invalid-creation"), user, user);
  155. return;
  156. }
  157. // Can only inject things with the component...
  158. if (!HasComp<CanHostGuardianComponent>(target))
  159. {
  160. _popupSystem.PopupEntity(Loc.GetString("guardian-activator-invalid-target"), user, user);
  161. return;
  162. }
  163. // If user is already a host don't duplicate.
  164. if (HasComp<GuardianHostComponent>(target))
  165. {
  166. _popupSystem.PopupEntity(Loc.GetString("guardian-already-present-invalid-creation"), user, user);
  167. return;
  168. }
  169. _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.InjectionDelay, new GuardianCreatorDoAfterEvent(), injector, target: target, used: injector) { BreakOnMove = true });
  170. }
  171. private void OnDoAfter(EntityUid uid, GuardianCreatorComponent component, DoAfterEvent args)
  172. {
  173. if (args.Handled || args.Args.Target == null)
  174. return;
  175. if (args.Cancelled || component.Deleted || component.Used || !_handsSystem.IsHolding(args.Args.User, uid, out _) || HasComp<GuardianHostComponent>(args.Args.Target))
  176. return;
  177. var hostXform = Transform(args.Args.Target.Value);
  178. var host = EnsureComp<GuardianHostComponent>(args.Args.Target.Value);
  179. // Use map position so it's not inadvertantly parented to the host + if it's in a container it spawns outside I guess.
  180. var guardian = Spawn(component.GuardianProto, _transform.GetMapCoordinates(args.Args.Target.Value, xform: hostXform));
  181. _container.Insert(guardian, host.GuardianContainer);
  182. host.HostedGuardian = guardian;
  183. if (TryComp<GuardianComponent>(guardian, out var guardianComp))
  184. {
  185. guardianComp.Host = args.Args.Target.Value;
  186. _audio.PlayPvs("/Audio/Effects/guardian_inject.ogg", args.Args.Target.Value);
  187. _popupSystem.PopupEntity(Loc.GetString("guardian-created"), args.Args.Target.Value, args.Args.Target.Value);
  188. // Exhaust the activator
  189. component.Used = true;
  190. }
  191. else
  192. {
  193. Log.Error($"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}");
  194. QueueDel(guardian);
  195. }
  196. args.Handled = true;
  197. }
  198. /// <summary>
  199. /// Triggers when the host receives damage which puts the host in either critical or killed state
  200. /// </summary>
  201. private void OnHostStateChange(EntityUid uid, GuardianHostComponent component, MobStateChangedEvent args)
  202. {
  203. if (component.HostedGuardian == null)
  204. return;
  205. if (args.NewMobState == MobState.Critical)
  206. {
  207. _popupSystem.PopupEntity(Loc.GetString("guardian-host-critical-warn"), component.HostedGuardian.Value, component.HostedGuardian.Value);
  208. _audio.PlayPvs("/Audio/Effects/guardian_warn.ogg", component.HostedGuardian.Value);
  209. }
  210. else if (args.NewMobState == MobState.Dead)
  211. {
  212. //TODO: Replace WithVariation with datafield
  213. _audio.PlayPvs("/Audio/Voice/Human/malescream_guardian.ogg", uid, AudioParams.Default.WithVariation(0.20f));
  214. RemComp<GuardianHostComponent>(uid);
  215. }
  216. }
  217. /// <summary>
  218. /// Handles guardian receiving damage and splitting it with the host according to his defence percent
  219. /// </summary>
  220. private void OnGuardianDamaged(EntityUid uid, GuardianComponent component, DamageChangedEvent args)
  221. {
  222. if (args.DamageDelta == null || component.Host == null || component.DamageShare == 0)
  223. return;
  224. _damageSystem.TryChangeDamage(
  225. component.Host,
  226. args.DamageDelta * component.DamageShare,
  227. origin: args.Origin,
  228. ignoreResistances: true,
  229. interruptsDoAfters: false);
  230. _popupSystem.PopupEntity(Loc.GetString("guardian-entity-taking-damage"), component.Host.Value, component.Host.Value);
  231. }
  232. /// <summary>
  233. /// Triggers while trying to examine an activator to see if it's used
  234. /// </summary>
  235. private void OnCreatorExamine(EntityUid uid, GuardianCreatorComponent component, ExaminedEvent args)
  236. {
  237. if (component.Used)
  238. args.PushMarkup(Loc.GetString("guardian-activator-empty-examine"));
  239. }
  240. /// <summary>
  241. /// Called every time the host moves, to make sure the distance between the host and the guardian isn't too far
  242. /// </summary>
  243. private void OnHostMove(EntityUid uid, GuardianHostComponent component, ref MoveEvent args)
  244. {
  245. if (!TryComp(component.HostedGuardian, out GuardianComponent? guardianComponent) ||
  246. !guardianComponent.GuardianLoose)
  247. {
  248. return;
  249. }
  250. CheckGuardianMove(uid, component.HostedGuardian.Value, component);
  251. }
  252. /// <summary>
  253. /// Called every time the guardian moves: makes sure it's not out of it's allowed distance
  254. /// </summary>
  255. private void OnGuardianMove(EntityUid uid, GuardianComponent component, ref MoveEvent args)
  256. {
  257. if (!component.GuardianLoose || component.Host == null)
  258. return;
  259. CheckGuardianMove(component.Host.Value, uid, guardianComponent: component);
  260. }
  261. /// <summary>
  262. /// Retract the guardian if either the host or the guardian move away from each other.
  263. /// </summary>
  264. private void CheckGuardianMove(
  265. EntityUid hostUid,
  266. EntityUid guardianUid,
  267. GuardianHostComponent? hostComponent = null,
  268. GuardianComponent? guardianComponent = null,
  269. TransformComponent? hostXform = null,
  270. TransformComponent? guardianXform = null)
  271. {
  272. if (TerminatingOrDeleted(guardianUid) || TerminatingOrDeleted(hostUid))
  273. return;
  274. if (!Resolve(hostUid, ref hostComponent, ref hostXform) ||
  275. !Resolve(guardianUid, ref guardianComponent, ref guardianXform))
  276. {
  277. return;
  278. }
  279. if (!guardianComponent.GuardianLoose)
  280. return;
  281. if (!_transform.InRange(guardianXform.Coordinates, hostXform.Coordinates, guardianComponent.DistanceAllowed))
  282. RetractGuardian(hostUid, hostComponent, guardianUid, guardianComponent);
  283. }
  284. private void ReleaseGuardian(EntityUid host, GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent)
  285. {
  286. if (guardianComponent.GuardianLoose)
  287. {
  288. DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardian));
  289. return;
  290. }
  291. DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
  292. _container.Remove(guardian, hostComponent.GuardianContainer);
  293. DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardian));
  294. guardianComponent.GuardianLoose = true;
  295. }
  296. private void RetractGuardian(EntityUid host, GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent)
  297. {
  298. if (!guardianComponent.GuardianLoose)
  299. {
  300. DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
  301. return;
  302. }
  303. _container.Insert(guardian, hostComponent.GuardianContainer);
  304. DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian));
  305. _popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), host);
  306. guardianComponent.GuardianLoose = false;
  307. }
  308. }
  309. }