Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CustomBeehiveSpawns v1.0.0
CustomBeehiveSpawns.dll
Decompiled 2 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CustomBeehiveSpawns.Patches; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("CustomBeehiveSpawns")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("CustomBeehiveSpawns")] [assembly: AssemblyTitle("CustomBeehiveSpawns")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace CustomBeehiveSpawns { internal static class HiveSeeder { private sealed class AddedBees { public SpawnableEnemyWithRarity Entry; public int Weight; public float Chance; } private sealed class Snapshot { public SelectableLevel Level; public SpawnableEnemyWithRarity AddedEntry; public SpawnableEnemyWithRarity RarityChanged; public int OriginalRarity; public bool DaytimeWokenUp; public int OriginalMaxDaytimePower; public AnimationCurve OriginalCurve; } private const int RollSeedOffset = 2286; private static readonly List<Snapshot> Touched = new List<Snapshot>(); private static readonly Dictionary<SelectableLevel, AddedBees> Added = new Dictionary<SelectableLevel, AddedBees>(); private static readonly HashSet<SelectableLevel> Handled = new HashSet<SelectableLevel>(); private static EnemyType _bees; private static int _autoWeight; private static SelectableLevel _reference; public static void BindConfig(SelectableLevel[] levels) { if (levels == null) { return; } foreach (SelectableLevel val in levels) { if (!((Object)(object)val == (Object)null) && val.spawnEnemiesAndScrap) { SpawnableEnemyWithRarity val2 = FindBeeEntry(val); if (val2 != null) { Plugin.Cfg.WeightFor(val, val2.rarity); } else { Plugin.Cfg.ChanceFor(val); } } } } public static void ApplyTo(SelectableLevel level) { if (!((Object)(object)level == (Object)null) && Plugin.Cfg.Enabled.Value && (!((Object)(object)_bees == (Object)null) || ResolveVanillaSetup(StartOfRound.Instance?.levels)) && level.spawnEnemiesAndScrap && Handled.Add(level)) { SpawnableEnemyWithRarity val = FindBeeEntry(level); if (val != null) { ReweightVanillaMoon(level, val); } else { SeedBeelessMoon(level); } } } private static void ReweightVanillaMoon(SelectableLevel level, SpawnableEnemyWithRarity entry) { int num = Plugin.Cfg.WeightFor(level, entry.rarity); if (num < 0 || num == entry.rarity) { Plugin.DebugLog($"{level.PlanetName}: has bees at weight {entry.rarity}, left alone."); return; } Plugin.Log.LogInfo((object)$"{level.PlanetName}: bee weight {entry.rarity} -> {num}."); Touched.Add(new Snapshot { Level = level, RarityChanged = entry, OriginalRarity = entry.rarity }); entry.rarity = num; } private static void SeedBeelessMoon(SelectableLevel level) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown float num = Plugin.Cfg.ChanceFor(level); if (num <= 0f) { Plugin.DebugLog(level.PlanetName + ": set to 0%, so it stays bee-free."); return; } if (level.DaytimeEnemies == null) { level.DaytimeEnemies = new List<SpawnableEnemyWithRarity>(); } Snapshot snapshot = new Snapshot { Level = level }; SpawnableEnemyWithRarity val = (snapshot.AddedEntry = new SpawnableEnemyWithRarity(_bees, _autoWeight)); level.DaytimeEnemies.Add(val); WakeDaytimeSpawningIfAsleep(level, snapshot); Touched.Add(snapshot); Added[level] = new AddedBees { Entry = val, Weight = _autoWeight, Chance = num }; Plugin.Log.LogInfo((object)$"{level.PlanetName}: bees added at weight {_autoWeight}, rolled at {num:0.##}% per landing."); } public static void RollForRound(int randomSeed, SelectableLevel level) { if (!((Object)(object)level == (Object)null) && Added.TryGetValue(level, out var value)) { bool flag = new Random(randomSeed + 2286).NextDouble() * 100.0 < (double)value.Chance; value.Entry.rarity = (flag ? value.Weight : 0); Plugin.Log.LogInfo((object)(flag ? $"{level.PlanetName}: rolled {value.Chance:0.##}% and passed — bees are on the table today." : $"{level.PlanetName}: rolled {value.Chance:0.##}% and failed — no bees today.")); } } public static void Restore() { for (int num = Touched.Count - 1; num >= 0; num--) { Snapshot snapshot = Touched[num]; SelectableLevel level = snapshot.Level; if (!((Object)(object)level == (Object)null)) { if (snapshot.AddedEntry != null) { level.DaytimeEnemies?.Remove(snapshot.AddedEntry); } if (snapshot.RarityChanged != null) { snapshot.RarityChanged.rarity = snapshot.OriginalRarity; } if (snapshot.DaytimeWokenUp) { level.maxDaytimeEnemyPowerCount = snapshot.OriginalMaxDaytimePower; level.daytimeEnemySpawnChanceThroughDay = snapshot.OriginalCurve; } } } Touched.Clear(); Added.Clear(); Handled.Clear(); } private static bool ResolveVanillaSetup(SelectableLevel[] levels) { if ((Object)(object)_bees != (Object)null) { return true; } if (levels == null) { return false; } List<SelectableLevel> list = new List<SelectableLevel>(); int num = 0; foreach (SelectableLevel val in levels) { SpawnableEnemyWithRarity val2 = FindBeeEntry(val); if (!((Object)(object)val2?.enemyType == (Object)null)) { if (_bees == null) { _bees = val2.enemyType; } list.Add(val); num += val2.rarity; } } if ((Object)(object)_bees == (Object)null) { Plugin.Log.LogWarning((object)"No moon in this game has beehive bees in its daytime list, so there is nothing to copy from and the mod will stay out of the way."); return false; } _autoWeight = Mathf.Max(1, Mathf.RoundToInt((float)num / (float)list.Count)); _reference = PickReference(list); Plugin.Log.LogInfo((object)($"Bees found on {list.Count} moon(s); average weight {_autoWeight}. " + "Moons with no daytime creatures of their own will be paced like '" + _reference.PlanetName + "'.")); Plugin.DebugLog($"Bee creature '{_bees.enemyName}': power cost {_bees.PowerLevel}, spawn cap {_bees.MaxCount}. " + "A moon can hold as many swarms as its daytime budget divided by that power cost, up to the cap — that, and nothing else, is what limits hives per landing."); return true; } private static SelectableLevel PickReference(List<SelectableLevel> donors) { string text = MoonKey.Normalize(Plugin.Cfg.ReferenceMoon.Value); if (text.Length > 0) { foreach (SelectableLevel donor in donors) { if (MoonKey.Normalize(donor.PlanetName) == text) { return donor; } } Plugin.Log.LogWarning((object)("ReferenceMoon is set to '" + Plugin.Cfg.ReferenceMoon.Value + "', which is not a moon that has bees. Falling back to picking one automatically.")); } foreach (SelectableLevel donor2 in donors) { if (!DaytimeSpawningIsAsleep(donor2)) { return donor2; } } return donors[0]; } private static void WakeDaytimeSpawningIfAsleep(SelectableLevel level, Snapshot snapshot) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown if (!((Object)(object)_reference == (Object)null) && !((Object)(object)level == (Object)(object)_reference) && DaytimeSpawningIsAsleep(level)) { snapshot.DaytimeWokenUp = true; snapshot.OriginalMaxDaytimePower = level.maxDaytimeEnemyPowerCount; snapshot.OriginalCurve = level.daytimeEnemySpawnChanceThroughDay; int value = Plugin.Cfg.MaxHivesOnWokenMoons.Value; level.maxDaytimeEnemyPowerCount = ((_bees.PowerLevel > 0f) ? Mathf.Max(1, Mathf.CeilToInt(_bees.PowerLevel * (float)value)) : _reference.maxDaytimeEnemyPowerCount); if (CurveIsSilent(level)) { level.daytimeEnemySpawnChanceThroughDay = new AnimationCurve(_reference.daytimeEnemySpawnChanceThroughDay.keys); } Plugin.Log.LogInfo((object)(level.PlanetName + ": runs no daytime creatures in vanilla, so it was given " + $"{_reference.PlanetName}'s pacing and a budget for {value} hive(s).")); } } private static bool DaytimeSpawningIsAsleep(SelectableLevel level) { if (!((float)level.maxDaytimeEnemyPowerCount < _bees.PowerLevel)) { return CurveIsSilent(level); } return true; } private static bool CurveIsSilent(SelectableLevel level) { AnimationCurve daytimeEnemySpawnChanceThroughDay = level.daytimeEnemySpawnChanceThroughDay; if (daytimeEnemySpawnChanceThroughDay == null || daytimeEnemySpawnChanceThroughDay.length == 0) { return true; } float num = 0f; for (float num2 = 0f; num2 <= 1.0001f; num2 += 0.05f) { num = Mathf.Max(num, daytimeEnemySpawnChanceThroughDay.Evaluate(num2)); } return num + level.daytimeEnemiesProbabilityRange < 1f; } private static SpawnableEnemyWithRarity FindBeeEntry(SelectableLevel level) { List<SpawnableEnemyWithRarity> list = level?.DaytimeEnemies; if (list == null) { return null; } foreach (SpawnableEnemyWithRarity item in list) { EnemyType val = item?.enemyType; if (!((Object)(object)val == (Object)null) && !((Object)(object)val.enemyPrefab == (Object)null) && (Object)(object)val.enemyPrefab.GetComponentInChildren<RedLocustBees>(true) != (Object)null) { return item; } } return null; } } internal static class MoonKey { public static string Normalize(string name) { if (string.IsNullOrEmpty(name)) { return string.Empty; } int i; for (i = 0; i < name.Length && char.IsDigit(name[i]); i++) { } if (i >= name.Length) { i = 0; } StringBuilder stringBuilder = new StringBuilder(name.Length - i); for (int j = i; j < name.Length; j++) { char c = name[j]; if (c != ' ' && c != '-' && c != '_') { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } public static string ConfigKey(string planetName) { if (string.IsNullOrWhiteSpace(planetName)) { return "Unnamed"; } StringBuilder stringBuilder = new StringBuilder(planetName.Length); foreach (char c in planetName) { stringBuilder.Append(("=\n\t\\\"'[]".IndexOf(c) >= 0) ? '_' : c); } string text = stringBuilder.ToString().Trim(); if (text.Length != 0) { return text; } return "Unnamed"; } } [BepInPlugin("Solon.CustomBeehiveSpawns", "CustomBeehiveSpawns", "1.0.0")] public class Plugin : BaseUnityPlugin { private Harmony _harmony; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static PluginConfig Cfg { get; private set; } private void Awake() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Cfg = new PluginConfig(((BaseUnityPlugin)this).Config); _harmony = new Harmony("Solon.CustomBeehiveSpawns"); _harmony.PatchAll(typeof(StartOfRoundPatches)); _harmony.PatchAll(typeof(RoundManagerPatches)); Log.LogInfo((object)("CustomBeehiveSpawns v1.0.0 loaded. " + (Cfg.Enabled.Value ? "Hosting a moon will seed it; joining someone else's game changes nothing." : "Disabled in the config — every moon keeps the list it shipped with."))); } internal static void DebugLog(string message) { if (Cfg != null && Cfg.DebugMode.Value) { Log.LogInfo((object)("[Debug] " + message)); } } private void OnDestroy() { HiveSeeder.Restore(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal sealed class PluginConfig { public const string WeightSection = "Weight On Moons That Already Have Bees"; public const string ChanceSection = "Chance On Moons With No Bees"; public const int Vanilla = -1; public readonly ConfigEntry<bool> Enabled; public readonly ConfigEntry<float> DefaultChance; public readonly ConfigEntry<int> MaxHivesOnWokenMoons; public readonly ConfigEntry<string> ReferenceMoon; public readonly ConfigEntry<bool> DebugMode; private readonly ConfigFile _file; private readonly Dictionary<string, ConfigEntry<int>> _weights = new Dictionary<string, ConfigEntry<int>>(); private readonly Dictionary<string, ConfigEntry<float>> _chances = new Dictionary<string, ConfigEntry<float>>(); public PluginConfig(ConfigFile file) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown _file = file; Enabled = file.Bind<bool>("General", "Enabled", true, "Switches the whole mod off without uninstalling it.\n\n true moons get the bees the two lists below ask for\n false every moon keeps the creature list the game shipped it with, exactly as\n if the mod were not installed. Nothing else in this file has any effect."); DefaultChance = file.Bind<float>("General", "DefaultChance", 25f, new ConfigDescription("The percentage written into the file the first time a bee-less moon is seen —\nthat is Offense, Rend, Dine, Titan and Embrion on a fresh install, plus any\nmodded moon that has no bees of its own.\n\n 0 new bee-less moons stay bee-free, so the mod does nothing until you say so\n 25 what the vanilla bee moons work out at in practice (the default)\n 100 every new bee-less moon is allowed a hive on every landing\n\nOnly affects moons not yet in the file; entries already written are never\noverwritten, so changing this will not undo your edits.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); MaxHivesOnWokenMoons = file.Bind<int>("General", "MaxHivesOnWokenMoons", 1, new ConfigDescription("The most hives one landing can produce on a moon that runs no daytime creatures\nin vanilla — Titan and Dine.\n\nThose two have a daytime budget of zero, so the mod has to invent one for them,\nand bees are the only thing on it: nothing competes for the slots. Left uncapped\nthey would out-hive March several times over, which is why this exists.\n\n 1 at most one hive, like a quiet day on a vanilla bee moon (the default)\n 3 a small cluster, closer to a busy March\n 6 hive farming\n\nEvery other moon ignores this. Offense, Rend and Embrion already run daytime\ncreatures, so their own budget decides, and bees take slots from the manticoils\ninstead of adding to them — exactly as on the vanilla bee moons.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>())); ReferenceMoon = file.Bind<string>("General", "ReferenceMoon", "", "Which moon Titan and Dine copy their daytime pacing from.\n\nThey run no daytime creatures at all, so they have no pacing of their own to keep.\nThe mod lends them the time-of-day curve of a moon that does.\n\n (empty) pick one automatically. This is almost always what you want.\n Vow use Vow's pacing\n March use March's instead, the busiest of the vanilla bee moons\n\nOnly those woken moons are affected; every other moon keeps its own pacing."); DebugMode = file.Bind<bool>("General", "DebugMode", false, "How much the mod writes to the BepInEx console.\n\n false one line per moon it actually changed, plus one line per landing that a\n hive was rolled for\n true also lists the moons it deliberately left alone, their vanilla weights,\n and the bees' own power cost and spawn cap. Turn this on to read the real\n numbers off your own install before changing anything."); } public int WeightFor(SelectableLevel level, int vanillaWeight) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown if ((Object)(object)level == (Object)null || string.IsNullOrEmpty(level.PlanetName)) { return -1; } string key = MoonKey.Normalize(level.PlanetName); if (!_weights.TryGetValue(key, out var value)) { value = _file.Bind<int>("Weight On Moons That Already Have Bees", MoonKey.ConfigKey(level.PlanetName), -1, new ConfigDescription(WeightDescription(level.PlanetName, vanillaWeight), (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 200), Array.Empty<object>())); _weights[key] = value; } return value.Value; } public float ChanceFor(SelectableLevel level) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown if ((Object)(object)level == (Object)null || string.IsNullOrEmpty(level.PlanetName)) { return 0f; } string key = MoonKey.Normalize(level.PlanetName); if (!_chances.TryGetValue(key, out var value)) { value = _file.Bind<float>("Chance On Moons With No Bees", MoonKey.ConfigKey(level.PlanetName), DefaultChance.Value, new ConfigDescription(ChanceDescription(level.PlanetName), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); _chances[key] = value; } return value.Value; } internal static string WeightDescription(string planetName, int vanillaWeight) { return "How big a share of " + planetName + "'s daytime spawns goes to bees.\n\n" + $"This moon has bees in vanilla, at weight {vanillaWeight}, and hives here are the\n" + "game's own business — this mod exists for the moons that have none.\n\nIt is a weight, not a percentage. Every daytime creature on the moon has one and\nthe game draws between them in proportion, so the number only means anything next\nto what else lives here. It does not control how often something spawns at all;\nthe moon's own pacing does that, and the mod never touches it.\n\n -1 keep this moon exactly as the game ships it (the default)\n 0 no bees here at all, and so never a hive\n" + $" {vanillaWeight / 2,-2} about half as many bee spawns as vanilla\n" + $" {vanillaWeight * 2,-2} about twice as many\n" + "\nDoubling the weight does not double the hives, because the moon still spawns the\nsame number of daytime creatures overall — bees just win more of those draws, and\nthe manticoils win fewer."; } internal static string ChanceDescription(string planetName) { return "How often " + planetName + " is allowed a beehive, in percent of landings.\n\nThis moon has no bees in vanilla, so without this mod it never has a hive. The\nnumber is rolled once per landing, off the map seed, before anything spawns.\n\n 0 leave this moon exactly as vanilla: bee-free, nothing touched\n 25 a hive is on the cards roughly one landing in four\n 50 every other landing\n 100 every landing\n\nRead it as a ceiling, not a promise. A passing roll only puts bees into the\nmoon's daytime creature list for the day; whether they then actually turn up,\nhow many, when and where is left to the game, exactly as on a vanilla bee moon.\nOn Titan and Dine bees are the only daytime creature, so a passing roll almost\nalways does produce a hive. On Offense, Rend and Embrion they compete with the\nmanticoils, so the real rate comes out somewhat below the number set here."; } } public static class MyPluginInfo { public const string PLUGIN_GUID = "Solon.CustomBeehiveSpawns"; public const string PLUGIN_NAME = "CustomBeehiveSpawns"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace CustomBeehiveSpawns.Patches { [HarmonyPatch(typeof(RoundManager))] internal static class RoundManagerPatches { [HarmonyPostfix] [HarmonyPatch("LoadNewLevel")] private static void LoadNewLevel_Postfix(RoundManager __instance, int randomSeed, SelectableLevel newLevel) { if (((NetworkBehaviour)__instance).IsServer) { HiveSeeder.ApplyTo(newLevel); HiveSeeder.RollForRound(randomSeed, newLevel); } } } [HarmonyPatch(typeof(StartOfRound))] internal static class StartOfRoundPatches { [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyPatch("Start")] private static void Start_Postfix(StartOfRound __instance) { HiveSeeder.BindConfig(__instance.levels); } [HarmonyPostfix] [HarmonyPatch("OnDestroy")] private static void OnDestroy_Postfix() { HiveSeeder.Restore(); } } }