SleepingSystem.cs 14 KB

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