Add current state of development

This commit is contained in:
Andrew Miner
2025-09-02 21:23:59 -06:00
parent cd2fd3bea8
commit fb58946028
5 changed files with 609 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
// Ideally, this should be true, but that prevents you from breaking your own base parts
server.pve "False"
// Base Decay
decay.upkeep_inside_decay_scale "0.01"
decay.bracket_0_costfraction "0.0050"
decay.bracket_1_costfraction "0.0075"
decay.bracket_2_costfraction "0.0100"
decay.bracket_3_costfraction "0.0150"
// Corpse Decay
server.corpsedespawn "1440" // 1 days
// Vehicle Decay
baseridableanimal.decayminutes "1440" // 1 day
basesubmarine.deepwaterdecayminutes "10080" // 7 days
basesubmarine.outsidedecayminutes "43200" // 30 days
bike.outsidedecayminutes "10080" // 7 days
hotairballoon.outsidedecayminutes "2880" // 2 days
modularcar.outsidedecayminutes "43200" // 30 days
motorrowboat.decaystartdelayminutes "0" // right away
motorrowboat.deepwaterdecayminutes "1440" // 1 day
motorrowboat.outsidedecayminutes "10080" // 7 days
playerhelicopter.insidedecayminutes "43200" // 30 days
playerhelicopter.outsidedecayminutes "10080" // 7 days
ridablehorse.decayminutes "1440" // 1 day
snowmobile.outsidedecayminutes "43200" // 30 days
tugboat.tugdecayminutes "43200" // 30 days
// Disable holiday events
halloween.enabled "False"
xmas.enabled "False"
easter.enabled "False"
// Minimal respawn time
server.respawntimeadditionbag "0" // Extra cooldown for bags
server.respawntimeadditionbed "0" // Extra cooldown for beds
// Security
ownerid 76561198025237120 "TungstonMiner" "sysadmin"
// Tax Rates
server.workbench1taxrate "0"
server.workbench2taxrate "0"
server.workbench3taxrate "0"
// Animal Populations
// Animal populations should reflect realistic proportion of predators to prey. They should also
// appear at about twice the natural population density to provide a good balance between realism
// and fun gameplay.
bear.population "0.33" // per sqkm, 3 total
boar.population "2.77" // per sqkm, 25 total
chicken.population "11.11" // per sqkm, 100 total
polarbear.population "0.11" // per sqkm, 1 total
crocodile.population "0.22" // per sqkm, 2 total
panther.population "0.11" // per sqkm, 1 total
ridablehorse.population "1.00" // per sqkm, 9 total
snakehazard.population "5.55" // per sqkm, 50 total
tiger.population "0.00" // per sqkm, none, only panthers
stag.population "5.55" // per sqkm, 50 total
wolf.population "0.55" // per sqkm, 5 total
// Vehicle Populations
bike.motorbikemonumentpopulation "0.50" // 50% chance per monument
bike.pedalmonumentpopulation "2.00" // 2 per monument
bike.pedalroadsidepopulation "2.22" // per sqkm, 20 total
hotairballoon.population "0.11" // per sqkm, 1 total
minicopter.population "0.22" // per sqkm, 2 total
modularcar.population "1.11" // per sqkm, 10 total
motorrowboat.population "1.11" // per sqkm, 10 total
rhib.rhibpopulation "0.00" // per sqkm, 0 total
scraptransporthelicopter.population "0.11" // per sqkm, 1 total
traincar.population "0.66" // per sqkm, 6 total
+240
View File
@@ -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);
}
}
}
+16
View File
@@ -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");
});
}
}
}
+46
View File
@@ -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");
}
}
}
+231
View File
@@ -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;
}
}
}