C#:
using System;
using System.Text;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Reflection;
using UnityEngine;
using Oxide.Core;
using Oxide.Core.Configuration;
using Rust;
namespace Oxide.Plugins {
[Info("SpawnControl", "FuJiCuRa", "1.6.5", ResourceId = 18)]
[Description("Provides complete control over the population of animals, ore's, tree's, junkpiles and all others")]
internal class SpawnControl : RustPlugin {
private bool Changed = false;
private bool _loaded = false;
private bool isAtStartup = true;
private bool onTerrainCalled;
private bool heartbeatOn = false;
private double lastMinute;
private bool newSave;
private int versionMajor;
private int versionMinor;
private bool _newConfig = false;
private Dictionary<string, object> spawnPopulations = new Dictionary<string, object>();
private Dictionary<string, object> spawnGroups = new Dictionary<string, object>();
private Dictionary<string, object> spawnDefaults = new Dictionary<string, object>();
private Dictionary<string, object> spawnPrefabDefaults = new Dictionary<string, object>();
private List<string> PopulationNames = new List<string>();
private Dictionary<string, ConvarControlledSpawnPopulation> convarCommands = new Dictionary<string, ConvarControlledSpawnPopulation>();
private Dictionary<string, string> populationConvars = new Dictionary<string, string>();
private Dictionary<string, int> fillPopulations = new Dictionary<string, int>();
private Dictionary<string, int> fillJobs = new Dictionary<string, int>();
private DynamicConfigFile defaultSpawnPops;
private DynamicConfigFile getFile(string file) {
return Interface.Oxide.DataFileSystem.GetDatafile($"{file}");
}
private bool chkFile(string file) {
return Interface.Oxide.DataFileSystem.ExistsDatafile($"{file}");
}
private FieldInfo _numToSpawn = typeof(SpawnPopulation).GetField("numToSpawn", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
private Coroutine _groupKill;
private Coroutine _groupFill;
private Coroutine _spawnKill;
private Coroutine _spawnFill;
private Coroutine _enforceLimits;
private Coroutine _spawnLowerDensity;
private Coroutine _spawnRaiseDensity;
private bool normalizeDefaultVariables;
private bool reloadWithIncludedFill;
private bool logJobsToConsole;
private bool fixBarricadeStacking;
private bool fillAtEveryStartup;
private float tickInterval;
private int minSpawnsPerTick;
private int maxSpawnsPerTick;
private SpawnPopulation[] AllSpawnPopulations = null;
private SpawnDistribution[] SpawnDistributions = null;
private List<string> killAllProtected;
private bool currentTickStatus;
private object GetConfig(string menu, string datavalue, object defaultValue) {
Dictionary<string, object> data = Config[menu] as Dictionary<string, object>;
if (data == null) {
data = new Dictionary<string, object>();
Config[menu] = data;
Changed = true;
}
object value;
if (!data.TryGetValue(datavalue, out value)) {
value = defaultValue;
data[datavalue] = value;
Changed = true;
}
return value;
}
private void LoadVariables() {
spawnPopulations = (Dictionary<string, object>)GetConfig("Spawn", "Population", new Dictionary<string, object>());
spawnGroups = (Dictionary<string, object>)GetConfig("Spawn", "Groups", new Dictionary<string, object>());
normalizeDefaultVariables = Convert.ToBoolean(GetConfig("Generic", "normalizeDefaultVariables", true));
reloadWithIncludedFill = Convert.ToBoolean(GetConfig("Generic", "reloadWithIncludedFill", false));
fillAtEveryStartup = Convert.ToBoolean(GetConfig("Generic", "fillAtEveryStartup", true));
logJobsToConsole = Convert.ToBoolean(GetConfig("Generic", "logJobsToConsole", true));
fixBarricadeStacking = Convert.ToBoolean(GetConfig("Generic", "fixBarricadeStacking", true));
tickInterval = Convert.ToSingle(GetConfig("Handler", "tickInterval", 60f));
minSpawnsPerTick = Convert.ToInt32(GetConfig("Handler", "minSpawnsPerTick", 100));
maxSpawnsPerTick = Convert.ToInt32(GetConfig("Handler", "maxSpawnsPerTick", 200));
bool configremoval = false;
if (Config.Get("Animals") as Dictionary<string, object> != null) {
Config.Remove("Animals");
configremoval = true;
}
if ((Config.Get("Generic") as Dictionary<string, object>).ContainsKey("enablePopulationPrefabs")) {
(Config.Get("Generic") as Dictionary<string, object>).Remove("enablePopulationPrefabs");
configremoval = true;
}
if ((Config.Get("Generic") as Dictionary<string, object>).ContainsKey("enableGroupPrefabs")) {
(Config.Get("Generic") as Dictionary<string, object>).Remove("enableGroupPrefabs");
configremoval = true;
}
if ((Config.Get("Generic") as Dictionary<string, object>).ContainsKey("enableSpawnGroups")) {
(Config.Get("Generic") as Dictionary<string, object>).Remove("enableSpawnGroups");
configremoval = true;
}
if ((Config.Get("Generic") as Dictionary<string, object>).ContainsKey("enableGroupTickData")) {
(Config.Get("Generic") as Dictionary<string, object>).Remove("enableGroupTickData");
configremoval = true;
}
if (!Changed && !configremoval) return;
SaveConfig();
Changed = false;
configremoval = false;
}
protected override void LoadDefaultConfig() {
_newConfig = true;
Config.Clear();
LoadVariables();
}
private void Init() {
LoadVariables();
onTerrainCalled = false;
if (normalizeDefaultVariables) {
ConVar.Spawn.max_density = 1f;
ConVar.Spawn.max_rate = 1f;
ConVar.Spawn.min_density = 0.5f;
ConVar.Spawn.min_rate = 0.5f;
SetTickConvars(true);
}
newSave = false;
lastMinute = DateTime.UtcNow.Minute;
}
private void OnNewSave(string strFilename) {
newSave = true;
if (!fillAtEveryStartup) {
ConVar.Spawn.respawn_populations = false;
ConVar.Spawn.respawn_groups = false;
}
}
private void Unload() {
if (Interface.Oxide.IsShuttingDown) return;
UnloadCoRoutines();
}
private void UnloadCoRoutines() {
if (_groupKill != null) Global.Runner.StopCoroutine(_groupKill);
if (_groupFill != null) Global.Runner.StopCoroutine(_groupFill);
if (_spawnKill != null) Global.Runner.StopCoroutine(_spawnKill);
if (_spawnFill != null) Global.Runner.StopCoroutine(_spawnFill);
if (_enforceLimits != null) Global.Runner.StopCoroutine(_enforceLimits);
if (_spawnLowerDensity != null) Global.Runner.StopCoroutine(_spawnLowerDensity);
if (_spawnRaiseDensity != null) Global.Runner.StopCoroutine(_spawnRaiseDensity);
}
private void OnTerrainInitialized() {
_loaded = true;
AllSpawnPopulations = SpawnHandler.Instance.AllSpawnPopulations;
SpawnDistributions = SpawnHandler.Instance.SpawnDistributions;
if (isAtStartup && !newSave) {
onTerrainCalled = true;
SaveDefaultsToFile();
}
SpawnHandler.Instance.MaxSpawnsPerTick = maxSpawnsPerTick;
SpawnHandler.Instance.MinSpawnsPerTick = minSpawnsPerTick;
if (spawnPopulations != null && spawnPopulations.Count > 0) LoadSpawnPopulations();
else GetSpawnPopulationDefaults();
if (spawnGroups != null && spawnGroups.Count > 0) LoadSpawnGroups();
else GetSpawnGroupDefaults();
}
private void OnServerInitialized() {
isAtStartup = false;
AllSpawnPopulations = SpawnHandler.Instance.AllSpawnPopulations;
SpawnDistributions = SpawnHandler.Instance.SpawnDistributions;
GetPopulationNames();
if (!_loaded) OnTerrainInitialized();
if (fillJobs.Count > 0) Puts($"Planned {fillJobs.Count} job(s) for spawn population");
LoadDefaultsFromFile();
if (onTerrainCalled) ReloadAnimals();
if (onTerrainCalled) {
if (fillAtEveryStartup) SpawnHandler.Instance.InitialSpawn();
else if (!fillAtEveryStartup && newSave) SetTickConvars(true);
}
SpawnHandler.Instance.StartSpawnTick();
currentTickStatus = true;
}
private void OnEntitySpawned(BaseNetworkable entity) {
if (!_loaded || !fixBarricadeStacking || entity == null || !(entity as BaseEntity).ShortPrefabName.StartsWith("door_barricade")) return;
NextTick(() => {
if (entity == null) return;
BaseEntity[] stacked = null;
try {
stacked = Physics.OverlapSphere(entity.transform.position, 1.0f).Where(entry => entry != null && entry.GetComponentInParent<BaseEntity>() != null && entry.GetComponentInParent<BaseEntity>().ShortPrefabName.StartsWith("door_barricade")).Select(entry => entry.GetComponentInParent<BaseEntity>()).Distinct().OrderByDescending(c => c.net.ID).ToArray();
}
catch {
}
if (stacked != null && stacked.Length > 1) for (int i = 0; i < stacked.Length - 1; i++) stacked[i].Kill();
}
);
}
private void OnServerCommand(ConsoleSystem.Arg arg) {
if (arg == null || arg.cmd == null || arg.FullString == string.Empty || arg.cmd.FullName == string.Empty) return;
if (populationConvars.ContainsKey(arg.cmd.FullName)) {
float targetDensity = -2;
float.TryParse(arg.Args[0], out targetDensity);
(spawnPopulations[populationConvars[arg.cmd.FullName]] as Dictionary<string, object>)["targetDensity"] = targetDensity;
Config["Spawn", "Population"] = spawnPopulations;
Config.Save();
}
}
private void OnTick() {
if (!_loaded || !heartbeatOn || lastMinute == DateTime.UtcNow.Minute || !(_spawnKill == null && _spawnFill == null && _enforceLimits == null && _spawnLowerDensity == null && _spawnRaiseDensity == null)) return;
lastMinute = DateTime.UtcNow.Minute;
foreach (KeyValuePair<string, int> job in fillJobs.ToList()) if ((int)fillJobs[job.Key] < 1) {
timer.Once(2f, () => ConsoleSystem.Run(ConsoleSystem.Option.Server.Quiet(), "sc.fillpopulation", new object[] {
job.Key.ToString(), "auto"
}
));
fillJobs[job.Key] = fillPopulations[job.Key];
}
else {
fillJobs[job.Key] = (int)job.Value - 1;
}
}
private void PauseTick() {
if (currentTickStatus) SetTickConvars(false);
}
private void ResumeTick() {
if (currentTickStatus) SetTickConvars(true);
}
private void ReloadAnimals() {
Dictionary<string, object> data = new Dictionary<string, object>(spawnPopulations);
if (data == null || data.Count() == 0) {
GetSpawnPopulationDefaults();
return;
}
for (int j = 0; j < AllSpawnPopulations.Length; j++) if (!(AllSpawnPopulations[j] == null)) {
SpawnPopulation population = AllSpawnPopulations[j];
object spawndata;
if (!data.TryGetValue(population.name, out spawndata)) continue;
if (!convarCommands.ContainsKey(population.name)) continue;
Dictionary<string, object> spawnData = spawndata as Dictionary<string, object>;
population.EnforcePopulationLimits = Convert.ToBoolean(spawnData["enforceLimits"]);
population.SpawnRate = Convert.ToSingle(spawnData["spawnRate"]);
population._targetDensity = Convert.ToSingle(spawnData["targetDensity"]);
SetAnimal(population.name, Convert.ToSingle(spawnData["targetDensity"]));
}
}
private void SetAnimal(string name, float value) {
if (convarCommands.ContainsKey(name)) ConsoleSystem.Run(ConsoleSystem.Option.Server.Quiet(), $"{convarCommands[name].PopulationConvar} {value.ToString()}");
}
private void SaveDefaultsToFile() {
spawnDefaults = new Dictionary<string, object>();
spawnPrefabDefaults = new Dictionary<string, object>();
for (int i = 0; i < AllSpawnPopulations.Length; i++) if (!(AllSpawnPopulations[i] == null)) {
SpawnPopulation population = AllSpawnPopulations[i];
if (!population.Initialize()) continue;
Dictionary<string, object> prefabdata = new Dictionary<string, object>();
Prefab<Spawnable>[] prefabs = population.Prefabs;
Dictionary<string, int> counts = prefabs.GroupBy(x => x.Name).ToDictionary(g => g.Key.ToLower(), g => g.Count());
foreach (KeyValuePair<string, int> prefab in counts) prefabdata.Add(prefab.Key.ToLower(), prefab.Value);
spawnPrefabDefaults.Add(population.name, prefabdata);
spawnDefaults.Add(population.name, population.TargetDensity);
}
defaultSpawnPops = getFile($"{Title}_defaults");
defaultSpawnPops.Clear();
defaultSpawnPops.Set("Backup", spawnDefaults);
defaultSpawnPops.Set("PrefabBackup", spawnPrefabDefaults);
defaultSpawnPops.Save();
}
private void LoadDefaultsFromFile() {
if (spawnDefaults == null || spawnDefaults.Count == 0) {
spawnDefaults = new Dictionary<string, object>();
defaultSpawnPops = getFile($"{Title}_defaults");
spawnDefaults = defaultSpawnPops["Backup"] as Dictionary<string, object>;
spawnPrefabDefaults = defaultSpawnPops["PrefabBackup"] as Dictionary<string, object>;
}
}
private void GetPopulationNames() {
PopulationNames = new List<string>();
foreach (SpawnPopulation pop in SpawnHandler.Instance.AllSpawnPopulations.Where(x => x != null).Distinct().ToList()) PopulationNames.Add(pop.name);
convarCommands = new Dictionary<string, ConvarControlledSpawnPopulation>();
populationConvars = new Dictionary<string, string>();
foreach (SpawnPopulation pop in SpawnHandler.Instance.ConvarSpawnPopulations.ToList().Where(x => x != null)) {
populationConvars.Add((pop as ConvarControlledSpawnPopulation).PopulationConvar, pop.name);
convarCommands.Add(pop.name, pop as ConvarControlledSpawnPopulation);
}
}
private void GetSpawnPopulationDefaults() {
Dictionary<string, object> data = new Dictionary<string, object>();
spawnPopulations.Clear();
for (int i = 0; i < AllSpawnPopulations.Length; i++) if (!(AllSpawnPopulations[i] == null)) {
SpawnPopulation population = AllSpawnPopulations[i];
if (!population.Initialize()) continue;
Dictionary<string, object> populationdata = new Dictionary<string, object>();
populationdata.Add("targetDensity", population.TargetDensity);
populationdata.Add("spawnRate", population.SpawnRate);
populationdata.Add("enforceLimits", population.EnforcePopulationLimits);
populationdata.Add("spawnFillHeartbeat", 0);
populationdata.Add("protectFromKillAll", false);
populationdata.Add("scaleWithServerPopulation", population.ScaleWithServerPopulation);
populationdata.Add("spawnFilter", GetSpawnFilter(population.Filter, population));
Dictionary<string, object> prefabdata = new Dictionary<string, object>();
Prefab<Spawnable>[] prefabs = population.Prefabs;
Dictionary<string, int> counts = prefabs.GroupBy(x => x.Name).ToDictionary(g => g.Key.ToLower(), g => g.Count());
foreach (KeyValuePair<string, int> prefab in counts) prefabdata.Add(prefab.Key.ToLower(), prefab.Value);
populationdata.Add("spawnWeights", prefabdata);
data.Add(population.name, populationdata);
}
spawnPopulations = new Dictionary<string, object>(data);
Config["Spawn", "Population"] = spawnPopulations;
Config.Save();
Puts($"Created SpawnPopulation with '{data.Count}' populations.");
}
private object GetSpawnFilter(SpawnFilter filter, SpawnPopulation population) {
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add(nameof(filter.TopologyAny).ToString(), (int)filter.TopologyAny);
dict.Add(nameof(filter.TopologyNot).ToString(), (int)filter.TopologyNot);
dict.Add(nameof(filter.TopologyAll).ToString(), (int)filter.TopologyAll);
return dict;
}
private SpawnFilter SetSpawnFilter(Dictionary<string, object> values, SpawnPopulation population) {
SpawnFilter filter = new SpawnFilter();
filter.BiomeType = population.Filter.BiomeType;
filter.SplatType = population.Filter.SplatType;
filter.TopologyAny = (TerrainTopology.Enum)(int)values[nameof(filter.TopologyAny).ToString()];
filter.TopologyAll = (TerrainTopology.Enum)(int)values[nameof(filter.TopologyAll).ToString()];
filter.TopologyNot = (TerrainTopology.Enum)(int)values[nameof(filter.TopologyNot).ToString()];
return filter;
}
private List<string> GetTerrainTopologies(int value) {
List<string> result = new List<string>();
string[] names = Enum.GetNames(typeof(TerrainTopology.Enum));
for (int i = 0; i < names.Length; i++) if ((value & (1 << i)) != 0) result.Add(names[i]);
return result;
}
[ConsoleCommand("sc.topologyget")]
private void GetTopology(ConsoleSystem.Arg arg) {
if (arg != null && !arg.IsAdmin) return;
if (arg.Args == null || arg.Args.Length < 1) {
SendReply(arg, "You need to provide a spawnFilter Topology value");
return;
}
List<string> allNames = Enum.GetNames(typeof(TerrainTopology.Enum)).ToList();
List<string> names = null;
if (arg.Args != null) {
int id = -1;
int.TryParse(arg.Args[0], out id);
if (id != -0) names = GetTerrainTopologies(id);
else return;
}
else {
return;
}
StringBuilder sb = new StringBuilder();
foreach (string name in names) sb.AppendLine($"{name} | {allNames.IndexOf(name)}");
SendReply(arg, "\n\n" + sb.ToString());
}
[ConsoleCommand("sc.topologylist")]
private void ListTopology(ConsoleSystem.Arg arg) {
if (!arg.IsAdmin) return;
List<string> names = Enum.GetNames(typeof(TerrainTopology.Enum)).ToList();
StringBuilder sb = new StringBuilder();
foreach (string name in names) sb.AppendLine($"{name} | {names.IndexOf(name)} | Single: {(int)Enum.Parse(typeof(TerrainTopology.Enum), name)}");
SendReply(arg, "\n\n" + sb.ToString());
}
[ConsoleCommand("sc.topologycreate")]
private void SetTopology(ConsoleSystem.Arg arg) {
if (arg != null && !arg.IsAdmin) return;
if (arg.Args == null || arg.Args.Length < 1) {
SendReply(arg, "You need to provide a list of spacer separated numbers (f.e.by: sc.topologylist)");
return;
}
List<string> allNames = Enum.GetNames(typeof(TerrainTopology.Enum)).ToList();
int maskOut = 0;
for (int i = 0; i < arg.Args.Length; i++) {
int id = -1;
int.TryParse(arg.Args[i], out id);
if (id != -1) maskOut += (int)Enum.Parse(typeof(TerrainTopology.Enum), allNames[id]);
}
SendReply(arg, "\n\nnew Topology mask is: " + maskOut);
}
private void LoadSpawnPopulations() {
killAllProtected = new List<string>();
Dictionary<string, object> data = new Dictionary<string, object>(spawnPopulations);
if (data == null || data.Count() == 0) {
GetSpawnPopulationDefaults();
return;
}
bool changed = false;
if (PopulationNames == null || PopulationNames.Count == 0) foreach (SpawnPopulation pop in SpawnHandler.Instance.AllSpawnPopulations.ToList().Where(x => x != null).Distinct().ToList()) PopulationNames.Add(pop.name);
foreach (KeyValuePair<string, object> population in data.ToList()) if (!PopulationNames.Contains(population.Key.ToString())) {
data.Remove(population.Key);
Puts($"Removed old SpawnPopulation: {population.Key}");
changed = true;
continue;
}
for (int i = 0; i < AllSpawnPopulations.Length; i++) if (!(AllSpawnPopulations[i] == null)) {
SpawnPopulation population = AllSpawnPopulations[i];
if (!population.Initialize()) continue;
string name = population.name;
if (name == null) continue;
if (!data.ContainsKey(name)) {
Dictionary<string, object> populationdata = new Dictionary<string, object>();
populationdata.Add("targetDensity", population.TargetDensity);
populationdata.Add("spawnRate", population.SpawnRate);
populationdata.Add("enforceLimits", population.EnforcePopulationLimits);
populationdata.Add("spawnFillHeartbeat", 0);
populationdata.Add("protectFromKillAll", false);
populationdata.Add("scaleWithServerPopulation", population.ScaleWithServerPopulation);
populationdata.Add("spawnFilter", GetSpawnFilter(population.Filter, population));
Prefab<Spawnable>[] prefabs = population.Prefabs;
Dictionary<string, int> counts = prefabs.GroupBy(x => x.Name).ToDictionary(g => g.Key, g => g.Count());
Dictionary<string, object> prefabdata = new Dictionary<string, object>();
foreach (KeyValuePair<string, int> prefab in counts) prefabdata.Add(prefab.Key, prefab.Value);
populationdata.Add("spawnWeights", prefabdata);
data.Add(name, populationdata);
Puts($"Added new SpawnPopulation: {name}");
changed = true;
}
if (!(data[name] as Dictionary<string, object>).ContainsKey("protectFromKillAll")) {
(data[name] as Dictionary<string, object>).Add("protectFromKillAll", false);
changed = true;
}
if (!(data[name] as Dictionary<string, object>).ContainsKey("spawnFilter")) {
(data[name] as Dictionary<string, object>).Add("spawnFilter", GetSpawnFilter(population.Filter, population));
changed = true;
}
if ((data[name] as Dictionary<string, object>).ContainsKey("spawnlimits")) {
(data[name] as Dictionary<string, object>).Remove("spawnlimits");
(data[name] as Dictionary<string, object>).Add("enforceLimits", true);
changed = true;
}
if (!(data[name] as Dictionary<string, object>).ContainsKey("scaleWithServerPopulation")) {
(data[name] as Dictionary<string, object>).Add("scaleWithServerPopulation", population.ScaleWithServerPopulation);
changed = true;
}
}
spawnPopulations = new Dictionary<string, object>(data);
if (changed) {
Config["Spawn", "Population"] = spawnPopulations;
Config.Save();
}
bool addedPrefabs = false;
for (int j = 0; j < AllSpawnPopulations.Length; j++) if (!(AllSpawnPopulations[j] == null)) {
SpawnPopulation population = AllSpawnPopulations[j];
if (!population.Initialize()) continue;
object spawndata;
if (!data.TryGetValue(population.name, out spawndata)) continue;
Dictionary<string, object> spawnData = spawndata as Dictionary<string, object>;
if (!spawnData.ContainsKey("spawnWeights")) {
Prefab<Spawnable>[] prefabs = population.Prefabs;
Dictionary<string, int> counts = prefabs.GroupBy(x => x.Name).ToDictionary(g => g.Key.ToLower(), g => g.Count());
Dictionary<string, object> prefabdata = new Dictionary<string, object>();
foreach (KeyValuePair<string, int> prefab in counts) prefabdata.Add(prefab.Key.ToLower(), prefab.Value);
spawnData.Add("spawnWeights", prefabdata);
addedPrefabs = true;
}
else if (spawnData.ContainsKey("spawnWeights")) {
Dictionary<string, object> prefabData = spawnData["spawnWeights"] as Dictionary<string, object>;
List<Prefab<Spawnable>> prefabs = new List<Prefab<Spawnable>>();
foreach (KeyValuePair<string, object> prefab in prefabData.ToList()) {
GameObject gameObject = GameManager.server.FindPrefab(prefab.Key);
if (gameObject == null) {
Puts($"Removed invalid/removed prefab from '{population.name}': {prefab.Key}");
((data[population.name] as Dictionary<string, object>)["spawnWeights"] as Dictionary<string, object>).Remove(prefab.Key);
addedPrefabs = true;
continue;
}
Spawnable component = gameObject.GetComponent<Spawnable>();
if (component) for (int i = 0; i < Convert.ToInt32(prefab.Value);
i++) prefabs.Add(new Prefab<Spawnable>(prefab.Key.ToLower(), gameObject, component, GameManager.server, PrefabAttribute.server));
}
population.Prefabs = prefabs.ToArray();
_numToSpawn.SetValue(population, new int[prefabs.ToArray().Length]);
}
population.EnforcePopulationLimits = Convert.ToBoolean(spawnData["enforceLimits"]);
population.SpawnRate = Convert.ToSingle(spawnData["spawnRate"]);
population._targetDensity = Convert.ToSingle(spawnData["targetDensity"]);
population.ScaleWithServerPopulation = Convert.ToBoolean(spawnData["scaleWithServerPopulation"]);
SetAnimal(population.name, Convert.ToSingle(spawnData["targetDensity"]));
if ((int)spawnData["spawnFillHeartbeat"] > 0) {
fillPopulations.Add(population.name, (int)spawnData["spawnFillHeartbeat"]);
heartbeatOn = true;
}
if (Convert.ToBoolean(spawnData["protectFromKillAll"])) killAllProtected.Add(population.name);
if (spawnData.ContainsKey("spawnFilter")) population.Filter = SetSpawnFilter((Dictionary<string, object>)spawnData["spawnFilter"], population);
}
if (addedPrefabs) {
spawnPopulations = new Dictionary<string, object>(data);
Config["Spawn", "Population"] = spawnPopulations;
Config.Save();
}
if (heartbeatOn) fillJobs = new Dictionary<string, int>(fillPopulations);
Puts($"Loaded '{data.Count}' SpawnPopulations");
}
private void GetSpawnGroupDefaults() {
if (TerrainMeta.Path.Monuments.Count == 0) return;
Dictionary<string, List<MonumentInfo>> monumentGroups = TerrainMeta.Path?.Monuments?.GroupBy(c => c.displayPhrase.english)?.ToDictionary(c => c.Key, c => c.ToList());
if (monumentGroups == null) return;
foreach (KeyValuePair<string, List<MonumentInfo>> current in monumentGroups) {
MonumentInfo monument = current.Value.First();
string displayPhrase = monument.displayPhrase.english;
if (displayPhrase.Length == 0) continue;
SpawnGroup[] groups = monument.gameObject.GetComponentsInChildren<SpawnGroup>();
if ((groups == null) | (groups.Length == 0)) continue;
List<object> list = new List<object>();
foreach (SpawnGroup group in groups.ToList()) {
Dictionary<string, object> spawner = new Dictionary<string, object>();
spawner.Add("_refDisplayPhrase", group.name);
spawner.Add("_refMaxPopulation", group.maxPopulation);
Vector3 center = Vector3.zero;
foreach (BaseSpawnPoint spawnPoint in group.spawnPoints.ToList()) center += spawnPoint.transform.position;
center /= group.spawnPoints.Length;
spawner.Add("_refPositionCenter", center.ToString().Replace(" ", ""));
Dictionary<string, object> prefabdata = new Dictionary<string, object>();
foreach (SpawnGroup.SpawnEntry prefab in group.prefabs) {
if (prefab.prefab.resourcePath.Contains("/npc/")) continue;
prefabdata[prefab.prefab.resourcePath] = prefab.weight;
}
if (prefabdata.Count == 0) continue;
spawner.Add("spawnWeights", prefabdata);
spawner.Add("respawnDelayMin", group.respawnDelayMin);
spawner.Add("respawnDelayMax", group.respawnDelayMax);
spawner.Add("numToSpawnPerTickMin", group.numToSpawnPerTickMin);
spawner.Add("numToSpawnPerTickMax", group.numToSpawnPerTickMax);
list.Add(spawner);
}
if (list.Count > 0) spawnGroups[displayPhrase] = list;
}
Config["Spawn", "Groups"] = spawnGroups;
Config.Save();
Puts($"Created '{spawnGroups.Count()}' spawnGroups");
}
private void LoadSpawnGroups() {
if (TerrainMeta.Path.Monuments.Count == 0) return;
if (spawnGroups == null || spawnGroups.Count() == 0) {
GetSpawnGroupDefaults();
return;
}
bool addedData = false;
foreach (KeyValuePair<string, object> monument in spawnGroups) {
foreach (object spawner in monument.Value as List<object>) {
Dictionary<string, object> group = spawner as Dictionary<string, object>;
if (group.ContainsKey("_refPositionCenter")) {
group.Remove("_refPositionCenter");
addedData = true;
}
}
}
Dictionary<string, List<MonumentInfo>> monumentGroups = TerrainMeta.Path?.Monuments?.GroupBy(c => c.displayPhrase.english)?.ToDictionary(c => c.Key, c => c.ToList());
if (monumentGroups == null) return;
foreach (KeyValuePair<string, List<MonumentInfo>> current in monumentGroups) {
MonumentInfo monument = current.Value.First();
string displayPhrase = monument.displayPhrase.english;
if (displayPhrase.Length == 0) continue;
SpawnGroup[] groups = monument.gameObject.GetComponentsInChildren<SpawnGroup>();
if ((groups == null) | (groups.Length == 0)) continue;
List<object> loadedGroups = new List<object>();
if (!spawnGroups.ContainsKey(displayPhrase)) {
List<object> list = new List<object>();
foreach (SpawnGroup group in groups.ToList()) {
Dictionary<string, object> spawner = new Dictionary<string, object>();
spawner.Add("_refDisplayPhrase", group.name);
spawner.Add("_refMaxPopulation", group.maxPopulation);
Vector3 center = Vector3.zero;
foreach (BaseSpawnPoint spawnPoint in group.spawnPoints.ToList()) center += spawnPoint.transform.position;
center /= group.spawnPoints.Length;
spawner.Add("_refPositionCenter", center.ToString().Replace(" ", ""));
Dictionary<string, object> prefabdata = new Dictionary<string, object>();
foreach (SpawnGroup.SpawnEntry prefab in group.prefabs) {
if (prefab.prefab.resourcePath.Contains("/npc/")) continue;
prefabdata[prefab.prefab.resourcePath] = prefab.weight;
}
if (prefabdata.Count == 0) continue;
spawner.Add("spawnWeights", prefabdata);
spawner.Add("respawnDelayMin", group.respawnDelayMin);
spawner.Add("respawnDelayMax", group.respawnDelayMax);
spawner.Add("numToSpawnPerTickMin", group.numToSpawnPerTickMin);
spawner.Add("numToSpawnPerTickMax", group.numToSpawnPerTickMax);
list.Add(spawner);
}
if (list.Count > 0) {
spawnGroups[displayPhrase] = list;
addedData = true;
}
continue;
}
loadedGroups = spawnGroups[displayPhrase] as List<object>;
List<object> list2 = new List<object>();
foreach (SpawnGroup group in groups.ToList()) {
bool groupMatch = false;
foreach (object loadedGroup in loadedGroups) {
Dictionary<string, object> checkGroup = loadedGroup as Dictionary<string, object>;
if (checkGroup.ContainsKey("_refDisplayPhrase") && checkGroup["_refDisplayPhrase"].Equals(group.name)) {
if (checkGroup.ContainsKey("_refMaxPopulation") && (int)checkGroup["_refMaxPopulation"] == @group.maxPopulation) {
@group.respawnDelayMin = Convert.ToSingle(checkGroup["respawnDelayMin"]);
@group.respawnDelayMax = Convert.ToSingle(checkGroup["respawnDelayMax"]);
@group.numToSpawnPerTickMin = (int)checkGroup["numToSpawnPerTickMin"];
@group.numToSpawnPerTickMax = (int)checkGroup["numToSpawnPerTickMax"];
if (@group.WantsTimedSpawn()) {
LocalClock spawnClock = new LocalClock();
spawnClock.Add(@group.GetSpawnDelta(), @group.GetSpawnVariance(), new Action(@group.Spawn));
spawnClock.Tick();
@group.spawnClock = spawnClock;
}
Vector3 center = Vector3.zero;
foreach (BaseSpawnPoint spawnPoint in @group.spawnPoints.ToList()) center += spawnPoint.transform.position;
center /= @group.spawnPoints.Length;
checkGroup["_refPositionCenter"] = center.ToString().Replace(" ", "");
addedData = true;
groupMatch = true;
}
}
}
if (!groupMatch) {
foreach (SpawnGroup.SpawnEntry prefab in @group.prefabs) {
if (!prefab.prefab.resourcePath.Contains("/npc/")) {
Puts($"Adding '{@group.name}' to '{displayPhrase}'");
Dictionary<string, object> spawner = new Dictionary<string, object>();
spawner.Add("_refDisplayPhrase", @group.name);
spawner.Add("_refMaxPopulation", @group.maxPopulation);
Vector3 center = Vector3.zero;
foreach (BaseSpawnPoint spawnPoint in @group.spawnPoints.ToList()) center += spawnPoint.transform.position;
center /= @group.spawnPoints.Length;
spawner.Add("_refPositionCenter", center.ToString().Replace(" ", ""));
Dictionary<string, object> prefabdata = new Dictionary<string, object>();
foreach (SpawnGroup.SpawnEntry prefab2 in @group.prefabs) {
if (prefab2.prefab.resourcePath.Contains("scientist")) continue;
prefabdata[prefab2.prefab.resourcePath] = prefab2.weight;
}
if (prefabdata.Count == 0) continue;
spawner.Add("spawnWeights", prefabdata);
spawner.Add("respawnDelayMin", @group.respawnDelayMin);
spawner.Add("respawnDelayMax", @group.respawnDelayMax);
spawner.Add("numToSpawnPerTickMin", @group.numToSpawnPerTickMin);
spawner.Add("numToSpawnPerTickMax", @group.numToSpawnPerTickMax);
(spawnGroups[displayPhrase] as List<object>).Add(spawner);
addedData = true;
}
}
}
}
}
if (addedData) {
Config["Spawn", "Groups"] = spawnGroups;
Config.Save();
}
Puts($"Loaded '{spawnGroups.Count()}' spawnGroups");
}
[ConsoleCommand("sc.cleardata")]
private void ccmdClearData(ConsoleSystem.Arg arg) {
if (arg.Connection != null && arg.Connection.authLevel < 2) return;
string data = string.Empty;
if (arg.Args == null || arg.Args.Length < 1) {
SendReply(arg, "You need to provide 'weights', 'population', 'groups' or 'all'");
return;
}
data = arg.Args[0];
if (data == "weights") {
foreach (KeyValuePair<string, object> pop in spawnPopulations) if ((spawnPopulations[pop.Key] as Dictionary<string, object>).ContainsKey("spawnWeights")) (spawnPopulations[pop.Key] as Dictionary<string, object>).Remove("spawnWeights");
Config["Spawn", "Population"] = spawnPopulations;
Config.Save();
SendReply(arg, "Did reset weights data");
}
else if (data == "population") {
spawnPopulations.Clear();
Config["Spawn", "Population"] = spawnPopulations;
Config.Save();
SendReply(arg, "Did reset population data.");
}
else if (data == "groups") {
spawnGroups.Clear();
Config["Spawn", "Groups"] = spawnGroups;
Config.Save();
SendReply(arg, "Did reset groups data");
}
else if (data == "all") {
spawnGroups.Clear();
spawnPopulations.Clear();
Config["Spawn", "Groups"] = spawnGroups;
Config["Spawn", "Population"] = spawnPopulations;
Config.Save();
SendReply(arg, "Did reset all data.Next server boot loads the game defaults");
}
else {
SendReply(arg, "You need to provide 'weights', 'population', 'groups' or 'all'");
return;
}
SendReply(arg, "Next server boot writes the game defaults back into the config.");
}
[ConsoleCommand("sc.tickinterval")]
private void ccmdTickInterval(ConsoleSystem.Arg arg) {
if (arg.Connection != null && arg.Connection.authLevel < 2) return;
float currTickInterval = SpawnHandler.Instance.TickInterval;
if (arg.Args == null) {
SendReply(arg, $"Current TickInterval is '{currTickInterval}' seconds, spawntick is {(currentTickStatus ? "active" : "inactive")}");
return;
}
if (arg.Args != null && arg.Args.Length > 0) {
float input = 0f;
if (float.TryParse(arg.Args[0], out input)) {
currTickInterval = input;
SendReply(arg, $"TickInterval set to '{currTickInterval}' seconds, spawntick is {(currentTickStatus ? "active" : "inactive")}");
SpawnHandler.Instance.TickInterval = currTickInterval;
if (currTickInterval != tickInterval) {
tickInterval = currTickInterval;
Config["Handler", "tickInterval"] = tickInterval;
Config.Save();
}
return;
}
else {
SendReply(arg, "You need to provide a full number in seconds");
return;
}
}
}
private void SetTickConvars(bool status) {
ConVar.Spawn.respawn_populations = status;
ConVar.Spawn.respawn_groups = status;
ConVar.Spawn.respawn_individuals = status;
}
[ConsoleCommand("sc.spawnticktoggle")]
private void ccmdToggleSpawnTick(ConsoleSystem.Arg arg) {
if (arg.Connection != null && arg.Connection.authLevel < 2) return;
currentTickStatus = !currentTickStatus;
SetTickConvars(currentTickStatus);
SendReply(arg, $"Spawntick is now: {(currentTickStatus ? "active" : "inactive")}");
}
[ConsoleCommand("sc.fillpopulation")]
private void ccmdSpawnFill(ConsoleSystem.Arg arg) {
if (arg.Connection != null && arg.Connection.authLevel < 2) return;
string populationname = string.Empty;
bool single = false;
if (arg.Args != null && arg.Args.Length > 0) {
populationname = arg.Args[0];
single = true;
}
if (_spawnFill != null) {
SendReply(arg, "Action aborted: another task is still running!");
return;
}
PauseTick();
_spawnFill = Global.Runner.StartCoroutine(SpawnFill(populationname, single, arg));
}
private IEnumerator SpawnFill(string populationname, bool single, ConsoleSystem.Arg arg) {
yield return new WaitWhile(() => _spawnKill != null || _enforceLimits != null || _spawnLowerDensity != null || _spawnRaiseDensity != null);
bool findany = false;
if (single) {
bool fullMatch = false;
for (int i = 0; i < AllSpawnPopulations.Length; i++) if (!(AllSpawnPopulations[i] == null)) if ((AllSpawnPopulations[i].name.Equals(populationname, StringComparison.OrdinalIgnoreCase) || AllSpawnPopulations[i].name.StartsWith(populationname, StringComparison.OrdinalIgnoreCase) || AllSpawnPopulations[i].name.EndsWith(populationname, StringComparison.OrdinalIgnoreCase)) && !fullMatch) {
if (AllSpawnPopulations[i].name == populationname) fullMatch = true;
int oldCount = SpawnHandler.Instance.GetCurrentCount(AllSpawnPopulations[i], SpawnDistributions[i]);
yield return CoroutineEx.waitForEndOfFrame;
SpawnHandler.Instance.SpawnInitial(AllSpawnPopulations[i], SpawnDistributions[i]);
yield return CoroutineEx.waitForEndOfFrame;
if (arg.Args.Length > 1 && arg.Args[1] == "auto") if (logJobsToConsole) SendReply(arg, $"Timed population of '{AllSpawnPopulations[i].name}' | Old: {oldCount} | New: {SpawnHandler.Instance.GetCurrentCount(AllSpawnPopulations[i], SpawnDistributions[i])}");
if (arg.Args.Length == 1) SendReply(arg, $"Forced population of '{AllSpawnPopulations[i].name}' | Old: {oldCount} | New: {SpawnHandler.Instance.GetCurrentCount(AllSpawnPopulations[i], SpawnDistributions[i])}");
findany = true;
}
if (!findany) SendReply(arg, $"SpawnPopulation '{populationname}' not found");
}
else {
int oldCount = 0;
int newCount = 0;
SendReply(arg, "Filling All SpawnPopulations");
for (int i = 0; i < AllSpawnPopulations.Length; i++) if (!(AllSpawnPopulations[i] == null)) {
oldCount += SpawnHandler.Instance.GetCurrentCount(AllSpawnPopulations[i], SpawnDistributions[i]);
SpawnHandler.Instance.SpawnInitial(AllSpawnPopulations[i], SpawnDistributions[i]);
yield return CoroutineEx.waitForEndOfFrame;
newCount += SpawnHandler.Instance.GetCurrentCount(AllSpawnPopulations[i], SpawnDistributions[i]);
yield return CoroutineEx.waitForEndOfFrame;
}
SendReply(arg, $"Filled SpawnPopulations | Old: {oldCount} | New: {newCount}");
}
_spawnFill = null;
ResumeTick();
yield break;
}
[ConsoleCommand("sc.raisedensity")]
private void ccmdSpawnRaiseDensity(ConsoleSystem.Arg arg) {
if (arg.Connection != null && arg.Connection.authLevel < 2) return;
// ... [Code preview truncated due to length. Full source in attached file] ...