]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Move SleepingSystem to Shared & cleanup (#28672)
authorTayrtahn <tayrtahn@gmail.com>
Fri, 14 Jun 2024 02:15:29 +0000 (22:15 -0400)
committerGitHub <noreply@github.com>
Fri, 14 Jun 2024 02:15:29 +0000 (12:15 +1000)
* Move SleepingSystem to Shared & cleanup

* Remove empty OnShutdown handler

Content.Client/Bed/SleepingSystem.cs [deleted file]
Content.Server/Bed/BedSystem.cs
Content.Server/Bed/Sleep/SleepingSystem.cs [deleted file]
Content.Server/Damage/ForceSay/DamageForceSaySystem.cs
Content.Shared/Bed/Sleep/SharedSleepingSystem.cs [deleted file]
Content.Shared/Bed/Sleep/SleepingComponent.cs
Content.Shared/Bed/Sleep/SleepingSystem.cs [new file with mode: 0644]
Content.Shared/Bed/Sleep/SnoringComponent.cs [moved from Content.Server/Bed/Components/SnoringComponent.cs with 54% similarity]

diff --git a/Content.Client/Bed/SleepingSystem.cs b/Content.Client/Bed/SleepingSystem.cs
deleted file mode 100644 (file)
index addf855..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-using Content.Server.Bed.Sleep;
-
-namespace Content.Client.Bed;
-
-public sealed class SleepingSystem : SharedSleepingSystem
-{
-
-}
index 49021c142f4fc6939716cac2a22ac8b262cb6517..ee43cff26de62173baf2fef006276c47a138adb3 100644 (file)
@@ -1,6 +1,5 @@
 using Content.Server.Actions;
 using Content.Server.Bed.Components;
-using Content.Server.Bed.Sleep;
 using Content.Server.Body.Systems;
 using Content.Server.Power.Components;
 using Content.Server.Power.EntitySystems;
diff --git a/Content.Server/Bed/Sleep/SleepingSystem.cs b/Content.Server/Bed/Sleep/SleepingSystem.cs
deleted file mode 100644 (file)
index 5e4f0ed..0000000
+++ /dev/null
@@ -1,254 +0,0 @@
-using Content.Server.Popups;
-using Content.Server.Sound;
-using Content.Shared.Sound.Components;
-using Content.Shared.Actions;
-using Content.Shared.Audio;
-using Content.Shared.Bed.Sleep;
-using Content.Shared.Damage;
-using Content.Shared.Examine;
-using Content.Shared.IdentityManagement;
-using Content.Shared.Interaction;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Slippery;
-using Content.Shared.StatusEffect;
-using Content.Shared.Stunnable;
-using Content.Shared.Verbs;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Player;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Bed.Sleep
-{
-    public sealed class SleepingSystem : SharedSleepingSystem
-    {
-        [Dependency] private readonly IGameTiming _gameTiming = default!;
-        [Dependency] private readonly IRobustRandom _robustRandom = default!;
-        [Dependency] private readonly PopupSystem _popupSystem = default!;
-        [Dependency] private readonly SharedAudioSystem _audio = default!;
-        [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
-        [Dependency] private readonly EmitSoundSystem _emitSound = default!;
-
-        [ValidatePrototypeId<EntityPrototype>] public const string SleepActionId = "ActionSleep";
-
-        public override void Initialize()
-        {
-            base.Initialize();
-            SubscribeLocalEvent<MobStateComponent, SleepStateChangedEvent>(OnSleepStateChanged);
-            SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
-            SubscribeLocalEvent<MobStateComponent, SleepActionEvent>(OnSleepAction);
-            SubscribeLocalEvent<ActionsContainerComponent, SleepActionEvent>(OnBedSleepAction);
-            SubscribeLocalEvent<MobStateComponent, WakeActionEvent>(OnWakeAction);
-            SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
-            SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
-            SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
-            SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined);
-            SubscribeLocalEvent<SleepingComponent, SlipAttemptEvent>(OnSlip);
-            SubscribeLocalEvent<SleepingComponent, ConsciousAttemptEvent>(OnConsciousAttempt);
-            SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
-        }
-
-        /// <summary>
-        /// when sleeping component is added or removed, we do some stuff with other components.
-        /// </summary>
-        private void OnSleepStateChanged(EntityUid uid, MobStateComponent component, SleepStateChangedEvent args)
-        {
-            if (args.FellAsleep)
-            {
-                // Expiring status effects would remove the components needed for sleeping
-                _statusEffectsSystem.TryRemoveStatusEffect(uid, "Stun");
-                _statusEffectsSystem.TryRemoveStatusEffect(uid, "KnockedDown");
-
-                EnsureComp<StunnedComponent>(uid);
-                EnsureComp<KnockedDownComponent>(uid);
-
-                if (TryComp<SleepEmitSoundComponent>(uid, out var sleepSound))
-                {
-                    var emitSound = EnsureComp<SpamEmitSoundComponent>(uid);
-                    if (HasComp<SnoringComponent>(uid))
-                    {
-                        emitSound.Sound = sleepSound.Snore;
-                    }
-                    emitSound.MinInterval = sleepSound.Interval;
-                    emitSound.MaxInterval = sleepSound.MaxInterval;
-                    emitSound.PopUp = sleepSound.PopUp;
-                }
-
-                return;
-            }
-
-            RemComp<StunnedComponent>(uid);
-            RemComp<KnockedDownComponent>(uid);
-            RemComp<SpamEmitSoundComponent>(uid);
-        }
-
-        /// <summary>
-        /// Wake up on taking an instance of damage at least the value of WakeThreshold.
-        /// </summary>
-        private void OnDamageChanged(EntityUid uid, SleepingComponent component, DamageChangedEvent args)
-        {
-            if (!args.DamageIncreased || args.DamageDelta == null)
-                return;
-
-            if (args.DamageDelta.GetTotal() >= component.WakeThreshold)
-                TryWaking(uid, component);
-        }
-
-        private void OnSleepAction(EntityUid uid, MobStateComponent component, SleepActionEvent args)
-        {
-            TrySleeping(uid);
-        }
-
-        private void OnBedSleepAction(EntityUid uid, ActionsContainerComponent component, SleepActionEvent args)
-        {
-            TrySleeping(args.Performer);
-        }
-
-        private void OnWakeAction(EntityUid uid, MobStateComponent component, WakeActionEvent args)
-        {
-            if (!TryWakeCooldown(uid))
-                return;
-
-            if (TryWaking(uid))
-                args.Handled = true;
-        }
-
-        /// <summary>
-        /// In crit, we wake up if we are not being forced to sleep.
-        /// And, you can't sleep when dead...
-        /// </summary>
-        private void OnMobStateChanged(EntityUid uid, SleepingComponent component, MobStateChangedEvent args)
-        {
-            if (args.NewMobState == MobState.Dead)
-            {
-                RemComp<SpamEmitSoundComponent>(uid);
-                RemComp<SleepingComponent>(uid);
-                return;
-            }
-            if (TryComp<SpamEmitSoundComponent>(uid, out var spam))
-                _emitSound.SetEnabled((uid, spam), args.NewMobState == MobState.Alive);
-        }
-
-        private void AddWakeVerb(EntityUid uid, SleepingComponent component, GetVerbsEvent<AlternativeVerb> args)
-        {
-            if (!args.CanInteract || !args.CanAccess)
-                return;
-
-            AlternativeVerb verb = new()
-            {
-                Act = () =>
-                {
-                    if (!TryWakeCooldown(uid))
-                        return;
-
-                    TryWaking(args.Target, user: args.User);
-                },
-                Text = Loc.GetString("action-name-wake"),
-                Priority = 2
-            };
-
-            args.Verbs.Add(verb);
-        }
-
-        /// <summary>
-        /// When you click on a sleeping person with an empty hand, try to wake them.
-        /// </summary>
-        private void OnInteractHand(EntityUid uid, SleepingComponent component, InteractHandEvent args)
-        {
-            args.Handled = true;
-
-            if (!TryWakeCooldown(uid))
-                return;
-
-            TryWaking(args.Target, user: args.User);
-        }
-
-        private void OnExamined(EntityUid uid, SleepingComponent component, ExaminedEvent args)
-        {
-            if (args.IsInDetailsRange)
-            {
-                args.PushMarkup(Loc.GetString("sleep-examined", ("target", Identity.Entity(uid, EntityManager))));
-            }
-        }
-
-        private void OnSlip(EntityUid uid, SleepingComponent component, SlipAttemptEvent args)
-        {
-            args.Cancel();
-        }
-
-        private void OnConsciousAttempt(EntityUid uid, SleepingComponent component, ConsciousAttemptEvent args)
-        {
-            args.Cancel();
-        }
-
-
-        private void OnInit(EntityUid uid, ForcedSleepingComponent component, ComponentInit args)
-        {
-            TrySleeping(uid);
-        }
-
-        /// <summary>
-        /// Try sleeping. Only mobs can sleep.
-        /// </summary>
-        public bool TrySleeping(EntityUid uid)
-        {
-            if (!HasComp<MobStateComponent>(uid))
-                return false;
-
-            var tryingToSleepEvent = new TryingToSleepEvent(uid);
-            RaiseLocalEvent(uid, ref tryingToSleepEvent);
-            if (tryingToSleepEvent.Cancelled)
-                return false;
-
-            EnsureComp<SleepingComponent>(uid);
-            return true;
-        }
-
-        private bool TryWakeCooldown(EntityUid uid, SleepingComponent? component = null)
-        {
-            if (!Resolve(uid, ref component, false))
-                return false;
-
-            var curTime = _gameTiming.CurTime;
-
-            if (curTime < component.CoolDownEnd)
-            {
-                return false;
-            }
-
-            component.CoolDownEnd = curTime + component.Cooldown;
-            return true;
-        }
-
-        /// <summary>
-        /// Try to wake up.
-        /// </summary>
-        public bool TryWaking(EntityUid uid, SleepingComponent? component = null, bool force = false, EntityUid? user = null)
-        {
-            if (!Resolve(uid, ref component, false))
-                return false;
-
-            if (!force && HasComp<ForcedSleepingComponent>(uid))
-            {
-                if (user != null)
-                {
-                    _audio.PlayPvs("/Audio/Effects/thudswoosh.ogg", uid, AudioHelpers.WithVariation(0.05f, _robustRandom));
-                    _popupSystem.PopupEntity(Loc.GetString("wake-other-failure", ("target", Identity.Entity(uid, EntityManager))), uid, Filter.Entities(user.Value), true, Shared.Popups.PopupType.SmallCaution);
-                }
-                return false;
-            }
-
-            if (user != null)
-            {
-                _audio.PlayPvs("/Audio/Effects/thudswoosh.ogg", uid, AudioHelpers.WithVariation(0.05f, _robustRandom));
-                _popupSystem.PopupEntity(Loc.GetString("wake-other-success", ("target", Identity.Entity(uid, EntityManager))), uid, Filter.Entities(user.Value), true);
-            }
-            RemComp<SleepingComponent>(uid);
-            return true;
-        }
-    }
-}
index 186fc91c46e9e973e124154cb8f8f881a655da2e..bc61c5d141a8e1bf23e03671f17dac3f4eb25791 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.Bed.Sleep;
 using Content.Shared.Damage;
 using Content.Shared.Damage.ForceSay;
 using Content.Shared.FixedPoint;
diff --git a/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs b/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs
deleted file mode 100644 (file)
index c6248c8..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Bed.Sleep;
-using Content.Shared.Damage.ForceSay;
-using Content.Shared.Eye.Blinding.Systems;
-using Content.Shared.Pointing;
-using Content.Shared.Speech;
-using Robust.Shared.Network;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Bed.Sleep
-{
-    public abstract class SharedSleepingSystem : EntitySystem
-    {
-        [Dependency] private readonly IGameTiming _gameTiming = default!;
-        [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
-        [Dependency] private readonly BlindableSystem _blindableSystem = default!;
-
-        [ValidatePrototypeId<EntityPrototype>] private const string WakeActionId = "ActionWake";
-
-        public override void Initialize()
-        {
-            base.Initialize();
-            SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
-            SubscribeLocalEvent<SleepingComponent, ComponentShutdown>(OnShutdown);
-            SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
-            SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
-            SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
-        }
-
-
-        private void OnMapInit(EntityUid uid, SleepingComponent component, MapInitEvent args)
-        {
-            var ev = new SleepStateChangedEvent(true);
-            RaiseLocalEvent(uid, ev);
-            _blindableSystem.UpdateIsBlind(uid);
-            _actionsSystem.AddAction(uid, ref component.WakeAction, WakeActionId, uid);
-
-            // TODO remove hardcoded time.
-            _actionsSystem.SetCooldown(component.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(2f));
-        }
-
-        private void OnShutdown(EntityUid uid, SleepingComponent component, ComponentShutdown args)
-        {
-            _actionsSystem.RemoveAction(uid, component.WakeAction);
-            var ev = new SleepStateChangedEvent(false);
-            RaiseLocalEvent(uid, ev);
-            _blindableSystem.UpdateIsBlind(uid);
-        }
-
-        private void OnSpeakAttempt(EntityUid uid, SleepingComponent component, SpeakAttemptEvent args)
-        {
-            // TODO reduce duplication of this behavior with MobStateSystem somehow
-            if (HasComp<AllowNextCritSpeechComponent>(uid))
-            {
-                RemCompDeferred<AllowNextCritSpeechComponent>(uid);
-                return;
-            }
-
-            args.Cancel();
-        }
-
-        private void OnSeeAttempt(EntityUid uid, SleepingComponent component, CanSeeAttemptEvent args)
-        {
-            if (component.LifeStage <= ComponentLifeStage.Running)
-                args.Cancel();
-        }
-
-        private void OnPointAttempt(EntityUid uid, SleepingComponent component, PointAttemptEvent args)
-        {
-            args.Cancel();
-        }
-    }
-}
-
-
-public sealed partial class SleepActionEvent : InstantActionEvent {}
-
-public sealed partial class WakeActionEvent : InstantActionEvent {}
-
-/// <summary>
-/// Raised on an entity when they fall asleep or wake up.
-/// </summary>
-public sealed class SleepStateChangedEvent : EntityEventArgs
-{
-    public bool FellAsleep = false;
-
-    public SleepStateChangedEvent(bool fellAsleep)
-    {
-        FellAsleep = fellAsleep;
-    }
-}
index cd468440f40d80b207101d88a69af9cd00cef519..cbea0a0516f03dcd88be7ba55b462df46e4942db 100644 (file)
@@ -1,31 +1,42 @@
 using Content.Shared.FixedPoint;
+using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
 namespace Content.Shared.Bed.Sleep;
 
 /// <summary>
 /// Added to entities when they go to sleep.
 /// </summary>
-[NetworkedComponent, RegisterComponent, AutoGenerateComponentPause(Dirty = true)]
+[NetworkedComponent, RegisterComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause(Dirty = true)]
 public sealed partial class SleepingComponent : Component
 {
     /// <summary>
     /// How much damage of any type it takes to wake this entity.
     /// </summary>
-    [DataField("wakeThreshold")]
+    [DataField]
     public FixedPoint2 WakeThreshold = FixedPoint2.New(2);
 
     /// <summary>
     ///     Cooldown time between users hand interaction.
     /// </summary>
-    [DataField("cooldown")]
-    [ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public TimeSpan Cooldown = TimeSpan.FromSeconds(1f);
 
-    [DataField("cooldownEnd", customTypeSerializer:typeof(TimeOffsetSerializer))]
-    [AutoPausedField]
-    public TimeSpan CoolDownEnd;
+    [DataField]
+    [AutoNetworkedField, AutoPausedField]
+    public TimeSpan CooldownEnd;
 
-    [DataField("wakeAction")] public EntityUid? WakeAction;
+    [DataField]
+    [AutoNetworkedField]
+    public EntityUid? WakeAction;
+
+    /// <summary>
+    /// Sound to play when another player attempts to wake this entity.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier WakeAttemptSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg")
+    {
+        Params = AudioParams.Default.WithVariation(0.05f)
+    };
 }
diff --git a/Content.Shared/Bed/Sleep/SleepingSystem.cs b/Content.Shared/Bed/Sleep/SleepingSystem.cs
new file mode 100644 (file)
index 0000000..aac3e7b
--- /dev/null
@@ -0,0 +1,314 @@
+using Content.Shared.Actions;
+using Content.Shared.Damage;
+using Content.Shared.Damage.ForceSay;
+using Content.Shared.Examine;
+using Content.Shared.Eye.Blinding.Systems;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Pointing;
+using Content.Shared.Popups;
+using Content.Shared.Slippery;
+using Content.Shared.Sound;
+using Content.Shared.Sound.Components;
+using Content.Shared.Speech;
+using Content.Shared.StatusEffect;
+using Content.Shared.Stunnable;
+using Content.Shared.Verbs;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Bed.Sleep;
+
+public sealed partial class SleepingSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+    [Dependency] private readonly BlindableSystem _blindableSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedEmitSoundSystem _emitSound = default!;
+    [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
+
+    public static readonly ProtoId<EntityPrototype> SleepActionId = "ActionSleep";
+    public static readonly ProtoId<EntityPrototype> WakeActionId = "ActionWake";
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ActionsContainerComponent, SleepActionEvent>(OnBedSleepAction);
+
+        SubscribeLocalEvent<MobStateComponent, SleepStateChangedEvent>(OnSleepStateChanged);
+        SubscribeLocalEvent<MobStateComponent, WakeActionEvent>(OnWakeAction);
+        SubscribeLocalEvent<MobStateComponent, SleepActionEvent>(OnSleepAction);
+
+        SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
+        SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
+        SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
+        SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
+        SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
+        SubscribeLocalEvent<SleepingComponent, SlipAttemptEvent>(OnSlip);
+        SubscribeLocalEvent<SleepingComponent, ConsciousAttemptEvent>(OnConsciousAttempt);
+        SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined);
+        SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
+        SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
+
+        SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
+    }
+
+    private void OnBedSleepAction(Entity<ActionsContainerComponent> ent, ref SleepActionEvent args)
+    {
+        TrySleeping(args.Performer);
+    }
+
+    private void OnWakeAction(Entity<MobStateComponent> ent, ref WakeActionEvent args)
+    {
+        if (TryWakeWithCooldown(ent.Owner))
+            args.Handled = true;
+    }
+
+    private void OnSleepAction(Entity<MobStateComponent> ent, ref SleepActionEvent args)
+    {
+        TrySleeping((ent, ent.Comp));
+    }
+
+    /// <summary>
+    /// when sleeping component is added or removed, we do some stuff with other components.
+    /// </summary>
+    private void OnSleepStateChanged(Entity<MobStateComponent> ent, ref SleepStateChangedEvent args)
+    {
+        if (args.FellAsleep)
+        {
+            // Expiring status effects would remove the components needed for sleeping
+            _statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "Stun");
+            _statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "KnockedDown");
+
+            EnsureComp<StunnedComponent>(ent);
+            EnsureComp<KnockedDownComponent>(ent);
+
+            if (TryComp<SleepEmitSoundComponent>(ent, out var sleepSound))
+            {
+                var emitSound = EnsureComp<SpamEmitSoundComponent>(ent);
+                if (HasComp<SnoringComponent>(ent))
+                {
+                    emitSound.Sound = sleepSound.Snore;
+                }
+                emitSound.MinInterval = sleepSound.Interval;
+                emitSound.MaxInterval = sleepSound.MaxInterval;
+                emitSound.PopUp = sleepSound.PopUp;
+                Dirty(ent.Owner, emitSound);
+            }
+
+            return;
+        }
+
+        RemComp<StunnedComponent>(ent);
+        RemComp<KnockedDownComponent>(ent);
+        RemComp<SpamEmitSoundComponent>(ent);
+    }
+
+    private void OnMapInit(Entity<SleepingComponent> ent, ref MapInitEvent args)
+    {
+        var ev = new SleepStateChangedEvent(true);
+        RaiseLocalEvent(ent, ref ev);
+        _blindableSystem.UpdateIsBlind(ent.Owner);
+        _actionsSystem.AddAction(ent, ref ent.Comp.WakeAction, WakeActionId, ent);
+
+        // TODO remove hardcoded time.
+        _actionsSystem.SetCooldown(ent.Comp.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(2f));
+    }
+
+    private void OnSpeakAttempt(Entity<SleepingComponent> ent, ref SpeakAttemptEvent args)
+    {
+        // TODO reduce duplication of this behavior with MobStateSystem somehow
+        if (HasComp<AllowNextCritSpeechComponent>(ent))
+        {
+            RemCompDeferred<AllowNextCritSpeechComponent>(ent);
+            return;
+        }
+
+        args.Cancel();
+    }
+
+    private void OnSeeAttempt(Entity<SleepingComponent> ent, ref CanSeeAttemptEvent args)
+    {
+        if (ent.Comp.LifeStage <= ComponentLifeStage.Running)
+            args.Cancel();
+    }
+
+    private void OnPointAttempt(Entity<SleepingComponent> ent, ref PointAttemptEvent args)
+    {
+        args.Cancel();
+    }
+
+    private void OnSlip(Entity<SleepingComponent> ent, ref SlipAttemptEvent args)
+    {
+        args.Cancel();
+    }
+
+    private void OnConsciousAttempt(Entity<SleepingComponent> ent, ref ConsciousAttemptEvent args)
+    {
+        args.Cancel();
+    }
+
+    private void OnExamined(Entity<SleepingComponent> ent, ref ExaminedEvent args)
+    {
+        if (args.IsInDetailsRange)
+        {
+            args.PushMarkup(Loc.GetString("sleep-examined", ("target", Identity.Entity(ent, EntityManager))));
+        }
+    }
+
+    private void AddWakeVerb(Entity<SleepingComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+    {
+        if (!args.CanInteract || !args.CanAccess)
+            return;
+
+        var target = args.Target;
+        var user = args.User;
+        AlternativeVerb verb = new()
+        {
+            Act = () =>
+            {
+                TryWakeWithCooldown((ent, ent.Comp), user: user);
+            },
+            Text = Loc.GetString("action-name-wake"),
+            Priority = 2
+        };
+
+        args.Verbs.Add(verb);
+    }
+
+    /// <summary>
+    /// When you click on a sleeping person with an empty hand, try to wake them.
+    /// </summary>
+    private void OnInteractHand(Entity<SleepingComponent> ent, ref InteractHandEvent args)
+    {
+        args.Handled = true;
+
+        TryWakeWithCooldown((ent, ent.Comp), args.User);
+    }
+
+    /// <summary>
+    /// Wake up on taking an instance of damage at least the value of WakeThreshold.
+    /// </summary>
+    private void OnDamageChanged(Entity<SleepingComponent> ent, ref DamageChangedEvent args)
+    {
+        if (!args.DamageIncreased || args.DamageDelta == null)
+            return;
+
+        if (args.DamageDelta.GetTotal() >= ent.Comp.WakeThreshold)
+            TryWaking((ent, ent.Comp));
+    }
+
+    /// <summary>
+    /// In crit, we wake up if we are not being forced to sleep.
+    /// And, you can't sleep when dead...
+    /// </summary>
+    private void OnMobStateChanged(Entity<SleepingComponent> ent, ref MobStateChangedEvent args)
+    {
+        if (args.NewMobState == MobState.Dead)
+        {
+            RemComp<SpamEmitSoundComponent>(ent);
+            RemComp<SleepingComponent>(ent);
+            return;
+        }
+        if (TryComp<SpamEmitSoundComponent>(ent, out var spam))
+            _emitSound.SetEnabled((ent, spam), args.NewMobState == MobState.Alive);
+    }
+
+    private void OnInit(Entity<ForcedSleepingComponent> ent, ref ComponentInit args)
+    {
+        TrySleeping(ent.Owner);
+    }
+
+    private void Wake(Entity<SleepingComponent> ent)
+    {
+        RemComp<SleepingComponent>(ent);
+        _actionsSystem.RemoveAction(ent, ent.Comp.WakeAction);
+
+        var ev = new SleepStateChangedEvent(false);
+        RaiseLocalEvent(ent, ref ev);
+
+        _blindableSystem.UpdateIsBlind(ent.Owner);
+    }
+
+    /// <summary>
+    /// Try sleeping. Only mobs can sleep.
+    /// </summary>
+    public bool TrySleeping(Entity<MobStateComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp, logMissing: false))
+            return false;
+
+        var tryingToSleepEvent = new TryingToSleepEvent(ent);
+        RaiseLocalEvent(ent, ref tryingToSleepEvent);
+        if (tryingToSleepEvent.Cancelled)
+            return false;
+
+        EnsureComp<SleepingComponent>(ent);
+        return true;
+    }
+
+    /// <summary>
+    /// Tries to wake up <paramref name="ent"/>, with a cooldown between attempts to prevent spam.
+    /// </summary>
+    public bool TryWakeWithCooldown(Entity<SleepingComponent?> ent, EntityUid? user = null)
+    {
+        if (!Resolve(ent, ref ent.Comp, false))
+            return false;
+
+        var curTime = _gameTiming.CurTime;
+
+        if (curTime < ent.Comp.CooldownEnd)
+            return false;
+
+        ent.Comp.CooldownEnd = curTime + ent.Comp.Cooldown;
+        Dirty(ent, ent.Comp);
+        return TryWaking(ent, user: user);
+    }
+
+    /// <summary>
+    /// Try to wake up <paramref name="ent"/>.
+    /// </summary>
+    public bool TryWaking(Entity<SleepingComponent?> ent, bool force = false, EntityUid? user = null)
+    {
+        if (!Resolve(ent, ref ent.Comp, false))
+            return false;
+
+        if (!force && HasComp<ForcedSleepingComponent>(ent))
+        {
+            if (user != null)
+            {
+                _audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user);
+                _popupSystem.PopupClient(Loc.GetString("wake-other-failure", ("target", Identity.Entity(ent, EntityManager))), ent, user, PopupType.SmallCaution);
+            }
+            return false;
+        }
+
+        if (user != null)
+        {
+            _audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user);
+            _popupSystem.PopupClient(Loc.GetString("wake-other-success", ("target", Identity.Entity(ent, EntityManager))), ent, user);
+        }
+
+        Wake((ent, ent.Comp));
+        return true;
+    }
+}
+
+
+public sealed partial class SleepActionEvent : InstantActionEvent;
+
+public sealed partial class WakeActionEvent : InstantActionEvent;
+
+/// <summary>
+/// Raised on an entity when they fall asleep or wake up.
+/// </summary>
+[ByRefEvent]
+public record struct SleepStateChangedEvent(bool FellAsleep);
similarity index 54%
rename from Content.Server/Bed/Components/SnoringComponent.cs
rename to Content.Shared/Bed/Sleep/SnoringComponent.cs
index 09f80327ba73fd8dab6094398c16eaf0a815270a..2fe92951f0738b1d9dc7ca1dd3d8b90e394f474c 100644 (file)
@@ -1,9 +1,11 @@
-namespace Content.Server.Bed.Sleep;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Bed.Sleep;
 
 /// <summary>
 /// This is used for the snoring trait.
 /// </summary>
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent]
 public sealed partial class SnoringComponent : Component
 {