Add current state of development
This commit is contained in:
@@ -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
|
||||||
@@ -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