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 { // the earliest time you can go to sleep public const float BED_TIME = 22f; // 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 time you wake up in the morning public const float WAKE_TIME = 7f; // a an actual instance of sleeping through the night private class SleepSession { // 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 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.lastStepAt = this.GetTime(); this.player.StartSleeping(); this.player.ChatMessage("You slowly start to drift off to sleep."); } public void KeepSleeping() { if (this.ShouldAbort()) { this.Abort(); return; } if (this.ShouldWake()) { this.End(); return; } var now = this.GetTime(); var sleepDuration = (now - this.lastStepAt) * 60 * 60; // in seconds this.player.Heal(sleepDuration * GoodNightsSleep.HEAL_RATIO); this.lastStepAt = now; this.SetTime( this.GetTime() + ( GoodNightsSleep.STEP_SIZE * GoodNightsSleep.SLEEP_SPEED / 60 / 60 ) ); } 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; if (this.GetTime() > GoodNightsSleep.WAKE_TIME) return true; return false; } }; // the specific instance of sleeping through the night private SleepSession session = null; // Hooks /////////////////////////////////////////////////////////////////////////////////// void OnServerInitialized() { Puts("Good Night's Sleep is active"); } // Commands //////////////////////////////////////////////////////////////////////////////// [ChatCommand("sleep")] private void SleepCommand(BasePlayer player, string command, string[] args) { if (!this.IsSleepy(player)) return; this.session = new SleepSession(player, () => this.OnSleepSessionEnded()); this.session.Begin(timer.Every(STEP_SIZE, () => this.session.KeepSleeping())); } // Helper Methods ////////////////////////////////////////////////////////////////////////// private void OnSleepSessionEnded() { this.session.Abort(); this.session = null; } 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("There's nowhere to sleep nearby."); return false; } if (!this.IsInBase(bag)) { player.ChatMessage("You're too uneasy to sleep here."); return false; } return true; } private SleepingBag FindNearbySleepingBag(BasePlayer player) { foreach (var bag in UnityEngine.Object.FindObjectsOfType()) { if (bag == null) continue; if (bag.deployerUserID != player.userID) continue; var distance = Vector3.Distance(player.transform.position, bag.transform.position); if (distance < 2f) { return bag; } } return null; } private float GetTime() { return TOD_Sky.Instance.Cycle.Hour; } private bool IsInBase(SleepingBag bag) { return ( this.IsOnFoundation(bag) && this.IsUnderRoof(bag) && this.IsNearToolchest(bag) && true ); } 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); 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; // 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; return true; } private bool IsUnderRoof(SleepingBag bag) { RaycastHit hit; var position = bag.transform.position; return Physics.Raycast(position, Vector3.up, out hit, LayerMask.Construction); } } }