From 6ebc151742ce0e75bdd3d691389a9b40bd5d1cc2 Mon Sep 17 00:00:00 2001 From: Andrew Miner Date: Thu, 4 Sep 2025 15:58:04 -0600 Subject: [PATCH] Fix the math around sleeping to be simpler --- cfg/server.cfg | 2 +- oxide/plugins/GoodNightsSleep.cs | 137 ++++++++++++++++++++++--------- 2 files changed, 99 insertions(+), 40 deletions(-) diff --git a/cfg/server.cfg b/cfg/server.cfg index e072393..3f3688a 100644 --- a/cfg/server.cfg +++ b/cfg/server.cfg @@ -48,7 +48,7 @@ server.workbench3taxrate "0" // Animal populations should reflect realistic proportion of predators to prey. They should also // appear at about twice the natural population density to provide a good balance between realism -// and fun gameplay. +// and fun gameplay. The totals provided are assuming a 9sqkm map (3000 world size). bear.population "0.33" // per sqkm, 3 total boar.population "2.77" // per sqkm, 25 total diff --git a/oxide/plugins/GoodNightsSleep.cs b/oxide/plugins/GoodNightsSleep.cs index 31ff84d..5f2c881 100644 --- a/oxide/plugins/GoodNightsSleep.cs +++ b/oxide/plugins/GoodNightsSleep.cs @@ -14,36 +14,46 @@ namespace Oxide.Plugins { [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 = 22f; + public const float BED_TIME = 20f; // the amount you heal per second from sleeping public const float HEAL_RATIO = 1f; - // the ratio at which time passes (e.g., 10 means 10x usual speed) - public const float SLEEP_SPEED = 20; - // 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 = 7f; + 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 player doing the sleeping - private BasePlayer player = 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; @@ -58,9 +68,8 @@ namespace Oxide.Plugins { this.Abort(); this.timer = timer; - this.lastStepAt = this.GetTime(); this.player.StartSleeping(); - this.player.ChatMessage("You slowly start to drift off to sleep."); + this.startedSleepingAt = this.GetTime(); } public void KeepSleeping() { @@ -74,16 +83,13 @@ namespace Oxide.Plugins { return; } - var now = this.GetTime(); - var sleepDuration = (now - this.lastStepAt) * 60 * 60; // in seconds - this.player.Heal(sleepDuration * GoodNightsSleep.HEAL_RATIO); + var totalHours = (24f - BED_TIME) + WAKE_TIME; + var totalSteps = SLEEP_TIME / STEP_SIZE; + var incrementHours = totalHours / totalSteps; + var incrementSeconds = incrementHours * 60 * 60; - this.lastStepAt = now; - this.SetTime( - this.GetTime() + ( - GoodNightsSleep.STEP_SIZE * GoodNightsSleep.SLEEP_SPEED / 60 / 60 - ) - ); + this.player.Heal(incrementSeconds * GoodNightsSleep.HEAL_RATIO); + this.SetTime((this.GetTime() + incrementHours) % 24f); } public void Abort() { @@ -118,18 +124,31 @@ namespace Oxide.Plugins { private bool ShouldWake() { if (this.ShouldAbort()) return false; - if (this.GetTime() > GoodNightsSleep.WAKE_TIME) return true; + + 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 //////////////////////////////////////////////////////////////////////////////// @@ -139,14 +158,26 @@ namespace Oxide.Plugins { if (!this.IsSleepy(player)) return; this.session = new SleepSession(player, () => this.OnSleepSessionEnded()); - this.session.Begin(timer.Every(STEP_SIZE, () => this.session.KeepSleeping())); + 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 OnSleepSessionEnded() { - this.session.Abort(); - this.session = null; + 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) { @@ -172,7 +203,7 @@ namespace Oxide.Plugins { var bag = this.FindNearbySleepingBag(player); if (bag == null) { - player.ChatMessage("There's nowhere to sleep nearby."); + player.ChatMessage("You'd be a lot more comfy in your bed."); return false; } @@ -185,17 +216,23 @@ namespace Oxide.Plugins { } private SleepingBag FindNearbySleepingBag(BasePlayer player) { - foreach (var bag in UnityEngine.Object.FindObjectsOfType()) { - if (bag == null) continue; - if (bag.deployerUserID != player.userID) continue; + // move up a bit so we can look down + var position = player.transform.position + Vector3.up * 0.1f; - var distance = Vector3.Distance(player.transform.position, bag.transform.position); - if (distance < 2f) { - return bag; - } - } - return null; + 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() { @@ -206,8 +243,7 @@ namespace Oxide.Plugins { return ( this.IsOnFoundation(bag) && this.IsUnderRoof(bag) && - this.IsNearToolchest(bag) && - true + this.IsNearToolchest(bag) ); } @@ -216,17 +252,23 @@ namespace Oxide.Plugins { 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.1f; + 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) return false; + if (hit.collider.GetComponentInParent() == null) { + Puts("Sleeping bag is not on a foundation"); + return false; + } return true; } @@ -234,7 +276,24 @@ namespace Oxide.Plugins { private bool IsUnderRoof(SleepingBag bag) { RaycastHit hit; var position = bag.transform.position; - return Physics.Raycast(position, Vector3.up, out hit, LayerMask.Construction); + 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."); + }); } } }