Files

232 lines
9.0 KiB
C#

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;
}
}
}