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 SpawnerCooldown v1.0.1
ValheimSpawnerCooldown.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("ValheimSpawnerCooldown")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+4348e5122925036f836cb7e64d7cb689f105ff2f")] [assembly: AssemblyProduct("ValheimSpawnerCooldown")] [assembly: AssemblyTitle("ValheimSpawnerCooldown")] [assembly: AssemblyVersion("1.0.0.0")] [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; } } } internal sealed class ConfigurationManagerAttributes { public Action<ConfigEntryBase> CustomDrawer; public bool? Browsable; } namespace ValheimSpawnerCooldown { [BepInPlugin("alex83690.valheim.spawnercooldown", "Spawner Cooldown", "1.0.1")] public class Plugin : BaseUnityPlugin { public const string Guid = "alex83690.valheim.spawnercooldown"; public const string Name = "Spawner Cooldown"; public const string Version = "1.0.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> ClaimOwnership; internal static ConfigEntry<bool> DebugLog; internal static ConfigEntry<bool> NaturalFirstWave; internal static ConfigEntry<float> GlobalMultiplier; private float m_flagTimer; private void Awake() { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Apply the configured spawn intervals."); ClaimOwnership = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ClaimOwnership", true, "Take network ownership of a spawner in range when its owner does not run this mod. Spawners only run on their owner, so without this the vanilla interval applies whenever another player owns them."); DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLog", false, "Log each spawner prefab with its vanilla and applied values, every ownership claim and the end of each natural phase."); NaturalFirstWave = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "NaturalFirstWave", true, "When this client becomes owner of a spawner, keep the vanilla interval for its first maxNear spawns, or until its creature cap is reached, then apply the configured interval."); GlobalMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Interval", "GlobalMultiplier", 1f, new ConfigDescription("Multiplier applied to every spawner interval, combined with the spawner's own multiplier. 1 = vanilla. Spawners tick every 2 seconds, so intervals below 2 s behave like 2 s.", (AcceptableValueBase)(object)SpawnerSettings.MultiplierRange, Array.Empty<object>())); Enabled.SettingChanged += delegate { SpawnerControl.ApplyAll(); }; NaturalFirstWave.SettingChanged += delegate { SpawnerControl.ApplyAll(); }; GlobalMultiplier.SettingChanged += delegate { SpawnerControl.ApplyAll(); }; SpawnerSettings.Init(((BaseUnityPlugin)this).Config); new Harmony("alex83690.valheim.spawnercooldown").PatchAll(); } private void Update() { m_flagTimer -= Time.deltaTime; if (!(m_flagTimer > 0f)) { m_flagTimer = 1f; SpawnerControl.UpdateLocalCandidateFlag(); } } } [HarmonyPatch(typeof(SpawnArea), "Awake")] internal static class SpawnAreaAwakePatch { private static void Postfix(SpawnArea __instance) { SpawnerControl.Attach(__instance); } } [HarmonyPatch(typeof(SpawnArea), "SpawnOne")] internal static class SpawnAreaSpawnOnePatch { private static void Postfix(SpawnArea __instance, bool __result) { SpawnerControl component = ((Component)__instance).GetComponent<SpawnerControl>(); if ((Object)(object)component != (Object)null) { component.OnSpawnAttempt(__result); } } } internal class SpawnerControl : MonoBehaviour { private delegate void GetInstancesFn(SpawnArea area, out int near, out int total); private const float TickSeconds = 2f; private static readonly int ModOwnerKey = StringExtensionMethods.GetStableHashCode("SpawnerCooldown_ModOwner"); private static readonly int CandidateKey = StringExtensionMethods.GetStableHashCode("SpawnerCooldown_Candidate"); private static readonly List<SpawnerControl> s_instances = new List<SpawnerControl>(); private static readonly HashSet<string> s_loggedPrefabs = new HashSet<string>(); private static readonly List<Player> s_playersInRange = new List<Player>(); private static readonly GetInstancesFn s_getInstances = AccessTools.MethodDelegate<GetInstancesFn>(AccessTools.Method(typeof(SpawnArea), "GetInstances", (Type[])null, (Type[])null), (object)null, true); private SpawnArea m_area; private ZNetView m_nview; private string m_prefabName; private float m_vanillaInterval; private int m_naturalSpawns; private bool m_naturalPhaseOver; private bool InNaturalPhase { get { if (Plugin.NaturalFirstWave.Value) { return !m_naturalPhaseOver; } return false; } } internal static void Attach(SpawnArea area) { ZNetView componentInParent = ((Component)area).GetComponentInParent<ZNetView>(); if (!((Object)(object)componentInParent == (Object)null)) { SpawnerControl spawnerControl = ((Component)area).gameObject.AddComponent<SpawnerControl>(); spawnerControl.m_area = area; spawnerControl.m_nview = componentInParent; spawnerControl.m_prefabName = Utils.GetPrefabName(((Component)componentInParent).gameObject); spawnerControl.m_vanillaInterval = area.m_spawnIntervalSec; spawnerControl.Apply(); spawnerControl.LogPrefabOnce(); s_instances.Add(spawnerControl); } } internal static void ApplyAll() { foreach (SpawnerControl s_instance in s_instances) { s_instance.Apply(); } } internal static void UpdateLocalCandidateFlag() { Player localPlayer = Player.m_localPlayer; ZNetView val = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).GetComponent<ZNetView>() : null); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner()) { long num = ((Plugin.Enabled.Value && Plugin.ClaimOwnership.Value) ? 1 : 0); ZDO zDO = val.GetZDO(); if (zDO.GetLong(CandidateKey, 0L) != num) { zDO.Set(CandidateKey, num); } } } private void Start() { ((MonoBehaviour)this).InvokeRepeating("Tick", Random.Range(0f, 2f), 2f); } private void OnDestroy() { s_instances.Remove(this); } internal void OnSpawnAttempt(bool spawned) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (!InNaturalPhase) { return; } if (spawned) { m_naturalSpawns++; } if (m_naturalSpawns >= m_area.m_maxNear || IsAtCap()) { m_naturalPhaseOver = true; Apply(); if (Plugin.DebugLog.Value) { Plugin.Log.LogInfo((object)$"{m_prefabName} at {((Component)this).transform.position}: natural phase over after {m_naturalSpawns} spawn(s)."); } } } private void Apply() { m_area.m_spawnIntervalSec = (InNaturalPhase ? m_vanillaInterval : SpawnerSettings.GetInterval(m_prefabName, m_vanillaInterval)); } private bool IsAtCap() { s_getInstances(m_area, out var near, out var total); if (near < m_area.m_maxNear) { return total >= m_area.m_maxTotal; } return true; } private void ResetNaturalPhase() { if (m_naturalSpawns != 0 || m_naturalPhaseOver) { m_naturalSpawns = 0; m_naturalPhaseOver = false; Apply(); } } private void Tick() { //IL_00d4: Unknown result type (might be due to invalid IL or missing references) if (!m_nview.IsValid()) { return; } ZDO zDO = m_nview.GetZDO(); long sessionID = ZDOMan.GetSessionID(); bool value = Plugin.Enabled.Value; if (zDO.IsOwner()) { long num = (value ? sessionID : 0); if (zDO.GetLong(ModOwnerKey, 0L) != num) { zDO.Set(ModOwnerKey, num); } return; } ResetNaturalPhase(); if (!value || !Plugin.ClaimOwnership.Value || !zDO.HasOwner()) { return; } long owner = zDO.GetOwner(); if (zDO.GetLong(ModOwnerKey, 0L) != owner && IsElected(sessionID)) { m_nview.ClaimOwnership(); zDO.Set(ModOwnerKey, sessionID); if (Plugin.DebugLog.Value) { Plugin.Log.LogInfo((object)$"Claimed {m_prefabName} at {((Component)this).transform.position} from owner {owner}."); } } } private bool IsElected(long self) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) s_playersInRange.Clear(); Player.GetPlayersInRange(((Component)m_area).transform.position, m_area.m_triggerDistance, s_playersInRange); long num = long.MaxValue; foreach (Player item in s_playersInRange) { ZNetView component = ((Component)item).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { ZDO zDO = component.GetZDO(); long owner = zDO.GetOwner(); if (owner != 0L && owner < num && zDO.GetLong(CandidateKey, 0L) != 0L) { num = owner; } } } return num == self; } private void LogPrefabOnce() { if (Plugin.DebugLog.Value && s_loggedPrefabs.Add(m_prefabName)) { Plugin.Log.LogInfo((object)($"{m_prefabName}: interval {m_vanillaInterval}s -> {SpawnerSettings.GetInterval(m_prefabName, m_vanillaInterval)}s, " + $"triggerDistance {m_area.m_triggerDistance}, maxNear {m_area.m_maxNear}, maxTotal {m_area.m_maxTotal}.")); } } } internal static class SpawnerSettings { private const string Section = "Spawners"; internal static readonly AcceptableValueRange<float> MultiplierRange = new AcceptableValueRange<float>(0.1f, 20f); private static readonly Dictionary<string, ConfigEntry<float>> s_multipliers = new Dictionary<string, ConfigEntry<float>>(); private static readonly Dictionary<string, float> s_vanillaIntervals = new Dictionary<string, float>(); private static ConfigFile s_config; private static ConfigEntry<string> s_knownSpawners; internal static void Init(ConfigFile config) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown s_config = config; s_knownSpawners = config.Bind<string>("Internal", "KnownSpawners", "", new ConfigDescription("Spawner prefabs and vanilla intervals found in the last loaded world, used to list them before a world is loaded. Maintained by the mod.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Browsable = false } })); string[] array = s_knownSpawners.Value.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { int num = text.LastIndexOf(':'); if (num > 0 && float.TryParse(text.Substring(num + 1), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { Register(text.Substring(0, num), result); } } } internal static float GetInterval(string prefabName, float vanilla) { if (!Plugin.Enabled.Value) { return vanilla; } ConfigEntry<float> value; float num = (s_multipliers.TryGetValue(prefabName, out value) ? value.Value : 1f); return vanilla * Plugin.GlobalMultiplier.Value * num; } internal static void RegisterFromPrefabs(IEnumerable<GameObject> prefabs) { foreach (GameObject prefab in prefabs) { SpawnArea val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponentInChildren<SpawnArea>(true) : null); if ((Object)(object)val != (Object)null) { Register(((Object)prefab).name, val.m_spawnIntervalSec); } } string text = string.Join(";", (from pair in s_vanillaIntervals.OrderBy<KeyValuePair<string, float>, string>((KeyValuePair<string, float> pair) => pair.Key, StringComparer.Ordinal) select pair.Key + ":" + pair.Value.ToString(CultureInfo.InvariantCulture)).ToArray()); if (s_knownSpawners.Value != text) { s_knownSpawners.Value = text; } } private static void Register(string prefabName, float vanilla) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown s_vanillaIntervals[prefabName] = vanilla; if (!s_multipliers.ContainsKey(prefabName)) { ConfigEntry<float> val = s_config.Bind<float>("Spawners", prefabName, 1f, new ConfigDescription("Multiplier of the " + prefabName + " spawn interval (vanilla " + vanilla.ToString("0.#", CultureInfo.InvariantCulture) + " s), applied on top of GlobalMultiplier.", (AcceptableValueBase)(object)MultiplierRange, new object[1] { new ConfigurationManagerAttributes { CustomDrawer = DrawMultiplier } })); val.SettingChanged += delegate { SpawnerControl.ApplyAll(); }; s_multipliers[prefabName] = val; } } private static void DrawMultiplier(ConfigEntryBase entry) { float num = (float)entry.BoxedValue; float num2 = GUILayout.HorizontalSlider(num, MultiplierRange.MinValue, MultiplierRange.MaxValue, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (!Mathf.Approximately(num2, num)) { entry.BoxedValue = Mathf.Round(num2 * 10f) / 10f; } string key = entry.Definition.Key; float num3 = s_vanillaIntervals[key]; float interval = GetInterval(key, num3); GUILayout.Label($"x{num:0.0} = {interval:0.#} s (vanilla {num3:0.#} s)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class ZNetSceneAwakePatch { private static void Postfix(ZNetScene __instance) { SpawnerSettings.RegisterFromPrefabs(__instance.m_prefabs); } } }