Files
rust/oxide/plugins/GoodNightsSleep.cs
T

313 lines
10 KiB
C#

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();
var now = this.GetTime();
if (this.lastYawnDay == today) return;
if ((now > GoodNightsSleep.BED_TIME) && (now < GoodNightsSleep.BED_TIME + 1f)) {
Puts("Players standing on their beds should be sleepy");
this.lastYawnDay = today;
foreach (var player in BasePlayer.activePlayerList) {
var bag = this.FindNearbySleepingBag(player);
if (bag == null) {
Puts($"{player.displayName} isn't standing on a sleeping bag");
continue;
}
if (!this.IsInBase(bag)) {
Puts($"{player.displayName}'s sleeping bag isn't in a base");
continue;
}
player.ChatMessage("You feel your eyelids drooping...");
}
}
}
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<SleepingBag>();
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<BuildingPrivlidge>();
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
foreach (var hit in Physics.RaycastAll(position, Vector3.down, 2f)) {
if (!hit.collider) continue;
var obj = hit.collider.gameObject;
if (obj.GetComponentInParent<BuildingBlock>()) return true;
}
Puts("Sleeping bag is not on a foundation");
return false;
}
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.");
});
}
}
}