Add current state of development
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Oxide.Core.Plugins;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oxide.Plugins {
|
||||
|
||||
[Info("RereadConfigs", "TungstonMiner", "0.1.0")]
|
||||
[Description("Re-read the server configs after loading to override gamemode settings")]
|
||||
public class RereadConfigs : RustPlugin {
|
||||
|
||||
void OnServerInitialized() {
|
||||
timer.Once(10f, () => {
|
||||
Server.Command("server.readcfg");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Oxide.Core.Plugins;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oxide.Plugins {
|
||||
|
||||
[Info("ZombieHorde", "TungstonMiner", "0.1.0")]
|
||||
[Description("Create a zombie horde at midnight every seventh day")]
|
||||
public class ZombieHorde : RustPlugin {
|
||||
|
||||
private bool isHordeActive = false;
|
||||
|
||||
void OnServerInitialized() {
|
||||
Puts("ZombieHorde loaded!");
|
||||
timer.Every(10f, () => this.CheckTime());
|
||||
}
|
||||
|
||||
void CheckTime() {
|
||||
var sky = TOD_Sky.Instance;
|
||||
var day = sky.Cycle.Day;
|
||||
var hour = sky.Cycle.Hour;
|
||||
var isNight = sky.IsNight;
|
||||
|
||||
if ((!this.isHordeActive) && (hour < 0.5) && (day % 7 == 0)) {
|
||||
this.StartHorde();
|
||||
this.isHordeActive = true;
|
||||
}
|
||||
|
||||
if (this.isHordeActive && !isNight) {
|
||||
this.EndHorde();
|
||||
this.isHordeActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void StartHorde() {
|
||||
Puts($"Starting horde at {TOD_Sky.Instance.Cycle.Hour}");
|
||||
Server.Command("halloween.murdererpopulation 10");
|
||||
Server.Command("spawn.fill_populations");
|
||||
}
|
||||
|
||||
void EndHorde() {
|
||||
Puts($"Ending horde at {TOD_Sky.Instance.Cycle.Hour}");
|
||||
Server.Command("halloween.murdererpopulation 0");
|
||||
Server.Command("spawn.delete_populations Murderer.Population");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Oxide.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oxide.Plugins {
|
||||
[Info("PlantBiomeCustomizer", "TungstonMiner", "1.0.0")]
|
||||
[Description("Customize placement of plants around the map by biome")]
|
||||
public class PlantBiomeTuner : RustPlugin {
|
||||
|
||||
// Config //////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PluginConfig configData;
|
||||
|
||||
public class PluginConfig {
|
||||
// Key = arbitrary rule name (e.g., "berry_blue", "pumpkins_desert", etc.), value = rule definition.
|
||||
public Dictionary<string, PlantRule> rules = new Dictionary<string, PlantRule>();
|
||||
public static PluginConfig Default() { return new PluginConfig(); }
|
||||
}
|
||||
|
||||
public class PlantRule {
|
||||
public bool enabled = true;
|
||||
public string[] prefabFilters = Array.Empty<string>(); // case-insensitive substrings matched against population identity
|
||||
public string[] biomes = Array.Empty<string>(); // names matching TerrainMeta.BiomeType flags
|
||||
public float densityMultiplier = 1.0f; // multiply existing density/weights
|
||||
}
|
||||
|
||||
protected override void LoadDefaultConfig() {
|
||||
configData = PluginConfig.Default();
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
protected override void SaveConfig() {
|
||||
Config.WriteObject(configData, true);
|
||||
}
|
||||
|
||||
protected override void LoadConfig() {
|
||||
base.LoadConfig();
|
||||
try {
|
||||
configData = Config.ReadObject<PluginConfig>();
|
||||
} catch {
|
||||
PrintError("Config read error; generating new default config.");
|
||||
configData = PluginConfig.Default();
|
||||
}
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
|
||||
// Hooks ///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OnServerInitialized(bool initial) {
|
||||
NextTick(ApplyAll);
|
||||
}
|
||||
|
||||
|
||||
// Commands ////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
[ConsoleCommand("plantbiometuner.apply")]
|
||||
void CmdApply(ConsoleSystem.Arg arg) {
|
||||
if (!arg.IsAdmin) return;
|
||||
ApplyAll();
|
||||
arg.ReplyWith("PlantBiomeTuner: applied settings and refilled populations.");
|
||||
}
|
||||
|
||||
// Core Logic //////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ApplyAll() {
|
||||
int changed = 0;
|
||||
int matched = 0;
|
||||
|
||||
var populations = Resources.FindObjectsOfTypeAll<SpawnPopulation>();
|
||||
if (populations == null || populations.Length == 0) {
|
||||
PrintWarning("No SpawnPopulation objects found (map not ready yet?). Will retry next tick.");
|
||||
NextTick(ApplyAll);
|
||||
return;
|
||||
}
|
||||
|
||||
if (configData.rules == null || configData.rules.Count == 0) {
|
||||
Puts("PlantBiomeTuner: no rules configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var kv in configData.rules) {
|
||||
string ruleKey = kv.Key;
|
||||
PlantRule rule = kv.Value;
|
||||
if (rule == null || !rule.enabled) continue;
|
||||
|
||||
var filters = (rule.prefabFilters ?? Array.Empty<string>())
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.Select(s => s.Trim())
|
||||
.ToArray();
|
||||
if (filters.Length == 0) continue;
|
||||
|
||||
int biomeMask = BuildBiomeMask(rule.biomes);
|
||||
if (biomeMask == 0) {
|
||||
PrintWarning($"[{ruleKey}] Biome mask is empty; skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var population in populations) {
|
||||
if (population == null) continue;
|
||||
|
||||
string id = GetPopulationIdentity(population);
|
||||
if (string.IsNullOrEmpty(id)) continue;
|
||||
|
||||
if (!filters.Any(f => id.IndexOf(f, StringComparison.OrdinalIgnoreCase) >= 0)) continue;
|
||||
|
||||
matched++;
|
||||
|
||||
try {
|
||||
bool anyChange = false;
|
||||
anyChange |= TrySetBiomeMask(population, biomeMask);
|
||||
anyChange |= TrySetDensity(population, rule.densityMultiplier);
|
||||
|
||||
if (anyChange) {
|
||||
changed++;
|
||||
Puts($"[{ruleKey}] Updated '{id}' (biomes={string.Join(",", rule.biomes)}, density x{rule.densityMultiplier:0.##})");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
PrintWarning($"[{ruleKey}] Failed to update '{id}': {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ConsoleSystem.Run(ConsoleSystem.Option.Server, "spawn.delete_populations");
|
||||
ConsoleSystem.Run(ConsoleSystem.Option.Server, "spawn.fill_populations");
|
||||
} catch (Exception e) {
|
||||
PrintWarning($"Failed to refill populations: {e.Message}");
|
||||
}
|
||||
|
||||
Puts($"PlantBiomeTuner: matched {matched} populations; applied changes to {changed}.");
|
||||
}
|
||||
|
||||
int BuildBiomeMask(IEnumerable<string> names) {
|
||||
if (names == null) return 0;
|
||||
int mask = 0;
|
||||
foreach (var n in names) {
|
||||
if (string.IsNullOrWhiteSpace(n)) continue;
|
||||
try {
|
||||
var value = (TerrainMeta.BiomeType)Enum.Parse(typeof(TerrainMeta.BiomeType), n.Trim(), true);
|
||||
mask |= (int)value; // combine provided flags; no manual shifts
|
||||
} catch {
|
||||
PrintWarning($"Unknown biome '{n}'. Ensure names match TerrainMeta.BiomeType.");
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
string GetPopulationIdentity(SpawnPopulation population) {
|
||||
try {
|
||||
var res = population.Resource;
|
||||
if (res != null) {
|
||||
var prefab = res.prefab;
|
||||
if (prefab != null) {
|
||||
var path = prefab.resourcePath;
|
||||
if (!string.IsNullOrEmpty(path)) return path;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return population.name ?? population.GetType().Name;
|
||||
}
|
||||
|
||||
bool TrySetBiomeMask(SpawnPopulation population, int biomeMask) {
|
||||
bool changed = false;
|
||||
|
||||
var field = population.GetType().GetField("BiomePreference") ??
|
||||
population.GetType().GetField("TopologyPreference");
|
||||
if (field != null && field.FieldType == typeof(int)) {
|
||||
int current = (int) field.GetValue(population);
|
||||
if (current != biomeMask) {
|
||||
field.SetValue(population, biomeMask);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
var prop = population.GetType().GetProperty("BiomePreference");
|
||||
if (prop != null && prop.PropertyType == typeof(int) && prop.CanWrite) {
|
||||
int current = (int) (prop.GetValue(population) ?? 0);
|
||||
if (current != biomeMask) {
|
||||
prop.SetValue(population, biomeMask, null);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TrySetDensity(SpawnPopulation population, float multiplier) {
|
||||
if (Mathf.Approximately(multiplier, 1f)) return false;
|
||||
|
||||
bool changed = false;
|
||||
|
||||
var densityField = population.GetType().GetField("Density");
|
||||
if (densityField != null && densityField.FieldType == typeof(float)) {
|
||||
float d = (float)densityField.GetValue(population);
|
||||
float nd = Mathf.Max(0f, d * multiplier);
|
||||
if (!Mathf.Approximately(d, nd)) {
|
||||
densityField.SetValue(population, nd);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
var sptField = population.GetType().GetField("SpawnPerTick");
|
||||
if (sptField != null && sptField.FieldType == typeof(float)) {
|
||||
float v = (float)sptField.GetValue(population);
|
||||
float nv = Mathf.Max(0f, v * multiplier);
|
||||
if (!Mathf.Approximately(v, nv)) {
|
||||
sptField.SetValue(population, nv);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
var clusterField = population.GetType().GetField("ClusterDensities");
|
||||
if (clusterField != null && typeof(float[]).IsAssignableFrom(clusterField.FieldType)) {
|
||||
var arr = (float[])clusterField.GetValue(population);
|
||||
if (arr != null && arr.Length > 0) {
|
||||
for (int i = 0; i < arr.Length; i++) arr[i] = Mathf.Max(0f, arr[i] * multiplier);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user