241 lines
7.8 KiB
C#
241 lines
7.8 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 {
|
|
|
|
// 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<SleepingBag>()) {
|
|
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<BuildingPrivlidge>();
|
|
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<BuildingBlock>() == 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);
|
|
}
|
|
}
|
|
}
|