SleepingSystem.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. using Content.Shared.Actions;
  2. using Content.Shared.Buckle.Components;
  3. using Content.Shared.Damage;
  4. using Content.Shared.Damage.Events;
  5. using Content.Shared.Damage.ForceSay;
  6. using Content.Shared.Emoting;
  7. using Content.Shared.Examine;
  8. using Content.Shared.Eye.Blinding.Systems;
  9. using Content.Shared.IdentityManagement;
  10. using Content.Shared.Interaction;
  11. using Content.Shared.Interaction.Events;
  12. using Content.Shared.Mobs;
  13. using Content.Shared.Mobs.Components;
  14. using Content.Shared.Pointing;
  15. using Content.Shared.Popups;
  16. using Content.Shared.Slippery;
  17. using Content.Shared.Sound;
  18. using Content.Shared.Sound.Components;
  19. using Content.Shared.Speech;
  20. using Content.Shared.StatusEffect;
  21. using Content.Shared.Stunnable;
  22. using Content.Shared.Traits.Assorted;
  23. using Content.Shared.Verbs;
  24. using Robust.Shared.Audio.Systems;
  25. using Robust.Shared.Prototypes;
  26. using Robust.Shared.Timing;
  27. namespace Content.Shared.Bed.Sleep;
  28. public sealed partial class SleepingSystem : EntitySystem
  29. {
  30. [Dependency] private readonly IGameTiming _gameTiming = default!;
  31. [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
  32. [Dependency] private readonly BlindableSystem _blindableSystem = default!;
  33. [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
  34. [Dependency] private readonly SharedAudioSystem _audio = default!;
  35. [Dependency] private readonly SharedEmitSoundSystem _emitSound = default!;
  36. [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
  37. public static readonly EntProtoId SleepActionId = "ActionSleep";
  38. public static readonly EntProtoId WakeActionId = "ActionWake";
  39. public override void Initialize()
  40. {
  41. base.Initialize();
  42. SubscribeLocalEvent<ActionsContainerComponent, SleepActionEvent>(OnBedSleepAction);
  43. SubscribeLocalEvent<MobStateComponent, SleepStateChangedEvent>(OnSleepStateChanged);
  44. SubscribeLocalEvent<MobStateComponent, WakeActionEvent>(OnWakeAction);
  45. SubscribeLocalEvent<MobStateComponent, SleepActionEvent>(OnSleepAction);
  46. SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
  47. SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
  48. SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
  49. SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
  50. SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
  51. SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
  52. SubscribeLocalEvent<SleepingComponent, SlipAttemptEvent>(OnSlip);
  53. SubscribeLocalEvent<SleepingComponent, ConsciousAttemptEvent>(OnConsciousAttempt);
  54. SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined);
  55. SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
  56. SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
  57. SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
  58. SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
  59. SubscribeLocalEvent<SleepingComponent, EmoteAttemptEvent>(OnEmoteAttempt);
  60. SubscribeLocalEvent<SleepingComponent, BeforeForceSayEvent>(OnChangeForceSay, after: new[] { typeof(PainNumbnessSystem) });
  61. }
  62. private void OnUnbuckleAttempt(Entity<SleepingComponent> ent, ref UnbuckleAttemptEvent args)
  63. {
  64. // TODO is this necessary?
  65. // Shouldn't the interaction have already been blocked by a general interaction check?
  66. if (ent.Owner == args.User)
  67. args.Cancelled = true;
  68. }
  69. private void OnBedSleepAction(Entity<ActionsContainerComponent> ent, ref SleepActionEvent args)
  70. {
  71. TrySleeping(args.Performer);
  72. }
  73. private void OnWakeAction(Entity<MobStateComponent> ent, ref WakeActionEvent args)
  74. {
  75. if (TryWakeWithCooldown(ent.Owner))
  76. args.Handled = true;
  77. }
  78. private void OnSleepAction(Entity<MobStateComponent> ent, ref SleepActionEvent args)
  79. {
  80. TrySleeping((ent, ent.Comp));
  81. }
  82. /// <summary>
  83. /// when sleeping component is added or removed, we do some stuff with other components.
  84. /// </summary>
  85. private void OnSleepStateChanged(Entity<MobStateComponent> ent, ref SleepStateChangedEvent args)
  86. {
  87. if (args.FellAsleep)
  88. {
  89. // Expiring status effects would remove the components needed for sleeping
  90. _statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "Stun");
  91. _statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "KnockedDown");
  92. EnsureComp<StunnedComponent>(ent);
  93. EnsureComp<KnockedDownComponent>(ent);
  94. if (TryComp<SleepEmitSoundComponent>(ent, out var sleepSound))
  95. {
  96. var emitSound = EnsureComp<SpamEmitSoundComponent>(ent);
  97. if (HasComp<SnoringComponent>(ent))
  98. {
  99. emitSound.Sound = sleepSound.Snore;
  100. }
  101. emitSound.MinInterval = sleepSound.Interval;
  102. emitSound.MaxInterval = sleepSound.MaxInterval;
  103. emitSound.PopUp = sleepSound.PopUp;
  104. Dirty(ent.Owner, emitSound);
  105. }
  106. return;
  107. }
  108. RemComp<StunnedComponent>(ent);
  109. RemComp<KnockedDownComponent>(ent);
  110. RemComp<SpamEmitSoundComponent>(ent);
  111. }
  112. private void OnMapInit(Entity<SleepingComponent> ent, ref MapInitEvent args)
  113. {
  114. var ev = new SleepStateChangedEvent(true);
  115. RaiseLocalEvent(ent, ref ev);
  116. _blindableSystem.UpdateIsBlind(ent.Owner);
  117. _actionsSystem.AddAction(ent, ref ent.Comp.WakeAction, WakeActionId, ent);
  118. }
  119. private void OnSpeakAttempt(Entity<SleepingComponent> ent, ref SpeakAttemptEvent args)
  120. {
  121. // TODO reduce duplication of this behavior with MobStateSystem somehow
  122. if (HasComp<AllowNextCritSpeechComponent>(ent))
  123. {
  124. RemCompDeferred<AllowNextCritSpeechComponent>(ent);
  125. return;
  126. }
  127. args.Cancel();
  128. }
  129. private void OnSeeAttempt(Entity<SleepingComponent> ent, ref CanSeeAttemptEvent args)
  130. {
  131. if (ent.Comp.LifeStage <= ComponentLifeStage.Running)
  132. args.Cancel();
  133. }
  134. private void OnPointAttempt(Entity<SleepingComponent> ent, ref PointAttemptEvent args)
  135. {
  136. args.Cancel();
  137. }
  138. private void OnSlip(Entity<SleepingComponent> ent, ref SlipAttemptEvent args)
  139. {
  140. args.NoSlip = true;
  141. }
  142. private void OnConsciousAttempt(Entity<SleepingComponent> ent, ref ConsciousAttemptEvent args)
  143. {
  144. args.Cancelled = true;
  145. }
  146. private void OnExamined(Entity<SleepingComponent> ent, ref ExaminedEvent args)
  147. {
  148. if (args.IsInDetailsRange)
  149. {
  150. args.PushMarkup(Loc.GetString("sleep-examined", ("target", Identity.Entity(ent, EntityManager))));
  151. }
  152. }
  153. private void AddWakeVerb(Entity<SleepingComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
  154. {
  155. if (!args.CanInteract || !args.CanAccess)
  156. return;
  157. var target = args.Target;
  158. var user = args.User;
  159. AlternativeVerb verb = new()
  160. {
  161. Act = () =>
  162. {
  163. TryWakeWithCooldown((ent, ent.Comp), user: user);
  164. },
  165. Text = Loc.GetString("action-name-wake"),
  166. Priority = 2
  167. };
  168. args.Verbs.Add(verb);
  169. }
  170. /// <summary>
  171. /// When you click on a sleeping person with an empty hand, try to wake them.
  172. /// </summary>
  173. private void OnInteractHand(Entity<SleepingComponent> ent, ref InteractHandEvent args)
  174. {
  175. args.Handled = true;
  176. TryWakeWithCooldown((ent, ent.Comp), args.User);
  177. }
  178. /// <summary>
  179. /// Wake up on taking an instance of damage at least the value of WakeThreshold.
  180. /// </summary>
  181. private void OnDamageChanged(Entity<SleepingComponent> ent, ref DamageChangedEvent args)
  182. {
  183. if (!args.DamageIncreased || args.DamageDelta == null)
  184. return;
  185. /* Shitmed Change Start - Surgery needs this, sorry! If the nocturine gamers get too feisty
  186. I'll probably just increase the threshold */
  187. if (args.DamageDelta.GetTotal() >= ent.Comp.WakeThreshold
  188. && !HasComp<ForcedSleepingComponent>(ent))
  189. TryWaking((ent, ent.Comp));
  190. // Shitmed Change End
  191. }
  192. /// <summary>
  193. /// In crit, we wake up if we are not being forced to sleep.
  194. /// And, you can't sleep when dead...
  195. /// </summary>
  196. private void OnMobStateChanged(Entity<SleepingComponent> ent, ref MobStateChangedEvent args)
  197. {
  198. if (args.NewMobState == MobState.Dead)
  199. {
  200. RemComp<SpamEmitSoundComponent>(ent);
  201. RemComp<SleepingComponent>(ent);
  202. return;
  203. }
  204. if (TryComp<SpamEmitSoundComponent>(ent, out var spam))
  205. _emitSound.SetEnabled((ent, spam), args.NewMobState == MobState.Alive);
  206. }
  207. private void OnInit(Entity<ForcedSleepingComponent> ent, ref ComponentInit args)
  208. {
  209. TrySleeping(ent.Owner);
  210. }
  211. private void Wake(Entity<SleepingComponent> ent)
  212. {
  213. RemComp<SleepingComponent>(ent);
  214. _actionsSystem.RemoveAction(ent, ent.Comp.WakeAction);
  215. var ev = new SleepStateChangedEvent(false);
  216. RaiseLocalEvent(ent, ref ev);
  217. _blindableSystem.UpdateIsBlind(ent.Owner);
  218. }
  219. /// <summary>
  220. /// Try sleeping. Only mobs can sleep.
  221. /// </summary>
  222. public bool TrySleeping(Entity<MobStateComponent?> ent)
  223. {
  224. if (!Resolve(ent, ref ent.Comp, logMissing: false))
  225. return false;
  226. var tryingToSleepEvent = new TryingToSleepEvent(ent);
  227. RaiseLocalEvent(ent, ref tryingToSleepEvent);
  228. if (tryingToSleepEvent.Cancelled)
  229. return false;
  230. EnsureComp<SleepingComponent>(ent);
  231. return true;
  232. }
  233. /// <summary>
  234. /// Tries to wake up <paramref name="ent"/>, with a cooldown between attempts to prevent spam.
  235. /// </summary>
  236. public bool TryWakeWithCooldown(Entity<SleepingComponent?> ent, EntityUid? user = null)
  237. {
  238. if (!Resolve(ent, ref ent.Comp, false))
  239. return false;
  240. var curTime = _gameTiming.CurTime;
  241. if (curTime < ent.Comp.CooldownEnd)
  242. return false;
  243. ent.Comp.CooldownEnd = curTime + ent.Comp.Cooldown;
  244. Dirty(ent, ent.Comp);
  245. return TryWaking(ent, user: user);
  246. }
  247. /// <summary>
  248. /// Try to wake up <paramref name="ent"/>.
  249. /// </summary>
  250. public bool TryWaking(Entity<SleepingComponent?> ent, bool force = false, EntityUid? user = null)
  251. {
  252. if (!Resolve(ent, ref ent.Comp, false))
  253. return false;
  254. if (!force && HasComp<ForcedSleepingComponent>(ent))
  255. {
  256. if (user != null)
  257. {
  258. _audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user);
  259. _popupSystem.PopupClient(Loc.GetString("wake-other-failure", ("target", Identity.Entity(ent, EntityManager))), ent, user, PopupType.SmallCaution);
  260. }
  261. return false;
  262. }
  263. if (user != null)
  264. {
  265. _audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user);
  266. _popupSystem.PopupClient(Loc.GetString("wake-other-success", ("target", Identity.Entity(ent, EntityManager))), ent, user);
  267. }
  268. Wake((ent, ent.Comp));
  269. return true;
  270. }
  271. /// <summary>
  272. /// Prevents the use of emote actions while sleeping
  273. /// </summary>
  274. public void OnEmoteAttempt(Entity<SleepingComponent> ent, ref EmoteAttemptEvent args)
  275. {
  276. args.Cancel();
  277. }
  278. private void OnChangeForceSay(Entity<SleepingComponent> ent, ref BeforeForceSayEvent args)
  279. {
  280. args.Prefix = ent.Comp.ForceSaySleepDataset;
  281. }
  282. }
  283. public sealed partial class SleepActionEvent : InstantActionEvent;
  284. public sealed partial class WakeActionEvent : InstantActionEvent;
  285. /// <summary>
  286. /// Raised on an entity when they fall asleep or wake up.
  287. /// </summary>
  288. [ByRefEvent]
  289. public record struct SleepStateChangedEvent(bool FellAsleep);