Decompiled source of QuietNights v0.1.0

QuietNights.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using MushroomMods;
using MushroomSync;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("QuietNights")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Turns off the extra night-time creature spawns that killing a boss unlocks.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+7757ccc54aeca6be4b442e05fcdfb2495dea3f50")]
[assembly: AssemblyProduct("Quiet Nights")]
[assembly: AssemblyTitle("QuietNights")]
[assembly: AssemblyVersion("0.1.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;
		}
	}
}
namespace MushroomMods
{
	internal static class PatchIsolation
	{
		internal static int PatchAllIsolated(Harmony harmony, Assembly assembly, ManualLogSource log)
		{
			int num = 0;
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(assembly);
			foreach (Type type in typesFromAssembly)
			{
				try
				{
					harmony.CreateClassProcessor(type).Patch();
				}
				catch (Exception ex)
				{
					num++;
					log.LogError((object)("Harmony patch class " + type.FullName + " was skipped and the rest of " + harmony.Id + " is still active. " + ex));
				}
			}
			return num;
		}
	}
}
namespace QuietNights
{
	internal static class DumpCommand
	{
		internal static void Run(ConsoleEventArgs args)
		{
			if (args.Length < 2 || !string.Equals(args[1], "dump", StringComparison.OrdinalIgnoreCase))
			{
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString("Usage: quietnights dump");
				}
				return;
			}
			if ((Object)(object)ZoneSystem.instance == (Object)null)
			{
				Terminal context2 = args.Context;
				if (context2 != null)
				{
					context2.AddString("Quiet Nights: load a world first - the spawn table belongs to it.");
				}
				return;
			}
			int covered;
			int edited;
			string text = Build(out covered, out edited);
			string text2 = Path.Combine(Paths.ConfigPath, "QuietNights.dump.txt");
			try
			{
				File.WriteAllText(text2, text);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not write " + text2 + ": " + ex.Message));
				text2 = "the BepInEx log only";
			}
			Plugin.Log.LogInfo((object)("Spawn table dump:" + Environment.NewLine + text));
			Terminal context3 = args.Context;
			if (context3 != null)
			{
				context3.AddString("Quiet Nights: " + covered + " boss-gated entries for covered creatures, " + edited + " currently suppressed. Written to " + text2);
			}
		}

		private static string Build(out int covered, out int edited)
		{
			covered = 0;
			edited = 0;
			Rules rules = Plugin.CurrentRules();
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("Quiet Nights 0.1.0 spawn table dump");
			stringBuilder.AppendLine("Enabled       = " + Plugin.Enabled.Value);
			stringBuilder.AppendLine("Creatures     = " + Plugin.Creatures.Value);
			stringBuilder.AppendLine("BossKeys      = " + Plugin.BossKeys.Value);
			string text = default(string);
			stringBuilder.AppendLine("Host values   = " + (Plugin.Sync.TryGetSyncedValue<string>(Plugin.Creatures, ref text) ? "yes, following the server" : "no, local config"));
			stringBuilder.AppendLine("World keys    = " + string.Join(", ", ZoneSystem.instance.GetGlobalKeys()));
			stringBuilder.AppendLine("Spawn lists   = " + DescribeSharing());
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Listed: every entry gated on a global key, plus every entry for a covered creature.");
			stringBuilder.AppendLine("biome= is the mask as shipped; now= appears when Quiet Nights has changed it.");
			stringBuilder.AppendLine();
			IReadOnlyList<SpawnSystemList> knownPrefabLists = SpawnSuppressor.KnownPrefabLists;
			for (int i = 0; i < knownPrefabLists.Count; i++)
			{
				if ((Object)(object)knownPrefabLists[i] != (Object)null)
				{
					Append(stringBuilder, "list" + i + ":" + ((Object)knownPrefabLists[i]).name, knownPrefabLists[i].m_spawners, rules, ref covered, ref edited);
				}
			}
			foreach (AltBiome altBiome in AltBiomeList.m_altBiomes)
			{
				if (altBiome != null)
				{
					Append(stringBuilder, "alt:" + altBiome.m_name, altBiome.m_spawn, rules, ref covered, ref edited);
				}
			}
			return stringBuilder.ToString();
		}

		private static void Append(StringBuilder text, string source, List<SpawnData> entries, Rules rules, ref int covered, ref int edited)
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			if (entries == null)
			{
				return;
			}
			for (int i = 0; i < entries.Count; i++)
			{
				SpawnData val = entries[i];
				if (val == null || (Object)(object)val.m_prefab == (Object)null)
				{
					continue;
				}
				bool flag = !string.IsNullOrEmpty(val.m_requiredGlobalKey);
				bool flag2 = rules.Find(((Object)val.m_prefab).name) != null;
				if (flag || flag2)
				{
					Biome kept;
					Verdict verdict = SpawnSuppressor.Classify(val, rules, out kept);
					if (verdict != Verdict.NotCovered)
					{
						covered++;
					}
					bool flag3 = SpawnSuppressor.IsEdited(val);
					if (flag3)
					{
						edited++;
					}
					text.Append(source).Append(" idx=").Append(i + 1)
						.Append(" name=\"")
						.Append(val.m_name)
						.Append('"')
						.Append(" prefab=")
						.Append(((Object)val.m_prefab).name)
						.Append(" biome=")
						.Append(SpawnSuppressor.OriginalBiome(val));
					if (flag3)
					{
						text.Append(" now=").Append(val.m_biome);
					}
					text.Append(" key=").Append(flag ? val.m_requiredGlobalKey : "-").Append(" night=")
						.Append(val.m_spawnAtNight ? 1 : 0)
						.Append(" day=")
						.Append(val.m_spawnAtDay ? 1 : 0)
						.Append(" enabled=")
						.Append(val.m_enabled ? 1 : 0)
						.Append(" max=")
						.Append(val.m_maxSpawned)
						.Append(" interval=")
						.Append(val.m_spawnInterval.ToString(CultureInfo.InvariantCulture))
						.Append(" chance=")
						.Append(val.m_spawnChance.ToString(CultureInfo.InvariantCulture))
						.Append(" hunt=")
						.Append(val.m_huntPlayer ? 1 : 0)
						.Append("  [")
						.Append(verdict)
						.Append(flag3 ? ", APPLIED" : "")
						.AppendLine("]");
				}
			}
		}

		private static string DescribeSharing()
		{
			IReadOnlyList<SpawnSystemList> knownPrefabLists = SpawnSuppressor.KnownPrefabLists;
			if (knownPrefabLists.Count == 0)
			{
				return "none found on the zone control prefab";
			}
			foreach (SpawnSystem item in SpawnSuppressor.LiveSpawnSystems())
			{
				if (!((Object)(object)item == (Object)null) && item.m_spawnLists.Count != 0 && !((Object)(object)item.m_spawnLists[0] == (Object)null))
				{
					return (item.m_spawnLists[0] == knownPrefabLists[0]) ? "SHARED - live zones use the prefab's own list objects" : "CLONED - each zone has its own copy (handled per zone from SpawnSystem.Awake)";
				}
			}
			return "unknown - no zone is loaded to compare against";
		}
	}
	[HarmonyPatch(typeof(ZoneSystem), "Start")]
	internal static class ZoneSystem_Start_Patch
	{
		private static void Postfix(ZoneSystem __instance)
		{
			SpawnSuppressor.OnWorldStart(__instance);
		}
	}
	[HarmonyPatch(typeof(SpawnSystem), "Awake")]
	internal static class SpawnSystem_Awake_Patch
	{
		private static void Postfix(SpawnSystem __instance)
		{
			SpawnSuppressor.OnZoneAwake(__instance);
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class Terminal_InitTerminal_Patch
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Run;
		}

		private static bool _registered;

		private static void Postfix()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			_registered = true;
			try
			{
				object obj = <>O.<0>__Run;
				if (obj == null)
				{
					ConsoleEvent val = DumpCommand.Run;
					<>O.<0>__Run = val;
					obj = (object)val;
				}
				new ConsoleCommand("quietnights", "dump - write every boss-gated spawn entry, and what Quiet Nights does to it, to the log and config folder", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not register the quietnights console command: " + ex));
			}
		}
	}
	[BepInPlugin("mushroom.quietnights", "QuietNights", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "mushroom.quietnights";

		public const string PluginName = "QuietNights";

		public const string PluginVersion = "0.1.0";

		internal static ManualLogSource Log;

		internal static ConfigSync Sync;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> Creatures;

		internal static ConfigEntry<string> BossKeys;

		private Harmony _harmony;

		private static Rules _rules;

		private static string _rulesSource;

		internal static Rules CurrentRules()
		{
			string text = Creatures.Value + "\n" + BossKeys.Value;
			if (_rules == null || text != _rulesSource)
			{
				_rules = Rules.Parse(Creatures.Value, BossKeys.Value);
				_rulesSource = text;
			}
			return _rules;
		}

		private void Awake()
		{
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Sync = ConfigSync.Create("mushroom.quietnights", "0.1.0", Log).Protecting(((BaseUnityPlugin)this).Config).OnApplied((Action)SpawnSuppressor.Apply);
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch. When off, every night spawn behaves as in the unmodded game. Set on the server; connected clients follow it.");
			Creatures = ((BaseUnityPlugin)this).Config.Bind<string>("Rules", "Creatures", "Goblin*:Plains, Seeker*:Mistlands, Charred*:AshLands", "Creatures whose boss-unlocked night spawns are removed everywhere except at home. Comma-separated Prefab:Biome pairs. A trailing * matches a family of prefabs (Goblin* covers Goblin, GoblinArcher, GoblinShaman, GoblinBrute). Join several home biomes with +, as in Plains+Meadows. Run 'quietnights dump' in the console to see the real prefab names and what each rule catches.");
			BossKeys = ((BaseUnityPlugin)this).Config.Bind<string>("Rules", "BossKeys", "defeated_*", "Global keys that count as a boss kill. Only spawn entries requiring one of these are touched. Comma-separated; a trailing * matches a prefix.");
			Sync.Register((ConfigEntryBase[])(object)new ConfigEntryBase[3]
			{
				(ConfigEntryBase)Enabled,
				(ConfigEntryBase)Creatures,
				(ConfigEntryBase)BossKeys
			});
			Enabled.SettingChanged += delegate
			{
				SpawnSuppressor.Apply();
			};
			Creatures.SettingChanged += delegate
			{
				SpawnSuppressor.Apply();
			};
			BossKeys.SettingChanged += delegate
			{
				SpawnSuppressor.Apply();
			};
			Sync.WatchForChanges(((BaseUnityPlugin)this).Config).Start();
			_harmony = new Harmony("mushroom.quietnights");
			int num = PatchIsolation.PatchAllIsolated(_harmony, typeof(Plugin).Assembly, Log);
			Log.LogInfo((object)("QuietNights 0.1.0 loaded." + ((num > 0) ? (" " + num + " patch class(es) skipped - see the errors above.") : "")));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal sealed class Rules
	{
		internal sealed class Creature
		{
			internal string Pattern;

			internal Biome Native;
		}

		internal readonly List<Creature> Creatures = new List<Creature>();

		internal readonly List<string> BossKeys = new List<string>();

		internal static Rules Parse(string creatures, string bossKeys)
		{
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			Rules rules = new Rules();
			foreach (string item in Split(creatures))
			{
				int num = item.LastIndexOf(':');
				if (num <= 0 || num == item.Length - 1)
				{
					Plugin.Log.LogWarning((object)("Rules.Creatures: '" + item + "' is not Prefab:Biome - ignored."));
					continue;
				}
				if (!TryParseBiomes(item.Substring(num + 1), out var biomes))
				{
					Plugin.Log.LogWarning((object)("Rules.Creatures: '" + item + "' names no biome the game knows - ignored. Valid: " + string.Join(", ", Enum.GetNames(typeof(Biome)))));
					continue;
				}
				rules.Creatures.Add(new Creature
				{
					Pattern = item.Substring(0, num).Trim(),
					Native = biomes
				});
			}
			rules.BossKeys.AddRange(Split(bossKeys));
			return rules;
		}

		internal Creature Find(string prefabName)
		{
			foreach (Creature creature in Creatures)
			{
				if (Matches(creature.Pattern, prefabName))
				{
					return creature;
				}
			}
			return null;
		}

		internal bool IsBossKey(string key)
		{
			foreach (string bossKey in BossKeys)
			{
				if (Matches(bossKey, key))
				{
					return true;
				}
			}
			return false;
		}

		private static bool Matches(string pattern, string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return false;
			}
			if (pattern.EndsWith("*", StringComparison.Ordinal))
			{
				return value.StartsWith(pattern.Substring(0, pattern.Length - 1), StringComparison.OrdinalIgnoreCase);
			}
			return string.Equals(pattern, value, StringComparison.OrdinalIgnoreCase);
		}

		private static bool TryParseBiomes(string text, out Biome biomes)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected I4, but got Unknown
			biomes = (Biome)0;
			string[] array = text.Split(new char[1] { '+' });
			for (int i = 0; i < array.Length; i++)
			{
				if (!Enum.TryParse<Biome>(array[i].Trim(), ignoreCase: true, out Biome result) || !Enum.IsDefined(typeof(Biome), result) || (int)result == 0)
				{
					return false;
				}
				biomes |= result;
			}
			return true;
		}

		private static IEnumerable<string> Split(string list)
		{
			string[] array = (list ?? "").Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					yield return text;
				}
			}
		}
	}
	internal enum Verdict
	{
		NotCovered,
		DayOnly,
		SpawnsByDayToo,
		NativeOnly,
		Narrowed,
		Disabled
	}
	internal static class SpawnSuppressor
	{
		private sealed class Original
		{
			internal Biome Biome;
		}

		private static readonly ConditionalWeakTable<SpawnData, Original> Originals = new ConditionalWeakTable<SpawnData, Original>();

		private static readonly List<SpawnSystemList> PrefabLists = new List<SpawnSystemList>();

		private static readonly FieldInfo InstancesField = AccessTools.Field(typeof(SpawnSystem), "m_instances");

		internal static IReadOnlyList<SpawnSystemList> KnownPrefabLists => PrefabLists;

		internal static void OnWorldStart(ZoneSystem zoneSystem)
		{
			PrefabLists.Clear();
			SpawnSystem val = (((Object)(object)zoneSystem.m_zoneCtrlPrefab != (Object)null) ? zoneSystem.m_zoneCtrlPrefab.GetComponent<SpawnSystem>() : null);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)"The zone control prefab has no SpawnSystem; nothing to suppress.");
			}
			else
			{
				foreach (SpawnSystemList spawnList in val.m_spawnLists)
				{
					if ((Object)(object)spawnList != (Object)null)
					{
						PrefabLists.Add(spawnList);
					}
				}
			}
			Apply();
		}

		internal static void OnZoneAwake(SpawnSystem spawnSystem)
		{
			if (!Plugin.Enabled.Value)
			{
				return;
			}
			Rules rules = null;
			foreach (SpawnSystemList spawnList in spawnSystem.m_spawnLists)
			{
				if (!((Object)(object)spawnList == (Object)null) && !PrefabLists.Contains(spawnList))
				{
					rules = rules ?? Plugin.CurrentRules();
					Suppress(spawnList.m_spawners, rules);
				}
			}
		}

		internal static void Apply()
		{
			int num = 0;
			int num2 = 0;
			foreach (List<SpawnData> item in ReachableLists())
			{
				num += Restore(item);
			}
			if (Plugin.Enabled.Value)
			{
				Rules rules = Plugin.CurrentRules();
				foreach (List<SpawnData> item2 in ReachableLists())
				{
					num2 += Suppress(item2, rules);
				}
			}
			if (num > 0 || num2 > 0)
			{
				Plugin.Log.LogInfo((object)("Night spawns: " + num2 + " entr" + ((num2 == 1) ? "y" : "ies") + " suppressed away from home (" + num + " restored first)."));
			}
		}

		internal static Verdict Classify(SpawnData entry, Rules rules, out Biome kept)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Expected I4, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected I4, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Invalid comparison between I4 and Unknown
			Biome val = OriginalBiome(entry);
			kept = (Biome)(int)val;
			if ((Object)(object)entry.m_prefab == (Object)null || string.IsNullOrEmpty(entry.m_requiredGlobalKey) || !rules.IsBossKey(entry.m_requiredGlobalKey))
			{
				return Verdict.NotCovered;
			}
			Rules.Creature creature = rules.Find(((Object)entry.m_prefab).name);
			if (creature == null)
			{
				return Verdict.NotCovered;
			}
			if (!entry.m_spawnAtNight)
			{
				return Verdict.DayOnly;
			}
			if (entry.m_spawnAtDay)
			{
				return Verdict.SpawnsByDayToo;
			}
			kept = (Biome)(val & creature.Native);
			if ((int)kept == (int)val)
			{
				return Verdict.NativeOnly;
			}
			if (kept)
			{
				return Verdict.Narrowed;
			}
			return Verdict.Disabled;
		}

		internal static Biome OriginalBiome(SpawnData entry)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			if (!Originals.TryGetValue(entry, out var value))
			{
				return entry.m_biome;
			}
			return value.Biome;
		}

		internal static bool IsEdited(SpawnData entry)
		{
			Original value;
			return Originals.TryGetValue(entry, out value);
		}

		private static int Suppress(List<SpawnData> entries, Rules rules)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			foreach (SpawnData entry in entries)
			{
				if (entry != null && entry.m_enabled && !IsEdited(entry))
				{
					Biome kept;
					Verdict verdict = Classify(entry, rules, out kept);
					if (verdict == Verdict.Disabled || verdict == Verdict.Narrowed)
					{
						Originals.Add(entry, new Original
						{
							Biome = entry.m_biome
						});
						entry.m_biome = kept;
						num++;
					}
				}
			}
			return num;
		}

		private static int Restore(List<SpawnData> entries)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			foreach (SpawnData entry in entries)
			{
				if (entry != null && Originals.TryGetValue(entry, out var value))
				{
					entry.m_biome = value.Biome;
					Originals.Remove(entry);
					num++;
				}
			}
			return num;
		}

		internal static List<List<SpawnData>> ReachableLists()
		{
			List<List<SpawnData>> lists = new List<List<SpawnData>>();
			foreach (SpawnSystemList prefabList in PrefabLists)
			{
				if ((Object)(object)prefabList != (Object)null)
				{
					Add(prefabList.m_spawners);
				}
			}
			foreach (AltBiome altBiome in AltBiomeList.m_altBiomes)
			{
				if (altBiome != null)
				{
					Add(altBiome.m_spawn);
				}
			}
			foreach (SpawnSystem item in LiveSpawnSystems())
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				foreach (SpawnSystemList spawnList in item.m_spawnLists)
				{
					if ((Object)(object)spawnList != (Object)null)
					{
						Add(spawnList.m_spawners);
					}
				}
			}
			return lists;
			void Add(List<SpawnData> entries)
			{
				if (entries != null && !lists.Contains(entries))
				{
					lists.Add(entries);
				}
			}
		}

		internal static List<SpawnSystem> LiveSpawnSystems()
		{
			return (InstancesField?.GetValue(null) as List<SpawnSystem>) ?? new List<SpawnSystem>();
		}
	}
}