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