313 lines
10 KiB
C#
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.");
|
|
});
|
|
}
|
|
}
|
|
}
|