Decompiled source of SpawnerCooldown v1.0.1

ValheimSpawnerCooldown.dll

Decompiled a day ago
using 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);
		}
	}
}