1
0

BibleSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. using Content.Server.Bible.Components;
  2. using Content.Server.Ghost.Roles.Events;
  3. using Content.Server.Popups;
  4. using Content.Shared.ActionBlocker;
  5. using Content.Shared.Actions;
  6. using Content.Shared.Bible;
  7. using Content.Shared.Damage;
  8. using Content.Shared.Ghost.Roles.Components;
  9. using Content.Shared.IdentityManagement;
  10. using Content.Shared.Interaction;
  11. using Content.Shared.Inventory;
  12. using Content.Shared.Mobs;
  13. using Content.Shared.Mobs.Systems;
  14. using Content.Shared.Popups;
  15. using Content.Shared.Timing;
  16. using Content.Shared.Verbs;
  17. using Robust.Shared.Audio;
  18. using Robust.Shared.Audio.Systems;
  19. using Robust.Shared.Player;
  20. using Robust.Shared.Random;
  21. namespace Content.Server.Bible
  22. {
  23. public sealed class BibleSystem : EntitySystem
  24. {
  25. [Dependency] private readonly IRobustRandom _random = default!;
  26. [Dependency] private readonly ActionBlockerSystem _blocker = default!;
  27. [Dependency] private readonly DamageableSystem _damageableSystem = default!;
  28. [Dependency] private readonly InventorySystem _invSystem = default!;
  29. [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
  30. [Dependency] private readonly PopupSystem _popupSystem = default!;
  31. [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
  32. [Dependency] private readonly SharedAudioSystem _audio = default!;
  33. [Dependency] private readonly UseDelaySystem _delay = default!;
  34. [Dependency] private readonly SharedTransformSystem _transform = default!;
  35. public override void Initialize()
  36. {
  37. base.Initialize();
  38. SubscribeLocalEvent<BibleComponent, AfterInteractEvent>(OnAfterInteract);
  39. SubscribeLocalEvent<SummonableComponent, GetVerbsEvent<AlternativeVerb>>(AddSummonVerb);
  40. SubscribeLocalEvent<SummonableComponent, GetItemActionsEvent>(GetSummonAction);
  41. SubscribeLocalEvent<SummonableComponent, SummonActionEvent>(OnSummon);
  42. SubscribeLocalEvent<FamiliarComponent, MobStateChangedEvent>(OnFamiliarDeath);
  43. SubscribeLocalEvent<FamiliarComponent, GhostRoleSpawnerUsedEvent>(OnSpawned);
  44. }
  45. private readonly Queue<EntityUid> _addQueue = new();
  46. private readonly Queue<EntityUid> _remQueue = new();
  47. /// <summary>
  48. /// This handles familiar respawning.
  49. /// </summary>
  50. public override void Update(float frameTime)
  51. {
  52. base.Update(frameTime);
  53. foreach (var entity in _addQueue)
  54. {
  55. EnsureComp<SummonableRespawningComponent>(entity);
  56. }
  57. _addQueue.Clear();
  58. foreach (var entity in _remQueue)
  59. {
  60. RemComp<SummonableRespawningComponent>(entity);
  61. }
  62. _remQueue.Clear();
  63. var query = EntityQueryEnumerator<SummonableRespawningComponent, SummonableComponent>();
  64. while (query.MoveNext(out var uid, out var _, out var summonableComp))
  65. {
  66. summonableComp.Accumulator += frameTime;
  67. if (summonableComp.Accumulator < summonableComp.RespawnTime)
  68. {
  69. continue;
  70. }
  71. // Clean up the old body
  72. if (summonableComp.Summon != null)
  73. {
  74. EntityManager.DeleteEntity(summonableComp.Summon.Value);
  75. summonableComp.Summon = null;
  76. }
  77. summonableComp.AlreadySummoned = false;
  78. _popupSystem.PopupEntity(Loc.GetString("bible-summon-respawn-ready", ("book", uid)), uid, PopupType.Medium);
  79. _audio.PlayPvs(summonableComp.SummonSound, uid);
  80. // Clean up the accumulator and respawn tracking component
  81. summonableComp.Accumulator = 0;
  82. _remQueue.Enqueue(uid);
  83. }
  84. }
  85. private void OnAfterInteract(EntityUid uid, BibleComponent component, AfterInteractEvent args)
  86. {
  87. if (!args.CanReach)
  88. return;
  89. if (!TryComp(uid, out UseDelayComponent? useDelay) || _delay.IsDelayed((uid, useDelay)))
  90. return;
  91. if (args.Target == null || args.Target == args.User || !_mobStateSystem.IsAlive(args.Target.Value))
  92. {
  93. return;
  94. }
  95. if (!HasComp<BibleUserComponent>(args.User))
  96. {
  97. _popupSystem.PopupEntity(Loc.GetString("bible-sizzle"), args.User, args.User);
  98. _audio.PlayPvs(component.SizzleSoundPath, args.User);
  99. _damageableSystem.TryChangeDamage(args.User, component.DamageOnUntrainedUse, true, origin: uid);
  100. _delay.TryResetDelay((uid, useDelay));
  101. return;
  102. }
  103. // This only has a chance to fail if the target is not wearing anything on their head and is not a familiar.
  104. if (!_invSystem.TryGetSlotEntity(args.Target.Value, "head", out var _) && !HasComp<FamiliarComponent>(args.Target.Value))
  105. {
  106. if (_random.Prob(component.FailChance))
  107. {
  108. var othersFailMessage = Loc.GetString(component.LocPrefix + "-heal-fail-others", ("user", Identity.Entity(args.User, EntityManager)), ("target", Identity.Entity(args.Target.Value, EntityManager)), ("bible", uid));
  109. _popupSystem.PopupEntity(othersFailMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.SmallCaution);
  110. var selfFailMessage = Loc.GetString(component.LocPrefix + "-heal-fail-self", ("target", Identity.Entity(args.Target.Value, EntityManager)), ("bible", uid));
  111. _popupSystem.PopupEntity(selfFailMessage, args.User, args.User, PopupType.MediumCaution);
  112. _audio.PlayPvs(component.BibleHitSound, args.User);
  113. _damageableSystem.TryChangeDamage(args.Target.Value, component.DamageOnFail, true, origin: uid);
  114. _delay.TryResetDelay((uid, useDelay));
  115. return;
  116. }
  117. }
  118. var damage = _damageableSystem.TryChangeDamage(args.Target.Value, component.Damage, true, origin: uid);
  119. if (damage == null || damage.Empty)
  120. {
  121. var othersMessage = Loc.GetString(component.LocPrefix + "-heal-success-none-others", ("user", Identity.Entity(args.User, EntityManager)), ("target", Identity.Entity(args.Target.Value, EntityManager)), ("bible", uid));
  122. _popupSystem.PopupEntity(othersMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.Medium);
  123. var selfMessage = Loc.GetString(component.LocPrefix + "-heal-success-none-self", ("target", Identity.Entity(args.Target.Value, EntityManager)), ("bible", uid));
  124. _popupSystem.PopupEntity(selfMessage, args.User, args.User, PopupType.Large);
  125. }
  126. else
  127. {
  128. var othersMessage = Loc.GetString(component.LocPrefix + "-heal-success-others", ("user", Identity.Entity(args.User, EntityManager)), ("target", Identity.Entity(args.Target.Value, EntityManager)), ("bible", uid));
  129. _popupSystem.PopupEntity(othersMessage, args.User, Filter.PvsExcept(args.User), true, PopupType.Medium);
  130. var selfMessage = Loc.GetString(component.LocPrefix + "-heal-success-self", ("target", Identity.Entity(args.Target.Value, EntityManager)), ("bible", uid));
  131. _popupSystem.PopupEntity(selfMessage, args.User, args.User, PopupType.Large);
  132. _audio.PlayPvs(component.HealSoundPath, args.User);
  133. _delay.TryResetDelay((uid, useDelay));
  134. }
  135. }
  136. private void AddSummonVerb(EntityUid uid, SummonableComponent component, GetVerbsEvent<AlternativeVerb> args)
  137. {
  138. if (!args.CanInteract || !args.CanAccess || component.AlreadySummoned || component.SpecialItemPrototype == null)
  139. return;
  140. if (component.RequiresBibleUser && !HasComp<BibleUserComponent>(args.User))
  141. return;
  142. AlternativeVerb verb = new()
  143. {
  144. Act = () =>
  145. {
  146. if (!TryComp(args.User, out TransformComponent? userXform))
  147. return;
  148. AttemptSummon((uid, component), args.User, userXform);
  149. },
  150. Text = Loc.GetString("bible-summon-verb"),
  151. Priority = 2
  152. };
  153. args.Verbs.Add(verb);
  154. }
  155. private void GetSummonAction(EntityUid uid, SummonableComponent component, GetItemActionsEvent args)
  156. {
  157. if (component.AlreadySummoned)
  158. return;
  159. args.AddAction(ref component.SummonActionEntity, component.SummonAction);
  160. }
  161. private void OnSummon(Entity<SummonableComponent> ent, ref SummonActionEvent args)
  162. {
  163. AttemptSummon(ent, args.Performer, Transform(args.Performer));
  164. }
  165. /// <summary>
  166. /// Starts up the respawn stuff when
  167. /// the chaplain's familiar dies.
  168. /// </summary>
  169. private void OnFamiliarDeath(EntityUid uid, FamiliarComponent component, MobStateChangedEvent args)
  170. {
  171. if (args.NewMobState != MobState.Dead || component.Source == null)
  172. return;
  173. var source = component.Source;
  174. if (source != null && HasComp<SummonableComponent>(source))
  175. {
  176. _addQueue.Enqueue(source.Value);
  177. }
  178. }
  179. /// <summary>
  180. /// When the familiar spawns, set its source to the bible.
  181. /// </summary>
  182. private void OnSpawned(EntityUid uid, FamiliarComponent component, GhostRoleSpawnerUsedEvent args)
  183. {
  184. var parent = Transform(args.Spawner).ParentUid;
  185. if (!TryComp<SummonableComponent>(parent, out var summonable))
  186. return;
  187. component.Source = parent;
  188. summonable.Summon = uid;
  189. }
  190. private void AttemptSummon(Entity<SummonableComponent> ent, EntityUid user, TransformComponent? position)
  191. {
  192. var (uid, component) = ent;
  193. if (component.AlreadySummoned || component.SpecialItemPrototype == null)
  194. return;
  195. if (component.RequiresBibleUser && !HasComp<BibleUserComponent>(user))
  196. return;
  197. if (!Resolve(user, ref position))
  198. return;
  199. if (component.Deleted || Deleted(uid))
  200. return;
  201. if (!_blocker.CanInteract(user, uid))
  202. return;
  203. // Make this familiar the component's summon
  204. var familiar = EntityManager.SpawnEntity(component.SpecialItemPrototype, position.Coordinates);
  205. component.Summon = familiar;
  206. // If this is going to use a ghost role mob spawner, attach it to the bible.
  207. if (HasComp<GhostRoleMobSpawnerComponent>(familiar))
  208. {
  209. _popupSystem.PopupEntity(Loc.GetString("bible-summon-requested"), user, user, PopupType.Medium);
  210. _transform.SetParent(familiar, uid);
  211. }
  212. component.AlreadySummoned = true;
  213. _actionsSystem.RemoveAction(user, component.SummonActionEntity);
  214. }
  215. }
  216. }