using Facepunch; using Oxide.Core.Plugins; using UnityEngine; using System; class LayerMask { public const int Deployed = 1 << 8; public const int Construction = 1 << 21; } namespace Oxide.Plugins { [Info("GoodNightsSleep", "TungstonMiner", "0.1.0")] [Description("An accelerated way to sleep through the night.")] public class GoodNightsSleep : RustPlugin { // Constants /////////////////////////////////////////////////////////// // the earliest time you can go to sleep public const float BED_TIME = 20f; // the amount you heal per second from sleeping public const float HEAL_RATIO = 1f; // the amount of wall-clock time (in seconds) between sleep steps public const float STEP_SIZE = 1f; // the real-world time sleeping should last (in seconds) public const float SLEEP_TIME = 30f; // the time you wake up in the morning public const float WAKE_TIME = 8f; // the prefab sound to play when someone yawns public const string YAWN_SOUND = "assets/bundled/prefabs/fx/player/yawn.prefab"; // Helper Class //////////////////////////////////////////////////////// // a an actual instance of sleeping through the night private class SleepSession { // the player doing the sleeping public BasePlayer player = null; // in-game time when we last completed a step private float lastStepAt = 0f; // callback for when the session is over private Action onComplete = null; // the plugin itself private RustPlugin plugin = null; // the in-game time when sleep began private float startedSleepingAt = 0; // the timer controlling the steps private Timer timer = null; public SleepSession(BasePlayer player, Action onComplete) { this.onComplete = onComplete; this.player = player; } // Lifecycle Methods /////////////////////////////////////////////// public void Begin(Timer timer) { this.Abort(); this.timer = timer; this.player.StartSleeping(); this.startedSleepingAt = this.GetTime(); } public void KeepSleeping() { if (this.ShouldAbort()) { this.Abort(); return; } if (this.ShouldWake()) { this.End(); return; } var totalHours = (24f - BED_TIME) + WAKE_TIME; var totalSteps = SLEEP_TIME / STEP_SIZE; var incrementHours = totalHours / totalSteps; var incrementSeconds = incrementHours * 60 * 60; this.player.Heal(incrementSeconds * GoodNightsSleep.HEAL_RATIO); this.SetTime((this.GetTime() + incrementHours) % 24f); } public void Abort() { if (this.timer == null) return; this.timer.Destroy(); this.timer = null; this.onComplete?.Invoke(); } public void End() { this.player.EndSleeping(); this.Abort(); } // Helper Methods ////////////////////////////////////////////////// private float GetTime() { return TOD_Sky.Instance.Cycle.Hour; } private void SetTime(float time) { ConsoleSystem.Run(ConsoleSystem.Option.Server, $"env.time {time}"); } private bool ShouldAbort() { if (!this.player.IsConnected) return true; if (this.player.IsDead()) return true; if (!this.player.IsSleeping()) return true; return false; } private bool ShouldWake() { if (this.ShouldAbort()) return false; var now = this.GetTime(); var outsideSleepHours = ( (now > GoodNightsSleep.WAKE_TIME) && (now < GoodNightsSleep.BED_TIME) ); if (outsideSleepHours) return true; return false; } }; // Member Variables //////////////////////////////////////////////////// // the specific instance of sleeping through the night private SleepSession session = null; // the last time each player got a yawn reminder private int lastYawnDay = -1; // Hooks /////////////////////////////////////////////////////////////////////////////////// void OnServerInitialized() { Puts("Good Night's Sleep is active"); timer.Every(60f, () => this.CheckSleepiness()); } // Commands //////////////////////////////////////////////////////////////////////////////// [ChatCommand("sleep")] private void SleepCommand(BasePlayer player, string command, string[] args) { if (!this.IsSleepy(player)) return; this.session = new SleepSession(player, () => this.OnSleepSessionEnded()); player.ChatMessage("You slowly start to drift off to sleep."); timer.Once(1f, () => { this.session.Begin(timer.Every(STEP_SIZE, () => this.session.KeepSleeping())); }); } // Helper Methods ////////////////////////////////////////////////////////////////////////// private void CheckSleepiness() { var today = this.GetDay(); if (this.lastYawnDay == today) return; var now = this.GetTime(); if ((now > GoodNightsSleep.BED_TIME) && (now < GoodNightsSleep.BED_TIME + 1f)) { this.lastYawnDay = today; foreach (var player in BasePlayer.activePlayerList) { Effect.server.Run(GoodNightsSleep.YAWN_SOUND, player.transform.position); } } } private bool IsSleepy(BasePlayer player) { var now = this.GetTime(); if (!player) return false; if (!player.IsConnected) return false; if (player.IsDead()) { player.ChatMessage("You're too dead to sleep."); return false; } if ((now > GoodNightsSleep.WAKE_TIME) && (now < GoodNightsSleep.BED_TIME)) { player.ChatMessage("You aren't feeling sleepy yet."); return false; } if (player.IsSleeping() || (this.session != null)) { player.ChatMessage("You are already asleep."); return false; } var bag = this.FindNearbySleepingBag(player); if (bag == null) { player.ChatMessage("You'd be a lot more comfy in your bed."); return false; } if (!this.IsInBase(bag)) { player.ChatMessage("You're too uneasy to sleep here."); return false; } return true; } private SleepingBag FindNearbySleepingBag(BasePlayer player) { // move up a bit so we can look down var position = player.transform.position + Vector3.up * 0.1f; RaycastHit hit; Physics.Raycast(position, Vector3.down, out hit, 2f); if (hit.collider == null) return null; var bag = hit.collider.GetComponentInParent(); if (bag == null) return null; if (bag.deployerUserID != player.userID) return null; return bag; } private int GetDay() { return TOD_Sky.Instance.Cycle.Day; } private float GetTime() { return TOD_Sky.Instance.Cycle.Hour; } private bool IsInBase(SleepingBag bag) { return ( this.IsOnFoundation(bag) && this.IsUnderRoof(bag) && this.IsNearToolchest(bag) ); } private bool IsNearToolchest(SleepingBag bag) { var privlidges = Pool.GetList(); Vis.Entities(bag.transform.position, 25f, privlidges, LayerMask.Deployed); var result = privlidges.Count > 0; Pool.FreeList(ref privlidges); if (!result) { Puts("Sleeping bag isn't close to a toolchest"); } return result; } private bool IsOnFoundation(SleepingBag bag) { // get a bit off the floor so we can look down var position = bag.transform.position + Vector3.up * 0.5f; // check that there's some kind of player-placed block under the sleeping bag RaycastHit hit; Physics.Raycast(position, Vector3.down, out hit, 2f); if (hit.collider.GetComponentInParent() == null) { Puts("Sleeping bag is not on a foundation"); return false; } return true; } private bool IsUnderRoof(SleepingBag bag) { RaycastHit hit; var position = bag.transform.position; if (!Physics.Raycast(position, Vector3.up, out hit, 10f, LayerMask.Construction)) { Puts("Sleeping Bag is not under a roof"); return false; } return true; } private void OnSleepSessionEnded() { if (this.session == null) return; var player = this.session.player; this.session.Abort(); this.session = null; timer.Once(1f, () => { player.ChatMessage("You awake feeling rested and refreshed."); }); } } }