using Content.Server.Actions; using Content.Server.Popups; using Content.Server.Sound.Components; using Content.Shared.Actions.ActionTypes; 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.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Slippery; using Content.Shared.Stunnable; using Content.Shared.Verbs; using Robust.Shared.Audio; 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 IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly ActionsSystem _actionsSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnSleepStateChanged); SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent(OnSleepAction); SubscribeLocalEvent(OnWakeAction); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent>(AddWakeVerb); SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnSlip); SubscribeLocalEvent(OnInit); } /// /// when sleeping component is added or removed, we do some stuff with other components. /// private void OnSleepStateChanged(EntityUid uid, MobStateComponent component, SleepStateChangedEvent args) { _prototypeManager.TryIndex("Wake", out var wakeAction); if (args.FellAsleep) { EnsureComp(uid); EnsureComp(uid); var emitSound = EnsureComp(uid); // TODO WTF is this, these should a data fields and not hard-coded. emitSound.Sound = new SoundCollectionSpecifier("Snores", AudioParams.Default.WithVariation(0.2f)); emitSound.PlayChance = 0.33f; emitSound.RollInterval = 5f; emitSound.PopUp = "sleep-onomatopoeia"; if (wakeAction != null) { var wakeInstance = new InstantAction(wakeAction); wakeInstance.Cooldown = (_gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(15)); _actionsSystem.AddAction(uid, wakeInstance, null); } return; } if (wakeAction != null) _actionsSystem.RemoveAction(uid, wakeAction); RemComp(uid); RemComp(uid); RemComp(uid); } /// /// Wake up if we take an instance of more than 2 damage. /// private void OnDamageChanged(EntityUid uid, SleepingComponent component, DamageChangedEvent args) { if (!args.DamageIncreased || args.DamageDelta == null) return; if (args.DamageDelta.Total >= component.WakeThreshold) TryWaking(uid, component); } private void OnSleepAction(EntityUid uid, MobStateComponent component, SleepActionEvent args) { TrySleeping(uid); } private void OnWakeAction(EntityUid uid, MobStateComponent component, WakeActionEvent args) { if (!TryWakeCooldown(uid)) return; if (TryWaking(uid)) args.Handled = true; } /// /// In crit, we wake up if we are not being forced to sleep. /// And, you can't sleep when dead... /// private void OnMobStateChanged(EntityUid uid, SleepingComponent component, MobStateChangedEvent args) { if (args.NewMobState == MobState.Dead) { RemComp(uid); RemComp(uid); return; } if (TryComp(uid, out var spam)) spam.Enabled = (args.NewMobState == MobState.Alive) ? true : false; } private void AddWakeVerb(EntityUid uid, SleepingComponent component, GetVerbsEvent 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); } /// /// When you click on a sleeping person with an empty hand, try to wake them. /// private void OnInteractHand(EntityUid uid, SleepingComponent component, InteractHandEvent args) { if (!TryWakeCooldown(uid)) return; args.Handled = true; 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 OnInit(EntityUid uid, ForcedSleepingComponent component, ComponentInit args) { TrySleeping(uid); } /// /// Try sleeping. Only mobs can sleep. /// public bool TrySleeping(EntityUid uid) { if (!HasComp(uid)) return false; var tryingToSleepEvent = new TryingToSleepEvent(uid); RaiseLocalEvent(uid, ref tryingToSleepEvent); if (tryingToSleepEvent.Cancelled) return false; if (_prototypeManager.TryIndex("Sleep", out var sleepAction)) _actionsSystem.RemoveAction(uid, sleepAction); EnsureComp(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; } /// /// Try to wake up. /// public bool TryWaking(EntityUid uid, SleepingComponent? component = null, bool force = false, EntityUid? user = null) { if (!Resolve(uid, ref component)) return false; if (!force && HasComp(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(uid); return true; } } }