Decompiled source of SeasonalSLSBridge v1.5.1

plugins/SeasonalSLSBridge.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[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("SeasonalSLSBridge")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.5.0.0")]
[assembly: AssemblyInformationalVersion("1.5.0")]
[assembly: AssemblyProduct("SeasonalSLSBridge")]
[assembly: AssemblyTitle("SeasonalSLSBridge")]
[assembly: AssemblyVersion("1.5.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 SeasonalSLSBridge
{
	internal sealed class EcologyConfig
	{
		private sealed class PrefabProfile
		{
			internal ConfigEntry<string> Affinity;

			internal readonly Dictionary<string, ConfigEntry<string>> Seasons = new Dictionary<string, ConfigEntry<string>>();
		}

		internal readonly ConfigEntry<bool> Enabled;

		internal readonly ConfigEntry<bool> LogDecisions;

		private readonly Dictionary<string, ConfigEntry<string>> _biomes = new Dictionary<string, ConfigEntry<string>>();

		private readonly Dictionary<string, ConfigEntry<string>> _affinities = new Dictionary<string, ConfigEntry<string>>();

		private readonly Dictionary<string, ConfigEntry<string>> _limits = new Dictionary<string, ConfigEntry<string>>();

		private readonly Dictionary<string, PrefabProfile> _prefabs = new Dictionary<string, PrefabProfile>(StringComparer.OrdinalIgnoreCase);

		private readonly string _registryError;

		internal EcologyConfig(ConfigFile config)
		{
			Enabled = config.Bind<bool>("Ecology", "Enabled", true, "Enable contextual ecology for NEW creatures. False uses the original 1.2 seasonal probability profiles.");
			LogDecisions = config.Bind<bool>("Diagnostics", "LogEcologyDecisions", false, "Log birth context, resolved affinity and actual selection percentages (including secondary weighting and global gate).");
			string[] seasons = EcologyPolicy.Seasons;
			foreach (string text in seasons)
			{
				string[] biomes = EcologyPolicy.Biomes;
				foreach (string text2 in biomes)
				{
					_biomes.Add(text + "/" + text2, config.Bind<string>("Ecology.Biome." + text, text2, EcologyDefaults.BiomePatch(text, text2), "Overrides to the seasonal baseline. Modifier=number; Modifier=number. Primary entries are absolute percentages; others are conditional weights. Empty inherits. Zero disables in this layer; later explicit overrides may enable it."));
				}
				foreach (CreatureAffinity value in Enum.GetValues(typeof(CreatureAffinity)))
				{
					_affinities.Add(text + "/" + value, config.Bind<string>("Ecology.Affinity." + text, value.ToString(), EcologyDefaults.AffinityPatch(text, value), "Explicit overrides after the biome profile. One affinity per creature; no stacking. Same percentage/weight semantics as biome profiles."));
				}
			}
			seasons = EcologyPolicy.Biomes;
			foreach (string text3 in seasons)
			{
				_limits.Add(text3, config.Bind<string>("Ecology.HabitatLimits", text3, EcologyDefaults.HabitatLimits(text3), "Upper limits after biome and affinity rules, before explicit prefab overrides. Only Poison, PoisonNova, ElementalChaos. Natural Poison affinity bypasses Poison/PoisonNova limits, but never the Chaos limit. Same percentage/weight units as the seasonal pool."));
			}
			foreach (string item in from n in config.Bind<string>("Ecology", "PrefabProfiles", "", "Optional comma-separated exact prefab names. Restart after editing this list; configure the generated [Prefab.Name] sections. Unlisted creatures use built-in or Neutral affinity.").Value.Split(',')
				select n.Trim() into n
				where n.Length > 0
				select n)
			{
				if (item.Any((char c) => !char.IsLetterOrDigit(c) && c != '_' && c != '-' && c != '.') || _prefabs.ContainsKey(item))
				{
					_registryError = "Invalid or duplicate Ecology.PrefabProfiles entry: " + item;
					continue;
				}
				PrefabProfile prefabProfile = new PrefabProfile
				{
					Affinity = config.Bind<string>("Prefab." + item, "Affinity", "Default", "Default keeps the built-in affinity. Or choose exactly one: Neutral, Cold, Fire, Poison, Spirit, Arcane.")
				};
				seasons = EcologyPolicy.Seasons;
				foreach (string text4 in seasons)
				{
					prefabProfile.Seasons.Add(text4, config.Bind<string>("Prefab." + item, text4, "", "Final explicit overrides for this prefab and season, after habitat limits. Modifier=number; Modifier=number. Empty inherits. Does not override creature filters."));
				}
				_prefabs.Add(item, prefabProfile);
			}
		}

		internal bool TryResolve(string season, string biome, string prefab, IDictionary<string, float> baseline, out CreatureAffinity affinity, out Dictionary<string, float> chances, out string error)
		{
			affinity = EcologyDefaults.Affinity(prefab);
			chances = null;
			error = _registryError;
			if (error != null)
			{
				return false;
			}
			if (!EcologyPolicy.Seasons.Contains(season) || !EcologyPolicy.Biomes.Contains(biome))
			{
				error = "Unknown season or biome; no ecological roll performed.";
				return false;
			}
			string prefabPatch = "";
			if (_prefabs.TryGetValue(prefab, out var value))
			{
				if (!string.Equals(value.Affinity.Value.Trim(), "Default", StringComparison.OrdinalIgnoreCase) && !EcologyPolicy.TryAffinity(value.Affinity.Value, out affinity))
				{
					error = "Unknown affinity for " + prefab + ": " + value.Affinity.Value;
					return false;
				}
				prefabPatch = value.Seasons[season].Value;
			}
			return EcologyPolicy.TryResolve(season, biome, affinity, baseline, _biomes[season + "/" + biome].Value, _affinities[season + "/" + affinity].Value, _limits[biome].Value, prefabPatch, out chances, out error);
		}
	}
	internal static class EcologyDefaults
	{
		private static readonly Dictionary<string, CreatureAffinity> Creatures = new Dictionary<string, CreatureAffinity>(StringComparer.OrdinalIgnoreCase)
		{
			["Fenring"] = CreatureAffinity.Cold,
			["Hatchling"] = CreatureAffinity.Cold,
			["Surtling"] = CreatureAffinity.Fire,
			["Fenring_Cultist"] = CreatureAffinity.Fire,
			["Blob"] = CreatureAffinity.Poison,
			["BlobElite"] = CreatureAffinity.Poison,
			["Leech"] = CreatureAffinity.Poison,
			["Wraith"] = CreatureAffinity.Spirit,
			["Skeleton"] = CreatureAffinity.Spirit,
			["Skeleton_Poison"] = CreatureAffinity.Poison,
			["Draugr"] = CreatureAffinity.Spirit,
			["Draugr_Ranged"] = CreatureAffinity.Spirit,
			["Draugr_Elite"] = CreatureAffinity.Spirit,
			["GoblinShaman"] = CreatureAffinity.Arcane,
			["DvergerMage"] = CreatureAffinity.Arcane
		};

		private static readonly Dictionary<string, string> BiomeRules = new Dictionary<string, string>
		{
			["winter/Meadows"] = "Frost=55; ResistFrost=25",
			["winter/BlackForest"] = "Frost=65; ResistFrost=35",
			["winter/Swamp"] = "Frost=25; ResistFrost=15",
			["winter/Mountain"] = "Frost=90; ResistFrost=80",
			["winter/DeepNorth"] = "Frost=90; ResistFrost=100",
			["winter/Plains"] = "Frost=45; ResistFrost=25",
			["winter/Mistlands"] = "Frost=45; ElementalChaos=20; ResistFrost=35",
			["winter/AshLands"] = "Frost=5; Fire=50; ResistFire=50; ResistFrost=5",
			["winter/Ocean"] = "Frost=35; ResistFrost=40",
			["spring/Meadows"] = "Poison=1; ElementalChaos=25; PoisonNova=0.5",
			["spring/BlackForest"] = "Poison=5; ElementalChaos=25; PoisonNova=2",
			["spring/Swamp"] = "Poison=75; ElementalChaos=5; PoisonNova=30; ResistPoison=35",
			["spring/Mountain"] = "Poison=0.2; ElementalChaos=5; PoisonNova=0.1",
			["spring/DeepNorth"] = "Poison=0.1; ElementalChaos=8; PoisonNova=0.1",
			["spring/Plains"] = "Poison=15; ElementalChaos=10; PoisonNova=5",
			["spring/Mistlands"] = "Poison=15; ElementalChaos=60; EitrDrain=20",
			["spring/AshLands"] = "Poison=1; ElementalChaos=15; Fire=30; ResistFire=20",
			["spring/Ocean"] = "Poison=3; ElementalChaos=10",
			["summer/Meadows"] = "Fire=55; Lightning=10",
			["summer/BlackForest"] = "Fire=60; Lightning=15",
			["summer/Swamp"] = "Fire=35; Lightning=15; Poison=10; PoisonNova=10",
			["summer/Mountain"] = "Fire=5; Lightning=10; Frost=40; ResistFrost=50",
			["summer/DeepNorth"] = "Fire=2; Lightning=10; Frost=50; ResistFrost=60",
			["summer/Plains"] = "Fire=75; Lightning=15; Fast=30",
			["summer/Mistlands"] = "Fire=40; Lightning=20; ElementalChaos=25; EitrDrain=15",
			["summer/AshLands"] = "Fire=90; Lightning=5; ResistFire=60",
			["summer/Ocean"] = "Fire=25; Lightning=25",
			["autumn/Meadows"] = "Lightning=25; Poison=1; ElementalChaos=20; PoisonNova=0.5",
			["autumn/BlackForest"] = "Lightning=35; Poison=5; ElementalChaos=20",
			["autumn/Swamp"] = "Lightning=25; Poison=55; ElementalChaos=10; PoisonNova=25",
			["autumn/Mountain"] = "Lightning=45; Poison=0.2; ElementalChaos=5",
			["autumn/DeepNorth"] = "Lightning=50; Poison=0.1; ElementalChaos=8",
			["autumn/Plains"] = "Lightning=50; Poison=15; ElementalChaos=15",
			["autumn/Mistlands"] = "Lightning=35; Poison=10; ElementalChaos=45",
			["autumn/AshLands"] = "Lightning=45; Poison=1; ElementalChaos=15; Fire=40; ResistFire=30",
			["autumn/Ocean"] = "Lightning=40; Poison=3; ElementalChaos=10"
		};

		private static readonly Dictionary<string, string> AffinityRules = new Dictionary<string, string>
		{
			["winter/Cold"] = "Frost=95; ResistFrost=80; Fire=0.5; FireNova=0.5; Poison=0.1; PoisonNova=0.1",
			["spring/Cold"] = "Poison=0.1; ElementalChaos=10; PoisonNova=0.1; Frost=80; ResistFrost=80; Fire=0.5; FireNova=0.5",
			["summer/Cold"] = "Fire=2; Lightning=10; FireNova=0.5; Frost=80; ResistFrost=80; Poison=0.1; PoisonNova=0.1",
			["autumn/Cold"] = "Lightning=20; Poison=0.1; ElementalChaos=10; Frost=80; ResistFrost=80; PoisonNova=0.1; Fire=0.5; FireNova=0.5",
			["winter/Fire"] = "Frost=1; Fire=80; ResistFire=80; FireNova=20; Poison=0.2; PoisonNova=0.2",
			["spring/Fire"] = "Poison=0.5; ElementalChaos=10; PoisonNova=0.2; Fire=80; ResistFire=80; Frost=1",
			["summer/Fire"] = "Fire=90; Lightning=5; ResistFire=80; Frost=1; Poison=0.2; PoisonNova=0.2",
			["autumn/Fire"] = "Lightning=35; Poison=0.5; ElementalChaos=10; Fire=80; ResistFire=80; Frost=1; PoisonNova=0.2",
			["winter/Poison"] = "Frost=15; Poison=80; PoisonNova=40; ResistPoison=50",
			["spring/Poison"] = "Poison=85; ElementalChaos=5; PoisonNova=40; ResistPoison=50",
			["summer/Poison"] = "Fire=15; Lightning=5; Poison=80; PoisonNova=40; ResistPoison=50",
			["autumn/Poison"] = "Lightning=15; Poison=70; ElementalChaos=5; PoisonNova=40; ResistPoison=50",
			["winter/Spirit"] = "Frost=45; Poison=0.5; PoisonNova=0.2; ElementalChaos=10; SoulEater=30; ResistSpirit=60",
			["spring/Spirit"] = "Poison=1; ElementalChaos=15; PoisonNova=0.2; SoulEater=30; ResistSpirit=60",
			["summer/Spirit"] = "Fire=25; Lightning=15; Poison=0.5; PoisonNova=0.2; ElementalChaos=15; SoulEater=30; ResistSpirit=60",
			["autumn/Spirit"] = "Lightning=35; Poison=1; ElementalChaos=20; PoisonNova=0.2; SoulEater=40; ResistSpirit=60",
			["winter/Arcane"] = "Frost=60; ElementalChaos=50; EitrDrain=25",
			["spring/Arcane"] = "Poison=5; ElementalChaos=70; EitrDrain=25",
			["summer/Arcane"] = "Fire=35; Lightning=25; ElementalChaos=70; EitrDrain=25",
			["autumn/Arcane"] = "Lightning=20; Poison=5; ElementalChaos=65; EitrDrain=25"
		};

		internal static CreatureAffinity Affinity(string prefab)
		{
			if (prefab == null || !Creatures.TryGetValue(prefab, out var value))
			{
				return CreatureAffinity.Neutral;
			}
			return value;
		}

		internal static string BiomePatch(string season, string biome)
		{
			if (!BiomeRules.TryGetValue(season + "/" + biome, out var value))
			{
				return "";
			}
			return value;
		}

		internal static string AffinityPatch(string season, CreatureAffinity affinity)
		{
			if (!AffinityRules.TryGetValue(season + "/" + affinity, out var value))
			{
				return "";
			}
			return value;
		}

		internal static string HabitatLimits(string biome)
		{
			return biome switch
			{
				"Meadows" => "Poison=1; PoisonNova=0.5", 
				"BlackForest" => "Poison=5; PoisonNova=2", 
				"Swamp" => "Poison=85; PoisonNova=40; ElementalChaos=30", 
				"Mountain" => "Poison=0.2; PoisonNova=0.1; ElementalChaos=5", 
				"DeepNorth" => "Poison=0.1; PoisonNova=0.1; ElementalChaos=10", 
				"Plains" => "Poison=20; PoisonNova=8; ElementalChaos=20", 
				"Mistlands" => "Poison=25; PoisonNova=10; ElementalChaos=75", 
				"AshLands" => "Poison=1; PoisonNova=0.5; ElementalChaos=20", 
				"Ocean" => "Poison=3; PoisonNova=1; ElementalChaos=10", 
				_ => "", 
			};
		}
	}
	internal enum CreatureAffinity
	{
		Neutral,
		Cold,
		Fire,
		Poison,
		Spirit,
		Arcane
	}
	internal static class EcologyPolicy
	{
		internal static readonly string[] Seasons = new string[4] { "winter", "spring", "summer", "autumn" };

		internal static readonly string[] Biomes = new string[9] { "Meadows", "BlackForest", "Swamp", "Mountain", "Plains", "Mistlands", "AshLands", "DeepNorth", "Ocean" };

		internal static bool TryParsePatch(string text, out Dictionary<string, float> patch, out string error)
		{
			patch = new Dictionary<string, float>(StringComparer.Ordinal);
			error = null;
			if (string.IsNullOrWhiteSpace(text))
			{
				return true;
			}
			string[] array = text.Split(';');
			foreach (string text2 in array)
			{
				if (!string.IsNullOrWhiteSpace(text2))
				{
					string[] parts = text2.Split('=');
					string text3 = ((parts.Length == 2) ? SeasonalPolicy.Categories.Keys.FirstOrDefault((string n) => string.Equals(n, parts[0].Trim(), StringComparison.OrdinalIgnoreCase)) : null);
					if (text3 == null || !float.TryParse(parts[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || float.IsNaN(result) || float.IsInfinity(result) || result < 0f || result > 100f || patch.ContainsKey(text3))
					{
						error = "Invalid or duplicate modifier assignment: '" + text2 + "'. Use Modifier=0..100, separated by semicolons (decimal point).";
						patch.Clear();
						return false;
					}
					patch.Add(text3, result);
				}
			}
			return true;
		}

		internal static bool TryAffinity(string value, out CreatureAffinity affinity)
		{
			foreach (CreatureAffinity value2 in Enum.GetValues(typeof(CreatureAffinity)))
			{
				if (string.Equals(value2.ToString(), value?.Trim(), StringComparison.OrdinalIgnoreCase))
				{
					affinity = value2;
					return true;
				}
			}
			affinity = CreatureAffinity.Neutral;
			return false;
		}

		internal static bool TryResolve(string season, string biome, CreatureAffinity affinity, IDictionary<string, float> baseline, string biomePatch, string affinityPatch, string habitatLimits, string prefabPatch, out Dictionary<string, float> chances, out string error)
		{
			chances = null;
			error = null;
			if (!Seasons.Contains(season) || !Biomes.Contains(biome) || !Enum.IsDefined(typeof(CreatureAffinity), affinity))
			{
				error = "Unknown season, biome or affinity.";
				return false;
			}
			if (baseline == null || baseline.Any((KeyValuePair<string, float> p) => !SeasonalPolicy.Categories.ContainsKey(p.Key) || float.IsNaN(p.Value) || float.IsInfinity(p.Value) || p.Value < 0f || p.Value > 100f))
			{
				error = "Seasonal baseline must contain known modifiers with finite values between 0 and 100.";
				return false;
			}
			Dictionary<string, float> dictionary = new Dictionary<string, float>(baseline, StringComparer.Ordinal);
			string[] array = new string[4] { biomePatch, affinityPatch, habitatLimits, prefabPatch };
			string[] array2 = new string[4] { "Biome", "Affinity", "HabitatLimits", "Prefab" };
			for (int num = 0; num < array.Length; num++)
			{
				if (!TryParsePatch(array[num], out var patch, out error))
				{
					error = array2[num] + ": " + error;
					return false;
				}
				if (num == 2 && patch.Keys.Any((string n) => n != "Poison" && n != "PoisonNova" && n != "ElementalChaos"))
				{
					error = "HabitatLimits only accepts Poison, PoisonNova and ElementalChaos.";
					return false;
				}
				foreach (KeyValuePair<string, float> item in patch)
				{
					if (num != 2)
					{
						dictionary[item.Key] = item.Value;
					}
					else if (affinity != CreatureAffinity.Poison || (!(item.Key == "Poison") && !(item.Key == "PoisonNova")))
					{
						dictionary[item.Key] = Math.Min(dictionary.TryGetValue(item.Key, out var value) ? value : 0f, item.Value);
					}
				}
			}
			if (!SeasonalPolicy.ValidProfile(season, dictionary))
			{
				error = "Effective primary percentages exceed 100; profile rejected without normalization.";
				return false;
			}
			chances = dictionary;
			return true;
		}

		internal static Dictionary<string, double> SelectionProbabilities(string season, IDictionary<string, float> chances, float global, string fallback = null)
		{
			Dictionary<string, double> dictionary = new Dictionary<string, double>();
			if (!SeasonalPolicy.ValidProfile(season, chances))
			{
				return dictionary;
			}
			string[] primary = SeasonalPolicy.Primary(season);
			double num = (double)SeasonalPolicy.Percent(global) / 100.0;
			float value3;
			double num2 = primary.Sum((string n) => (!chances.TryGetValue(n, out value3)) ? 0.0 : ((double)SeasonalPolicy.Percent(value3)));
			double num3 = SeasonalPolicy.Categories.Keys.Where((string n) => !primary.Contains(n)).Sum((string n) => (!chances.TryGetValue(n, out value3)) ? 0.0 : ((double)SeasonalPolicy.Percent(value3)));
			foreach (string key in SeasonalPolicy.Categories.Keys)
			{
				float value;
				double num4 = (chances.TryGetValue(key, out value) ? SeasonalPolicy.Percent(value) : 0f);
				dictionary[key] = num * (primary.Contains(key) ? num4 : ((100.0 - num2) * num4 / Math.Max(100.0, num3)));
			}
			if (fallback != null && SeasonalPolicy.Categories.TryGetValue(fallback, out var value2) && value2 == 1)
			{
				dictionary[fallback] += num * (100.0 - num2) * Math.Max(0.0, 100.0 - num3) / 100.0;
			}
			return dictionary;
		}

		internal static string Describe(string season, string biome, string prefab, CreatureAffinity affinity, IDictionary<string, float> chances, float global, string fallback = null)
		{
			Dictionary<string, double> dictionary = SelectionProbabilities(season, chances, global, fallback);
			return "season=" + season + "; biome=" + biome + "; prefab=" + prefab + "; affinity=" + affinity.ToString() + "; selection % (before SLS acceptance): " + string.Join(", ", from p in dictionary.Where((KeyValuePair<string, double> p) => p.Value > 0.0).OrderBy<KeyValuePair<string, double>, string>((KeyValuePair<string, double> p) => p.Key, StringComparer.Ordinal)
				select p.Key + "=" + p.Value.ToString("0.###", CultureInfo.InvariantCulture)) + "; none=" + (100.0 - dictionary.Values.Sum()).ToString("0.###", CultureInfo.InvariantCulture);
		}
	}
	[BepInPlugin("TheLionCid.SeasonalSLSBridge", "Seasonal SLS Bridge", "1.5.0")]
	[BepInDependency("MidnightsFX.StarLevelSystem", "1.14.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "TheLionCid.SeasonalSLSBridge";

		public const string PluginName = "Seasonal SLS Bridge";

		public const string PluginVersion = "1.5.0";

		internal const string ProcessedKey = "TheLionCid.SeasonalSLSBridge.processed";

		internal const string SeasonKey = "TheLionCid.SeasonalSLSBridge.season";

		internal static Plugin Instance;

		private Harmony _harmony;

		internal bool ApiReady;

		private EcologyConfig _ecology;

		internal SeasonProviderManager Seasons;

		internal ConfigEntry<SeasonProviderChoice> SeasonProvider;

		private SeasonProviderChoice _selectedConfig;

		private readonly HashSet<Character> _pendingBirths = new HashSet<Character>();

		private readonly HashSet<string> _reportedErrors = new HashSet<string>();

		private readonly Dictionary<string, ConfigEntry<float>> _winter = new Dictionary<string, ConfigEntry<float>>();

		private readonly Dictionary<string, ConfigEntry<float>> _spring = new Dictionary<string, ConfigEntry<float>>();

		private readonly Dictionary<string, ConfigEntry<float>> _summer = new Dictionary<string, ConfigEntry<float>>();

		private readonly Dictionary<string, ConfigEntry<float>> _autumn = new Dictionary<string, ConfigEntry<float>>();

		private static readonly string[] AllModifiers = SeasonalPolicy.Categories.Keys.ToArray();

		internal ConfigEntry<float> SeasonalRollChance;

		internal ConfigEntry<float> DelaySeconds;

		internal ConfigEntry<bool> IncludeTamed;

		internal ConfigEntry<bool> LogAppliedModifiers;

		internal ConfigEntry<bool> EnsureSeasonalModifier;

		internal ConfigEntry<bool> ShowSeasonTitle;

		internal ConfigEntry<bool> ShowSeasonIcon;

		internal ConfigEntry<int> SeasonIconSize;

		internal ConfigEntry<int> SeasonIconSpacing;

		internal ConfigEntry<string> ExcludedPrefabs;

		internal ConfigEntry<string> AllowedPrefabs;

		private void Awake()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Expected O, but got Unknown
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Expected O, but got Unknown
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Expected O, but got Unknown
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Expected O, but got Unknown
			Instance = this;
			SeasonProvider = ((BaseUnityPlugin)this).Config.Bind<SeasonProviderChoice>("General", "SeasonProvider", SeasonProviderChoice.Auto, "Auto prefers Seasonality, then Seasons, then ValheimSeasons. Manual selection never falls back. Disabled stops new seasonal births; saved identities and SLS modifier textures remain. Set on the server; clients do not roll modifiers.");
			SeasonalRollChance = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SeasonalRollChance", 100f, new ConfigDescription("Modifier roll gate only. With EnsureSeasonalModifier enabled, every passed valid roll selects a modifier; SLS must still accept it. Birth-season titles are independent, including at 0.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			EnsureSeasonalModifier = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnsureSeasonalModifier", true, "Fill empty passed rolls with a Minor: winter Big, spring Fast, summer ResistFire, autumn Alert. This separate backup applies even if all profile weights are zero. False restores empty rolls. Never bypasses the global gate, filters or invalid profiles.");
			DelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "DelaySeconds", 0.75f, new ConfigDescription("Delay before the server attempts the seasonal roll.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			IncludeTamed = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "IncludeTamed", false, "Explicit opt-in for tamed creatures. Players, bosses and traders are always excluded.");
			LogAppliedModifiers = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "LogAppliedModifiers", true, "Log accepted seasonal modifiers.");
			ExcludedPrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("Filters", "ExcludedPrefabs", "Haldor,Hildir,BogWitch", "Comma-separated exact prefab names; case-insensitive. Exclusions take priority.");
			AllowedPrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("Filters", "AllowedPrefabs", "", "Optional comma-separated exact prefab allowlist. Empty allows all otherwise eligible creatures.");
			ShowSeasonTitle = ((BaseUnityPlugin)this).Config.Bind<bool>("Titles", "ShowSeasonTitle", true, "Show the persistent Vetrbarn/Várbarn/Sumarbarn/Haustbarn suffix next to the creature name.");
			ShowSeasonIcon = ((BaseUnityPlugin)this).Config.Bind<bool>("Titles", "ShowSeasonIcon", true, "Show the seasonal icon after the creature name above its health bar. Client-side UI only.");
			SeasonIconSize = ((BaseUnityPlugin)this).Config.Bind<int>("Titles", "SeasonIconSize", 24, new ConfigDescription("Icon size in UI units; follows the creature name and HUD scaling.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(12, 64), Array.Empty<object>()));
			SeasonIconSpacing = ((BaseUnityPlugin)this).Config.Bind<int>("Titles", "SeasonIconSpacing", 4, new ConfigDescription("Gap in UI units after the last visible letter of the creature name.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
			ConfigureSeasons();
			_ecology = new EcologyConfig(((BaseUnityPlugin)this).Config);
			ApiReady = SlsApi.Validate(out var reason);
			if (!ApiReady || SlsApi.VersionWarning)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)reason);
			}
			Seasons = SeasonProviderManager.Detect(() => (Object)(object)ZNet.instance != (Object)null && (Object)(object)ZoneSystem.instance != (Object)null && (Object)(object)EnvMan.instance != (Object)null, delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)message);
			}, delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)message);
			});
			SelectProvider();
			VisualSystem.Initialize(this);
			SeasonalIconSystem.Initialize(this);
			_harmony = new Harmony("TheLionCid.SeasonalSLSBridge");
			_harmony.PatchAll();
			VisualProviderHooks.Install(_harmony);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Seasonal SLS Bridge 1.5.0: " + ((ApiReady && !SlsApi.VersionWarning) ? reason : "See compatibility warning above.")));
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Features: new births require selected provider + server/ZDO owner; SLS={ApiReady}; textures={VisualSystem.Ready && VisualSystem.Enabled.Value}; titles={ShowSeasonTitle.Value}; icons={ShowSeasonIcon.Value && SeasonalIconSystem.Ready}. Functional in-game validation of this release is pending.");
		}

		internal void SelectProvider()
		{
			_selectedConfig = SeasonProvider.Value;
			Seasons?.Select(_selectedConfig);
		}

		private void Update()
		{
			if (_selectedConfig != SeasonProvider.Value)
			{
				SelectProvider();
			}
			Seasons?.Poll(Time.unscaledTime);
		}

		private void OnDestroy()
		{
			Seasons?.Dispose();
			VisualSystem.Shutdown();
			SeasonalIconSystem.Shutdown();
			SlsApi.Reset();
			((MonoBehaviour)this).StopAllCoroutines();
			_pendingBirths.Clear();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void ConfigureSeasons()
		{
			string[] seasons = EcologyPolicy.Seasons;
			foreach (string text in seasons)
			{
				string text2 = char.ToUpperInvariant(text[0]) + text.Substring(1);
				Dictionary<string, float> dictionary = SeasonalDefaults.ForSeason(text);
				Dictionary<string, ConfigEntry<float>> seasonDictionary = GetSeasonDictionary(text);
				string[] allModifiers = AllModifiers;
				foreach (string text3 in allModifiers)
				{
					seasonDictionary[text3] = ((BaseUnityPlugin)this).Config.Bind<float>(text2, text3 + "Chance", dictionary[text3], "Seasonal baseline. Primary entries are absolute percentages; others are conditional secondary weights. Ecology profiles can explicitly replace these values, including zero. Disable Ecology.Enabled to use this baseline directly.");
				}
			}
		}

		internal static string GetSeason()
		{
			Plugin instance = Instance;
			if ((Object)(object)instance == (Object)null || instance.Seasons == null)
			{
				return null;
			}
			if (instance._selectedConfig != instance.SeasonProvider.Value)
			{
				instance.SelectProvider();
			}
			return SeasonNames.Key(instance.Seasons.ReadSeason(Time.unscaledTime));
		}

		internal void BeginBirth(Character creature, ZDO zdo, Vector3 position, string prefab)
		{
			//IL_0069: 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)
			string season = GetSeason();
			if (Seasons == null || !Seasons.HasSelection)
			{
				return;
			}
			string text = zdo.GetString("TheLionCid.SeasonalSLSBridge.season", "");
			if (string.IsNullOrEmpty(text))
			{
				text = season;
			}
			if (text == null)
			{
				if (_pendingBirths.Add(creature))
				{
					((MonoBehaviour)this).StartCoroutine(WaitForBirthSeason(creature, position, prefab, Seasons.SelectionRevision));
				}
			}
			else
			{
				CompleteBirth(creature, zdo, text, position, prefab, Seasons.SelectionRevision);
			}
		}

		private IEnumerator WaitForBirthSeason(Character creature, Vector3 position, string prefab, int revision)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				for (int attempt = 0; attempt < 120; attempt++)
				{
					yield return (object)new WaitForSeconds(0.25f);
					if (!HasAuthority(creature, out var zdo) || !Eligible(creature))
					{
						yield break;
					}
					string season = GetSeason();
					if (!Seasons.HasSelection || Seasons.SelectionRevision != revision)
					{
						yield break;
					}
					if (season != null)
					{
						string text = zdo.GetString("TheLionCid.SeasonalSLSBridge.season", "");
						CompleteBirth(creature, zdo, string.IsNullOrEmpty(text) ? season : text, position, prefab, revision);
						yield break;
					}
				}
				Seasons.Warn("birth-timeout", "Seasons: initial world state unavailable for 30 seconds; pending new birth skipped without writing its ZDO.");
			}
			finally
			{
				_pendingBirths.Remove(creature);
			}
		}

		private void CompleteBirth(Character creature, ZDO zdo, string season, Vector3 position, string prefab, int revision)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(zdo.GetString("TheLionCid.SeasonalSLSBridge.season", "")))
			{
				zdo.Set("TheLionCid.SeasonalSLSBridge.season", season);
			}
			if (ApiReady && !zdo.GetBool("TheLionCid.SeasonalSLSBridge.processed", false))
			{
				((MonoBehaviour)this).StartCoroutine(RollAfterDelay(creature, season, position, prefab, revision));
			}
		}

		internal Dictionary<string, ConfigEntry<float>> GetSeasonDictionary(string season)
		{
			return season switch
			{
				"winter" => _winter, 
				"spring" => _spring, 
				"summer" => _summer, 
				"autumn" => _autumn, 
				_ => null, 
			};
		}

		internal static bool HasAuthority(Character creature, out ZDO zdo)
		{
			zdo = null;
			if ((Object)(object)creature == (Object)null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return false;
			}
			ZNetView component = ((Component)creature).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner())
			{
				return false;
			}
			zdo = component.GetZDO();
			return zdo != null;
		}

		internal bool Eligible(Character creature)
		{
			if ((Object)(object)creature == (Object)null || creature.IsDead() || creature.IsPlayer() || creature.IsBoss())
			{
				return false;
			}
			if (!IncludeTamed.Value && creature.IsTamed())
			{
				return false;
			}
			if ((Object)(object)((Component)creature).GetComponent<Trader>() != (Object)null)
			{
				return false;
			}
			return SeasonalPolicy.PrefabAllowed(Utils.GetPrefabName(((Component)creature).gameObject), AllowedPrefabs.Value, ExcludedPrefabs.Value);
		}

		internal void VisualWarning(string message)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Visuals: " + message));
		}

		internal void VisualInfo(string message)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Visuals: " + message));
		}

		private void ReportError(string error)
		{
			if (_reportedErrors.Count < 100 && _reportedErrors.Add(error))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)error);
			}
		}

		internal IEnumerator RollAfterDelay(Character creature, string birthSeason, Vector3 birthPosition, string prefab, int providerRevision)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			yield return (object)new WaitForSeconds(Math.Min(30f, SeasonalPolicy.Percent(DelaySeconds.Value)));
			if (GetSeason() == null || Seasons.SelectionRevision != providerRevision || !ApiReady || !HasAuthority(creature, out var zdo) || !Eligible(creature) || zdo.GetBool("TheLionCid.SeasonalSLSBridge.processed", false))
			{
				yield break;
			}
			Dictionary<string, float> dictionary = GetSeasonDictionary(birthSeason)?.ToDictionary((KeyValuePair<string, ConfigEntry<float>> x) => x.Key, (KeyValuePair<string, ConfigEntry<float>> x) => x.Value.Value);
			if (dictionary == null)
			{
				yield break;
			}
			Dictionary<string, float> chances = dictionary;
			string biome = "legacy";
			CreatureAffinity affinity = CreatureAffinity.Neutral;
			if (_ecology.Enabled.Value)
			{
				if (!WorldContext.TryGetBiome(birthPosition, out biome))
				{
					ReportError("Ecology: no supported biome at birth for " + prefab + "; skipped.");
					yield break;
				}
				if (!_ecology.TryResolve(birthSeason, biome, prefab, dictionary, out affinity, out chances, out var error))
				{
					ReportError("Ecology " + birthSeason + "/" + biome + "/" + prefab + ": " + error);
					yield break;
				}
			}
			if (!SeasonalPolicy.ValidProfile(birthSeason, chances))
			{
				ReportError("Invalid " + birthSeason + " primary chances: sum exceeds 100%. No roll performed.");
				yield break;
			}
			string fallback = (EnsureSeasonalModifier.Value ? SeasonalDefaults.FallbackModifier(birthSeason) : null);
			if (_ecology.LogDecisions.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Ecology] " + EcologyPolicy.Describe(birthSeason, biome, prefab, affinity, chances, SeasonalRollChance.Value, fallback)));
			}
			zdo.Set("TheLionCid.SeasonalSLSBridge.processed", true);
			string text = SeasonalPolicy.Roll(birthSeason, chances, SeasonalRollChance.Value, () => Random.value, fallback);
			if (text == null || !HasAuthority(creature, out zdo) || !Eligible(creature))
			{
				yield break;
			}
			if (SlsApi.TryAdd(creature, text, out var detail))
			{
				if (LogAppliedModifiers.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)$"Seasonal modifier: {text} ({SeasonalPolicy.Categories[text]}) -> {creature.m_name} {SeasonalPolicy.Title(birthSeason)}; biome={biome}; affinity={affinity}; {detail}");
				}
			}
			else if (_reportedErrors.Count < 100 && _reportedErrors.Add("SLS/" + text + "/" + detail))
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Seasonal modifier " + text + " rejected: " + detail));
			}
		}
	}
	[HarmonyPatch(typeof(Character), "Awake")]
	internal static class CharacterAwakePatch
	{
		private static void Postfix(Character __instance)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			Plugin instance = Plugin.Instance;
			if (!((Object)(object)instance == (Object)null) && Plugin.HasAuthority(__instance, out var zdo) && SpawnTracking.IsNew(((Component)__instance).GetComponent<ZNetView>()) && instance.Eligible(__instance))
			{
				instance.BeginBirth(__instance, zdo, ((Component)__instance).transform.position, Utils.GetPrefabName(((Component)__instance).gameObject));
			}
		}
	}
	[HarmonyPatch(typeof(ZoneSystem), "Start")]
	internal static class SeasonProviderWorldPatch
	{
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			Plugin.Instance?.SelectProvider();
		}
	}
	[HarmonyPatch(typeof(Character), "GetHoverName")]
	internal static class CharacterHoverNamePatch
	{
		[HarmonyPriority(0)]
		[HarmonyAfter(new string[] { "MidnightsFX.StarLevelSystem" })]
		private static void Postfix(Character __instance, ref string __result)
		{
			if (!((Object)(object)__instance == (Object)null) && !__instance.IsPlayer() && !string.IsNullOrEmpty(__result))
			{
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				if (!((Object)(object)component == (Object)null) && component.IsValid() && (Object)(object)Plugin.Instance != (Object)null && Plugin.Instance.ShowSeasonTitle.Value)
				{
					__result = SeasonalPolicy.AppendTitle(__result, component.GetZDO().GetString("TheLionCid.SeasonalSLSBridge.season", ""));
				}
			}
		}
	}
	internal static class SeasonalDefaults
	{
		internal static string FallbackModifier(string season)
		{
			return season switch
			{
				"winter" => "Big", 
				"spring" => "Fast", 
				"summer" => "ResistFire", 
				"autumn" => "Alert", 
				_ => null, 
			};
		}

		internal static Dictionary<string, float> ForSeason(string season)
		{
			Dictionary<string, float> dictionary = SeasonalPolicy.Categories.Keys.ToDictionary((string n) => n, (string n) => 0f);
			switch (season)
			{
			case "winter":
				dictionary["Frost"] = 95f;
				dictionary["ResistFrost"] = 25f;
				dictionary["Big"] = 25f;
				dictionary["StaminaDrain"] = 15f;
				dictionary["ResistPierce"] = 10f;
				dictionary["ResistSlash"] = 10f;
				dictionary["ResistBlunt"] = 10f;
				dictionary["EitrDrain"] = 5f;
				break;
			case "spring":
				dictionary["Poison"] = 65f;
				dictionary["ElementalChaos"] = 25f;
				dictionary["PoisonNova"] = 20f;
				dictionary["ResistPoison"] = 15f;
				dictionary["Fast"] = 20f;
				dictionary["Evolving"] = 10f;
				dictionary["Splitter"] = 5f;
				break;
			case "summer":
				dictionary["Fire"] = 70f;
				dictionary["FireNova"] = 20f;
				dictionary["ResistFire"] = 20f;
				dictionary["Lightning"] = 15f;
				dictionary["Fast"] = 15f;
				dictionary["Brutal"] = 10f;
				dictionary["Big"] = 10f;
				break;
			case "autumn":
				dictionary["Lightning"] = 40f;
				dictionary["Poison"] = 30f;
				dictionary["ElementalChaos"] = 20f;
				dictionary["SoulEater"] = 15f;
				dictionary["ResistSpirit"] = 10f;
				dictionary["Alert"] = 15f;
				dictionary["ResistPoison"] = 10f;
				dictionary["Big"] = 5f;
				break;
			}
			return dictionary;
		}
	}
	internal static class SeasonalIconSystem
	{
		private static Plugin _plugin;

		private static string _root;

		private static FieldInfo _nameField;

		private static readonly Dictionary<string, Sprite> Sprites = new Dictionary<string, Sprite>(StringComparer.Ordinal);

		private static readonly HashSet<string> Reported = new HashSet<string>();

		internal static bool Ready { get; private set; }

		internal static bool Enabled
		{
			get
			{
				if (Ready && (Object)(object)_plugin != (Object)null)
				{
					return _plugin.ShowSeasonIcon.Value;
				}
				return false;
			}
		}

		internal static bool ShowTitle
		{
			get
			{
				if (Ready && (Object)(object)_plugin != (Object)null)
				{
					return _plugin.ShowSeasonTitle.Value;
				}
				return false;
			}
		}

		internal static float Size => Mathf.Clamp(_plugin.SeasonIconSize.Value, 12, 64);

		internal static float Spacing => Mathf.Clamp(_plugin.SeasonIconSpacing.Value, 0, 16);

		internal static void Initialize(Plugin plugin)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			_plugin = plugin;
			Ready = (int)SystemInfo.graphicsDeviceType != 4;
			_root = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)plugin).Info.Location), "SeasonalIcons");
		}

		internal static void Warn(string message)
		{
			if (Reported.Count < 20 && Reported.Add(message))
			{
				_plugin?.VisualWarning("Season icons: " + message);
			}
		}

		internal static void Attach(Character creature, IDictionary huds)
		{
			if (!Ready || (Object)(object)creature == (Object)null || creature.IsPlayer() || huds == null)
			{
				return;
			}
			try
			{
				object obj = huds[creature];
				if (obj != null)
				{
					if (_nameField == null || _nameField.DeclaringType != obj.GetType())
					{
						_nameField = AccessTools.Field(obj.GetType(), "m_name");
					}
					if (_nameField == null)
					{
						throw new MissingFieldException("EnemyHud.HudData.m_name");
					}
					object? value = _nameField.GetValue(obj);
					TMP_Text val = (TMP_Text)((value is TMP_Text) ? value : null);
					if (!((Object)(object)val == (Object)null))
					{
						(((Component)val).GetComponent<SeasonalNameIcon>() ?? ((Component)val).gameObject.AddComponent<SeasonalNameIcon>()).Bind(creature, val);
					}
				}
			}
			catch (Exception ex)
			{
				Warn("Cannot attach to creature name: " + ex.GetBaseException().Message);
			}
		}

		internal static string BirthSeason(Character creature)
		{
			if ((Object)(object)creature == (Object)null || creature.IsPlayer())
			{
				return null;
			}
			ZNetView component = ((Component)creature).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				return null;
			}
			ZDO zDO = component.GetZDO();
			string text = ((zDO != null) ? zDO.GetString("TheLionCid.SeasonalSLSBridge.season", "") : null);
			if (SeasonalPolicy.Title(text) != null)
			{
				return text;
			}
			return null;
		}

		internal static Sprite LoadSprite(string season)
		{
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Expected O, but got Unknown
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			if (!Ready || SeasonalPolicy.Title(season) == null)
			{
				return null;
			}
			if (Sprites.TryGetValue(season, out var value))
			{
				return value;
			}
			Texture2D val = null;
			Sprite val2 = null;
			try
			{
				string text = Path.Combine(_root, season + ".png");
				FileInfo fileInfo = new FileInfo(text);
				if (!fileInfo.Exists)
				{
					throw new FileNotFoundException("Missing icon: " + text);
				}
				if (fileInfo.Length > 4194304)
				{
					throw new InvalidDataException("Icon exceeds 4 MiB: " + text);
				}
				byte[] array = VisualFiles.ReadEditedPng(text, null, 2048);
				val = new Texture2D(2, 2, (TextureFormat)4, false, false);
				if (!ImageConversion.LoadImage(val, array, true))
				{
					throw new InvalidDataException("Cannot decode icon: " + text);
				}
				((Object)val).name = "SSB_SeasonIcon_" + season;
				((Object)val).hideFlags = (HideFlags)52;
				((Texture)val).filterMode = (FilterMode)1;
				((Texture)val).wrapMode = (TextureWrapMode)1;
				val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 128f);
				((Object)val2).name = ((Object)val).name;
				((Object)val2).hideFlags = (HideFlags)52;
				Sprites[season] = val2;
				return val2;
			}
			catch (Exception ex)
			{
				if ((Object)(object)val2 != (Object)null)
				{
					Object.Destroy((Object)(object)val2);
				}
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
				Sprites[season] = null;
				Warn(ex.GetBaseException().Message);
				return null;
			}
		}

		internal static void Shutdown()
		{
			Ready = false;
			foreach (SeasonalNameIcon item in new List<SeasonalNameIcon>(SeasonalNameIcon.Active))
			{
				if ((Object)(object)item != (Object)null)
				{
					item.Release();
					Object.Destroy((Object)(object)item);
				}
			}
			foreach (Sprite value in Sprites.Values)
			{
				if ((Object)(object)value != (Object)null)
				{
					Object.Destroy((Object)(object)value.texture);
					Object.Destroy((Object)(object)value);
				}
			}
			Sprites.Clear();
			Reported.Clear();
			_nameField = null;
			_root = null;
			_plugin = null;
		}
	}
	[DefaultExecutionOrder(10000)]
	internal sealed class SeasonalNameIcon : MonoBehaviour
	{
		internal static readonly HashSet<SeasonalNameIcon> Active = new HashSet<SeasonalNameIcon>();

		private Character _creature;

		private TMP_Text _label;

		private Image _image;

		private RectTransform _rect;

		private string _lastText;

		private bool _display;

		private string _titleBase;

		private string _titleOutput;

		private string _titleSeason;

		private bool _showTitle;

		internal void Bind(Character creature, TMP_Text label)
		{
			_creature = creature;
			if (!((Object)(object)_label == (Object)(object)label))
			{
				if ((Object)(object)_label != (Object)null)
				{
					_label.OnPreRenderText -= Place;
				}
				_label = label;
				_label.OnPreRenderText += Place;
				_lastText = null;
				Active.Add(this);
			}
		}

		private void LateUpdate()
		{
			try
			{
				Refresh();
			}
			catch (Exception ex)
			{
				Hide();
				SeasonalIconSystem.Warn("Name layout: " + ex.GetBaseException().Message);
			}
		}

		internal void Refresh()
		{
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Expected O, but got Unknown
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_label == (Object)null || !((Behaviour)_label).isActiveAndEnabled || string.IsNullOrEmpty(_label.text))
			{
				Hide();
				return;
			}
			string text = SeasonalIconSystem.BirthSeason(_creature);
			string text2 = _label.text;
			string text3 = ((text2 == _titleOutput) ? _titleBase : text2);
			string text4 = SeasonalPolicy.Title(text);
			if (text2 != _titleOutput && text4 != null && text3.EndsWith(" " + text4, StringComparison.Ordinal))
			{
				text3 = text3.Substring(0, text3.Length - text4.Length - 1);
			}
			bool showTitle = SeasonalIconSystem.ShowTitle;
			if (text3 != _titleBase || text != _titleSeason || showTitle != _showTitle || _titleOutput == null)
			{
				_titleBase = text3;
				_titleSeason = text;
				_showTitle = showTitle;
				_titleOutput = (showTitle ? SeasonalPolicy.AppendTitle(text3, text) : text3);
			}
			if (text2 != _titleOutput)
			{
				_label.text = _titleOutput;
			}
			if (!SeasonalIconSystem.Enabled)
			{
				Hide();
				return;
			}
			Sprite val = ((text == null) ? null : SeasonalIconSystem.LoadSprite(text));
			if ((Object)(object)val == (Object)null)
			{
				Hide();
				return;
			}
			if ((Object)(object)_image == (Object)null)
			{
				GameObject val2 = new GameObject("SSB_SeasonIcon", new Type[1] { typeof(RectTransform) });
				((Object)val2).hideFlags = (HideFlags)52;
				_rect = val2.GetComponent<RectTransform>();
				((Transform)_rect).SetParent(_label.transform, false);
				((Transform)_rect).localScale = Vector3.one;
				((Transform)_rect).localRotation = Quaternion.identity;
				_rect.pivot = new Vector2(0.5f, 0.5f);
				_image = val2.AddComponent<Image>();
				((Graphic)_image).raycastTarget = false;
				_image.preserveAspect = true;
				val2.AddComponent<LayoutElement>().ignoreLayout = true;
			}
			_image.sprite = val;
			_display = true;
			if (_lastText != _label.text || _label.havePropertiesChanged)
			{
				_lastText = _label.text;
				_label.ForceMeshUpdate(false, false);
			}
			Place(_label.textInfo);
		}

		private void Place(TMP_TextInfo info)
		{
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_image == (Object)null)
			{
				return;
			}
			if (!_display || !SeasonalIconSystem.Enabled || (Object)(object)_label == (Object)null || !((Behaviour)_label).isActiveAndEnabled || info == null || info.characterInfo == null)
			{
				Hide();
				return;
			}
			int num = Math.Min(info.characterCount, info.characterInfo.Length) - 1;
			while (num >= 0 && !info.characterInfo[num].isVisible)
			{
				num--;
			}
			if (num < 0)
			{
				Hide();
				return;
			}
			TMP_CharacterInfo val = info.characterInfo[num];
			float num2 = Math.Max(val.topRight.x, val.xAdvance);
			float num3 = (val.ascender + val.descender) * 0.5f;
			if (float.IsNaN(num2) || float.IsInfinity(num2) || float.IsNaN(num3) || float.IsInfinity(num3))
			{
				Hide();
				return;
			}
			float size = SeasonalIconSystem.Size;
			RectTransform rect = _rect;
			Vector2 anchorMin = (_rect.anchorMax = _label.rectTransform.pivot);
			rect.anchorMin = anchorMin;
			_rect.sizeDelta = new Vector2(size, size);
			_rect.anchoredPosition = new Vector2(num2 + SeasonalIconSystem.Spacing + size * 0.5f, num3);
			((Graphic)_image).color = new Color(1f, 1f, 1f, ((Graphic)_label).color.a);
			((Behaviour)_image).enabled = true;
		}

		private void Hide()
		{
			_display = false;
			if ((Object)(object)_image != (Object)null)
			{
				((Behaviour)_image).enabled = false;
			}
		}

		private void OnDisable()
		{
			Hide();
			_lastText = null;
		}

		internal void Release()
		{
			if ((Object)(object)_label != (Object)null && _label.text == _titleOutput && _titleBase != null)
			{
				_label.text = _titleBase;
			}
			if ((Object)(object)_label != (Object)null)
			{
				_label.OnPreRenderText -= Place;
			}
			if ((Object)(object)_image != (Object)null)
			{
				Hide();
				Object.Destroy((Object)(object)((Component)_image).gameObject);
			}
			_image = null;
			_rect = null;
			_label = null;
			_creature = null;
			Active.Remove(this);
		}

		private void OnDestroy()
		{
			Release();
		}
	}
	[HarmonyPatch(typeof(EnemyHud), "ShowHud")]
	internal static class SeasonalIconHudPatch
	{
		[HarmonyPriority(0)]
		private static void Postfix(Character __0, IDictionary ___m_huds)
		{
			SeasonalIconSystem.Attach(__0, ___m_huds);
		}
	}
	internal static class SeasonalPolicy
	{
		internal static readonly IReadOnlyDictionary<string, int> Categories = BuildCategories();

		private static Dictionary<string, int> BuildCategories()
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			string[] array = new string[11]
			{
				"Brutal", "ElementalChaos", "Fire", "Frost", "Poison", "Lightning", "Splitter", "SoulEater", "ResistPierce", "ResistSlash",
				"ResistBlunt"
			};
			foreach (string key in array)
			{
				dictionary.Add(key, 0);
			}
			array = new string[13]
			{
				"ResistFire", "ResistFrost", "ResistPoison", "ResistSpirit", "FireNova", "PoisonNova", "Lootbags", "Alert", "Big", "Fast",
				"StaminaDrain", "Evolving", "EitrDrain"
			};
			foreach (string key2 in array)
			{
				dictionary.Add(key2, 1);
			}
			return dictionary;
		}

		internal static float Percent(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Max(0f, Math.Min(100f, value));
			}
			return 0f;
		}

		internal static string[] Primary(string season)
		{
			return season switch
			{
				"winter" => new string[1] { "Frost" }, 
				"spring" => new string[2] { "Poison", "ElementalChaos" }, 
				"summer" => new string[2] { "Fire", "Lightning" }, 
				"autumn" => new string[3] { "Lightning", "Poison", "ElementalChaos" }, 
				_ => Array.Empty<string>(), 
			};
		}

		internal static bool ValidProfile(string season, IDictionary<string, float> chances)
		{
			if (Primary(season).Length != 0)
			{
				return Primary(season).Sum((string n) => Chance(chances, n)) <= 100f;
			}
			return false;
		}

		private static float Chance(IDictionary<string, float> chances, string name)
		{
			if (!chances.TryGetValue(name, out var value))
			{
				return 0f;
			}
			return Percent(value);
		}

		private static double Unit(Func<float> random)
		{
			return Math.Max(0.0, Math.Min(0.999999999, random()));
		}

		internal static string Roll(string season, IDictionary<string, float> chances, float global, Func<float> random, string fallback = null)
		{
			if (!ValidProfile(season, chances) || Unit(random) * 100.0 >= (double)Percent(global))
			{
				return null;
			}
			string[] primary = Primary(season);
			double num = Unit(random) * 100.0;
			string[] array = primary;
			foreach (string text in array)
			{
				num -= (double)Chance(chances, text);
				if (num < 0.0)
				{
					return text;
				}
			}
			string[] array2 = Categories.Keys.Where((string n) => !primary.Contains(n)).OrderBy<string, string>((string n) => n, StringComparer.Ordinal).ToArray();
			double val = ((IEnumerable<string>)array2).Sum((Func<string, double>)((string n) => Chance(chances, n)));
			num = Unit(random) * Math.Max(100.0, val);
			array = array2;
			foreach (string text2 in array)
			{
				num -= (double)Chance(chances, text2);
				if (num < 0.0)
				{
					return text2;
				}
			}
			if (fallback == null || !Categories.TryGetValue(fallback, out var value) || value != 1)
			{
				return null;
			}
			return fallback;
		}

		internal static bool PrefabAllowed(string prefab, string allowed, string excluded)
		{
			if (string.IsNullOrWhiteSpace(prefab))
			{
				return false;
			}
			if (InList(prefab, "Haldor,Hildir,BogWitch") || InList(prefab, excluded))
			{
				return false;
			}
			if (!string.IsNullOrWhiteSpace(allowed))
			{
				return InList(prefab, allowed);
			}
			return true;
		}

		private static bool InList(string prefab, string list)
		{
			return (list ?? "").Split(',').Any((string n) => string.Equals(n.Trim(), prefab, StringComparison.OrdinalIgnoreCase));
		}

		internal static string AppendTitle(string name, string season)
		{
			string text = Title(season);
			if (string.IsNullOrEmpty(name) || text == null || name.EndsWith(" " + text, StringComparison.Ordinal))
			{
				return name;
			}
			return name + " " + text;
		}

		internal static string Title(string season)
		{
			return season switch
			{
				"winter" => "Vetrbarn", 
				"spring" => "Várbarn", 
				"summer" => "Sumarbarn", 
				"autumn" => "Haustbarn", 
				_ => null, 
			};
		}
	}
	internal enum WorldSeason
	{
		Unknown,
		Spring,
		Summer,
		Autumn,
		Winter
	}
	internal enum SeasonProviderChoice
	{
		Auto,
		Seasonality,
		Seasons,
		ValheimSeasons,
		Disabled
	}
	internal interface ISeasonProvider : IDisposable
	{
		SeasonProviderChoice Id { get; }

		string Version { get; }

		bool IsActive { get; }

		WorldSeason ReadSeason();
	}
	internal static class SeasonNames
	{
		internal static WorldSeason Parse(string name)
		{
			switch (name?.ToLowerInvariant())
			{
			case "spring":
				return WorldSeason.Spring;
			case "summer":
				return WorldSeason.Summer;
			case "fall":
			case "autumn":
				return WorldSeason.Autumn;
			case "winter":
				return WorldSeason.Winter;
			default:
				return WorldSeason.Unknown;
			}
		}

		internal static string Key(WorldSeason season)
		{
			return season switch
			{
				WorldSeason.Spring => "spring", 
				WorldSeason.Summer => "summer", 
				WorldSeason.Autumn => "autumn", 
				WorldSeason.Winter => "winter", 
				_ => null, 
			};
		}
	}
	internal static class ProviderAccess
	{
		internal const BindingFlags Static = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		internal const BindingFlags Instance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		internal static Type Type(Assembly assembly, string name)
		{
			return assembly.GetType(name, throwOnError: true);
		}

		internal static Expression Field(Type type, string name, Type expected = null, Expression target = null)
		{
			FieldInfo field = type.GetField(name, (target == null) ? (BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
			if (field == null || (expected != null && field.FieldType != expected))
			{
				throw new MissingFieldException(type.FullName, name);
			}
			return Expression.Field(target, field);
		}

		internal static Expression Call(Type type, string name, Type result, Expression target = null)
		{
			MethodInfo method = type.GetMethod(name, (target == null) ? (BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), null, System.Type.EmptyTypes, null);
			if (method == null || method.ReturnType != result || method.IsGenericMethod)
			{
				throw new MissingMethodException(type.FullName, name);
			}
			return Expression.Call(target, method);
		}

		internal static Expression Normalize(Expression value)
		{
			if (!value.Type.IsEnum || Enum.GetUnderlyingType(value.Type) != typeof(int))
			{
				throw new InvalidOperationException("Expected a season enum backed by Int32.");
			}
			string[] names = Enum.GetNames(value.Type);
			WorldSeason[] array = new WorldSeason[4]
			{
				WorldSeason.Spring,
				WorldSeason.Summer,
				WorldSeason.Autumn,
				WorldSeason.Winter
			};
			foreach (WorldSeason required in array)
			{
				if (!names.Any((string n) => SeasonNames.Parse(n) == required))
				{
					throw new InvalidOperationException("Incomplete season enum: " + value.Type.FullName);
				}
			}
			return Expression.Switch(value, Expression.Constant(WorldSeason.Unknown), names.Select((string n) => Expression.SwitchCase(Expression.Constant(SeasonNames.Parse(n)), Expression.Constant(Enum.Parse(value.Type, n), value.Type))).ToArray());
		}

		internal static Func<WorldSeason> Reader(Expression ready, Expression value)
		{
			return Expression.Lambda<Func<WorldSeason>>(Expression.Condition(ready, Normalize(value), Expression.Constant(WorldSeason.Unknown)), Array.Empty<ParameterExpression>()).Compile();
		}

		internal static Expression NotNull(Expression expression)
		{
			return Expression.NotEqual(expression, Expression.Constant(null, expression.Type));
		}
	}
	internal sealed class SeasonProviderManager : IDisposable
	{
		private readonly List<ISeasonProvider> _providers;

		private readonly Action<string> _info;

		private readonly Action<string> _warning;

		private readonly Func<bool> _worldReady;

		private readonly HashSet<string> _reported = new HashSet<string>();

		private ISeasonProvider _selected;

		private float _nextPoll;

		private float _retryAt;

		private volatile bool _dirty = true;

		private WorldSeason _last;

		private bool _disposed;

		private SeasonProviderChoice _choice;

		internal int SelectionRevision { get; private set; }

		internal bool HasSelection
		{
			get
			{
				if (!_disposed)
				{
					return _selected != null;
				}
				return false;
			}
		}

		internal SeasonProviderChoice Selected => _selected?.Id ?? SeasonProviderChoice.Disabled;

		internal SeasonProviderManager(IEnumerable<ISeasonProvider> providers, Func<bool> worldReady, Action<string> info, Action<string> warning)
		{
			_providers = providers.OrderBy((ISeasonProvider p) => p.Id).ToList();
			_worldReady = worldReady;
			_info = info;
			_warning = warning;
		}

		internal static SeasonProviderManager Detect(Func<bool> worldReady, Action<string> info, Action<string> warning)
		{
			List<ISeasonProvider> list = new List<ISeasonProvider>();
			SeasonProviderManager manager = null;
			string[] array = new string[3] { "RustyMods.Seasonality", "shudnal.Seasons", "glitnir.valheimseasons" };
			foreach (string text in array)
			{
				if (!Chainloader.PluginInfos.TryGetValue(text, out var plugin))
				{
					continue;
				}
				try
				{
					if ((Object)(object)plugin.Instance == (Object)null)
					{
						throw new InvalidOperationException("Plugin instance is not loaded.");
					}
					Assembly assembly = ((object)plugin.Instance).GetType().Assembly;
					string text2 = plugin.Metadata.Version.ToString();
					Func<bool> active = () => (Object)(object)plugin.Instance != (Object)null && ((Behaviour)plugin.Instance).isActiveAndEnabled;
					ISeasonProvider seasonProvider = ((text == "RustyMods.Seasonality") ? new SeasonalityProvider(assembly, text2, active, delegate
					{
						if (manager != null)
						{
							manager._dirty = true;
						}
					}) : ((!(text == "shudnal.Seasons")) ? ((SeasonProvider)new MorgnarSeasonsProvider(assembly, text2, active)) : ((SeasonProvider)new ShudnalSeasonsProvider(assembly, text2, active))));
					list.Add(seasonProvider);
					bool flag = (seasonProvider.Id == SeasonProviderChoice.Seasonality && (text2 == "3.8.4" || text2 == "3.8.3")) || (seasonProvider.Id == SeasonProviderChoice.Seasons && text2 == "1.10.5") || (seasonProvider.Id == SeasonProviderChoice.ValheimSeasons && text2 == "1.2.0");
					info($"Seasons: detected {seasonProvider.Id} {text2}; contract compatible; active={seasonProvider.IsActive}. " + (flag ? "DLL contract inspected; in-game verification pending." : "Version not inspected; required members match."));
					if (!flag)
					{
						warning($"Seasons: {seasonProvider.Id} {text2} is untested; continuing with its matching contract.");
					}
				}
				catch (Exception ex)
				{
					warning("Seasons: " + text + " detected but unavailable/incompatible: " + ex.GetBaseException().Message);
				}
			}
			manager = new SeasonProviderManager(list, worldReady, info, warning);
			return manager;
		}

		internal void Select(SeasonProviderChoice choice)
		{
			if (_disposed)
			{
				return;
			}
			ISeasonProvider[] array = _providers.Where((ISeasonProvider p) => p.IsActive).ToArray();
			ISeasonProvider seasonProvider = ((choice == SeasonProviderChoice.Auto) ? array.FirstOrDefault() : ((choice == SeasonProviderChoice.Disabled) ? null : array.FirstOrDefault((ISeasonProvider p) => p.Id == choice)));
			if (_choice != choice || seasonProvider != _selected)
			{
				SelectionRevision++;
			}
			if (seasonProvider != _selected)
			{
				_selected?.Dispose();
			}
			_choice = choice;
			_selected = seasonProvider;
			_last = WorldSeason.Unknown;
			_dirty = true;
			_retryAt = 0f;
			if (choice == SeasonProviderChoice.Disabled)
			{
				_info("Seasons: Disabled; no new birth assignments. Existing identities and modifier textures are retained.");
			}
			else if (_selected == null)
			{
				Warn("selection/" + choice, $"Seasons: {choice} has no active compatible provider. New birth assignments are inactive; no calendar fallback.");
			}
			else
			{
				_info($"Seasons: selected {_selected.Id} {_selected.Version}; effective season pending world state.");
			}
			if (array.Length > 1)
			{
				Warn("multiple/" + choice, "Seasons: multiple active providers (" + string.Join(", ", array.Select((ISeasonProvider p) => p.Id)) + $"); selected {Selected}. Only this calendar is read; the bridge cannot resolve conflicts between the providers themselves.");
			}
		}

		internal void Poll(float now)
		{
			if (_dirty || !(now < _nextPoll))
			{
				_dirty = false;
				_nextPoll = now + 1f;
				ReadSeason(now);
			}
		}

		internal WorldSeason ReadSeason(float now)
		{
			if (!HasSelection || !_worldReady())
			{
				return SetCurrent(WorldSeason.Unknown);
			}
			if (now < _retryAt)
			{
				return SetCurrent(WorldSeason.Unknown);
			}
			try
			{
				WorldSeason current = _selected.ReadSeason();
				return SetCurrent(current);
			}
			catch (Exception ex)
			{
				_retryAt = now + 1f;
				Warn("read/" + _selected.Id.ToString() + "/" + ex.GetBaseException().GetType().Name + "/" + ex.GetBaseException().Message, $"Seasons: {_selected.Id} state unavailable: {ex.GetBaseException().Message}. No season guessed; will retry.");
				return SetCurrent(WorldSeason.Unknown);
			}
		}

		private WorldSeason SetCurrent(WorldSeason season)
		{
			if (season != _last)
			{
				_last = season;
				_info($"Seasons: {Selected} effective season = {season}. Existing creatures retain their birth season.");
			}
			return season;
		}

		internal void Warn(string cause, string message)
		{
			if (_reported.Count < 100 && _reported.Add(cause))
			{
				_warning(message);
			}
		}

		public void Dispose()
		{
			if (_disposed)
			{
				return;
			}
			_disposed = true;
			foreach (ISeasonProvider provider in _providers)
			{
				provider.Dispose();
			}
			_providers.Clear();
			_selected = null;
		}
	}
	internal abstract class SeasonProvider : ISeasonProvider, IDisposable
	{
		private readonly Func<bool> _active;

		protected Func<WorldSeason> Read;

		public abstract SeasonProviderChoice Id { get; }

		public string Version { get; }

		public bool IsActive => _active();

		protected SeasonProvider(string version, Func<bool> active)
		{
			Version = version;
			_active = active;
		}

		public virtual WorldSeason ReadSeason()
		{
			if (!IsActive)
			{
				return WorldSeason.Unknown;
			}
			return Read();
		}

		public virtual void Dispose()
		{
		}
	}
	internal sealed class SeasonalityProvider : SeasonProvider
	{
		internal const string Guid = "RustyMods.Seasonality";

		private readonly Func<ConfigEntryBase> _entry;

		private ConfigEntryBase _subscribed;

		private readonly EventHandler _changed;

		private readonly Action<ConfigEntryBase, EventHandler> _subscribe;

		private readonly Action<ConfigEntryBase, EventHandler> _unsubscribe;

		public override SeasonProviderChoice Id => SeasonProviderChoice.Seasonality;

		internal SeasonalityProvider(Assembly assembly, string version, Func<bool> active, Action changed)
			: base(version, active)
		{
			Type type = ProviderAccess.Type(assembly, "Seasonality.Configs");
			Type type2 = ProviderAccess.Type(assembly, "Seasonality.Season");
			Expression expression = ProviderAccess.Field(type, "m_season", typeof(ConfigEntry<>).MakeGenericType(type2));
			Expression value = ProviderAccess.Call(expression.Type, "get_Value", type2, expression);
			Read = ProviderAccess.Reader(ProviderAccess.NotNull(expression), value);
			_entry = Expression.Lambda<Func<ConfigEntryBase>>(Expression.Convert(expression, typeof(ConfigEntryBase)), Array.Empty<ParameterExpression>()).Compile();
			EventInfo eventInfo = expression.Type.GetEvent("SettingChanged");
			if (eventInfo == null || eventInfo.EventHandlerType != typeof(EventHandler))
			{
				throw new MissingMemberException(expression.Type.FullName, "SettingChanged");
			}
			ParameterExpression parameterExpression = Expression.Parameter(typeof(ConfigEntryBase));
			ParameterExpression parameterExpression2 = Expression.Parameter(typeof(EventHandler));
			_subscribe = Expression.Lambda<Action<ConfigEntryBase, EventHandler>>(Expression.Call(Expression.Convert(parameterExpression, expression.Type), eventInfo.AddMethod, parameterExpression2), new ParameterExpression[2] { parameterExpression, parameterExpression2 }).Compile();
			_unsubscribe = Expression.Lambda<Action<ConfigEntryBase, EventHandler>>(Expression.Call(Expression.Convert(parameterExpression, expression.Type), eventInfo.RemoveMethod, parameterExpression2), new ParameterExpression[2] { parameterExpression, parameterExpression2 }).Compile();
			_changed = delegate
			{
				changed();
			};
		}

		public override WorldSeason ReadSeason()
		{
			ConfigEntryBase val = _entry();
			if (val != _subscribed)
			{
				Dispose();
				_subscribed = val;
				if (_subscribed != null)
				{
					_subscribe(_subscribed, _changed);
				}
			}
			return base.ReadSeason();
		}

		public override void Dispose()
		{
			if (_subscribed != null)
			{
				_unsubscribe(_subscribed, _changed);
			}
			_subscribed = null;
		}
	}
	internal sealed class ShudnalSeasonsProvider : SeasonProvider
	{
		internal const string Guid = "shudnal.Seasons";

		public override SeasonProviderChoice Id => SeasonProviderChoice.Seasons;

		internal ShudnalSeasonsProvider(Assembly assembly, string version, Func<bool> active)
			: base(version, active)
		{
			Type type = ProviderAccess.Type(assembly, "Seasons.Seasons");
			Type type2 = ProviderAccess.Type(assembly, "Seasons.SeasonState");
			Type result = ProviderAccess.Type(assembly, "Seasons.Seasons+Season");
			Expression expression = ProviderAccess.Field(type, "seasonState", type2);
			BinaryExpression ready = Expression.AndAlso(ProviderAccess.Call(type2, "get_WorldInitialized", typeof(bool)), Expression.AndAlso(ProviderAccess.NotNull(expression), Expression.AndAlso(ProviderAccess.Call(type2, "get_IsActive", typeof(bool)), Expression.GreaterThan(ProviderAccess.Call(type2, "GetCurrentDay", typeof(int), expression), Expression.Constant(0)))));
			Read = ProviderAccess.Reader(ready, ProviderAccess.Call(type2, "GetCurrentSeason", result, expression));
		}
	}
	internal sealed class MorgnarSeasonsProvider : SeasonProvider
	{
		internal const string Guid = "glitnir.valheimseasons";

		public override SeasonProviderChoice Id => SeasonProviderChoice.ValheimSeasons;

		internal MorgnarSeasonsProvider(Assembly assembly, string version, Func<bool> active)
			: base(version, active)
		{
			Type type = ProviderAccess.Type(assembly, "ValheimSeasons.SeasonClient");
			Type type2 = ProviderAccess.Type(assembly, "ValheimSeasons.SeasonState");
			Type expected = ProviderAccess.Type(assembly, "ValheimSeasons.Season");
			Expression target = ProviderAccess.Field(type, "State", type2);
			Read = ProviderAccess.Reader(ProviderAccess.Field(type, "HasState", typeof(bool)), ProviderAccess.Field(type2, "Season", expected, target));
		}
	}
	internal static class SlsApi
	{
		private static Func<bool> _available;

		private static Func<int, List<string>> _possible;

		private static Func<Character, Dictionary<string, int>> _modifiers;

		private static Func<Character, string, int, bool, bool> _add;

		internal static bool VersionWarning { get; private set; }

		internal static bool Validate(out string reason)
		{
			Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "StarLevelSystem");
			return Bind(assembly?.GetType("StarLevelSystem.API"), assembly?.GetName().Version, out reason);
		}

		internal static bool Bind(Type api, Version version, out string reason)
		{
			Reset();
			if (!SlsContract.Validate(api, typeof(Character), version, out reason))
			{
				return false;
			}
			try
			{
				_available = (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), api.GetMethod("get_IsAvailable", Type.EmptyTypes));
				_possible = (Func<int, List<string>>)Delegate.CreateDelegate(typeof(Func<int, List<string>>), api.GetMethod("GetPossibleModifiers", new Type[1] { typeof(int) }));
				_modifiers = (Func<Character, Dictionary<string, int>>)Delegate.CreateDelegate(typeof(Func<Character, Dictionary<string, int>>), api.GetMethod("GetCreaturesModifiers", new Type[1] { typeof(Character) }));
				_add = (Func<Character, string, int, bool, bool>)Delegate.CreateDelegate(typeof(Func<Character, string, int, bool, bool>), api.GetMethod("AddModifierToTargetCreature", new Type[4]
				{
					typeof(Character),
					typeof(string),
					typeof(int),
					typeof(bool)
				}));
				VersionWarning = !SlsContract.IsVerified(version);
				return true;
			}
			catch (Exception ex)
			{
				Reset();
				reason = "SLS integration disabled: cannot bind required API: " + ex.GetBaseException().Message;
				return false;
			}
		}

		internal static void Reset()
		{
			_available = null;
			_possible = null;
			_modifiers = null;
			_add = null;
			VersionWarning = false;
		}

		internal static bool TryGetModifiers(Character creature, out Dictionary<string, int> modifiers)
		{
			modifiers = null;
			if (_available == null || !_available())
			{
				return false;
			}
			modifiers = _modifiers(creature);
			return modifiers != null;
		}

		internal static bool TryAdd(Character creature, string modifier, out string detail)
		{
			detail = "No authority or unsupported modifier.";
			if (_add == null || (Object)(object)Plugin.Instance == (Object)null || !Plugin.Instance.ApiReady || !Plugin.HasAuthority(creature, out var _) || !Plugin.Instance.Eligible(creature) || !SeasonalPolicy.Categories.TryGetValue(modifier, out var value))
			{
				return false;
			}
			try
			{
				if (!_available())
				{
					detail = "API unavailable.";
					return false;
				}
				List<string> list = _possible(value);
				if (list == null || !list.Contains<string>(modifier, StringComparer.Ordinal))
				{
					detail = $"Not configured in SLS category {value}.";
					return false;
				}
				Dictionary<string, int> dictionary = _modifiers(creature);
				if (dictionary == null)
				{
					detail = "Creature modifier state unavailable.";
					return false;
				}
				if (dictionary.ContainsKey(modifier))
				{
					detail = "Modifier already present; retained without adding another.";
					return true;
				}
				bool flag = _add(creature, modifier, value, arg4: true);
				detail = (flag ? "Accepted by SLS." : "SLS returned false.");
				return flag;
			}
			catch (Exception ex)
			{
				detail = ex.GetBaseException().Message;
				return false;
			}
		}
	}
	internal static class SlsContract
	{
		internal static bool IsVerified(Version version)
		{
			if (version != null)
			{
				if (!(version == new Version(1, 14, 0, 0)) && !(version == new Version(1, 15, 0, 0)) && !(version == new Version(1, 16, 0, 0)) && !(version == new Version(1, 17, 0, 0)) && !(version == new Version(1, 18, 0, 0)) && !(version == new Version(1, 18, 1, 0)) && !(version == new Version(1, 18, 2, 0)))
				{
					return version == new Version(1, 25, 1, 0);
				}
				return true;
			}
			return false;
		}

		internal static bool Validate(Type api, Type character, Version version, out string reason)
		{
			if (version != null && version < new Version(1, 14, 0, 0))
			{
				reason = "SLS integration disabled: minimum supported version is 1.14.0; found " + version?.ToString() + ".";
				return false;
			}
			try
			{
				if (api == null || character == null)
				{
					throw new InvalidOperationException("Public SLS API or Character type is missing.");
				}
				Require(api, "get_IsAvailable", typeof(bool), Type.EmptyTypes);
				Require(api, "GetPossibleModifiers", typeof(List<string>), new Type[1] { typeof(int) });
				Require(api, "GetCreaturesModifiers", typeof(Dictionary<string, int>), new Type[1] { character });
				Require(api, "AddModifierToTargetCreature", typeof(bool), new Type[4]
				{
					character,
					typeof(string),
					typeof(int),
					typeof(bool)
				});
			}
			catch (Exception ex)
			{
				reason = "SLS API disabled: incompatible contract for " + version?.ToString() + ": " + ex.Message;
				return false;
			}
			reason = (IsVerified(version) ? ("SLS " + version?.ToString() + " API contract inspected; server + ZDO owner only. In-game validation of this release is pending.") : ("SLS " + (version?.ToString() ?? "(unknown version)") + " has not been tested with this bridge. Required API signatures match; integration enabled. Update the bridge if behavior changes."));
			return true;
		}

		private static void Require(Type api, string name, Type result, Type[] parameters)
		{
			MethodInfo method = api.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, null, parameters, null);
			if (method == null || method.IsGenericMethod || method.ReturnType != result || !(from p in method.GetParameters()
				select p.ParameterType).SequenceEqual(parameters))
			{
				throw new InvalidOperationException("Missing exact public static signature: " + name);
			}
		}
	}
	[HarmonyPatch(typeof(ZNetView), "Awake")]
	internal static class SpawnTracking
	{
		private static readonly ConditionalWeakTable<ZNetView, object> NewViews = new ConditionalWeakTable<ZNetView, object>();

		[HarmonyPriority(800)]
		private static void Prefix(out bool __state)
		{
			__state = ZNetView.m_initZDO == null;
		}

		private static void Postfix(ZNetView __instance, bool __state)
		{
			if (__state && (Object)(object)__instance != (Object)null && __instance.IsValid() && __instance.IsOwner() && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				NewViews.GetValue(__instance, (ZNetView _) => new object());
			}
		}

		internal static bool IsNew(ZNetView view)
		{
			object value;
			if ((Object)(object)view != (Object)null)
			{
				return NewViews.TryGetValue(view, out value);
			}
			return false;
		}
	}
	internal sealed class VisualSlot
	{
		internal string Prefab;

		internal string Folder;

		internal string RendererPath;

		internal string Material;

		internal string File;

		internal string VanillaHash;

		internal string Property;

		internal bool Linear;

		internal int Slot;

		internal bool IsColor => File.EndsWith(".color", StringComparison.OrdinalIgnoreCase);
	}
	internal sealed class VisualCatalog
	{
		private readonly Dictionary<string, List<VisualSlot>> _creatures = new Dictionary<string, List<VisualSlot>>(StringComparer.Ordinal);

		internal IReadOnlyList<VisualSlot> Find(string prefab, bool balrondVariants = false)
		{
			if (prefab == null)
			{
				return Array.Empty<VisualSlot>();
			}
			if (_creatures.TryGetValue(prefab, out var value))
			{
				return value;
			}
			int num = prefab.IndexOf("_Variant_", StringComparison.Ordinal);
			if (balrondVariants && num > 0 && num + 9 < prefab.Length && SafeName(prefab))
			{
				string text = prefab.Substring(0, num);
				if ((text == "Skeleton" || text.StartsWith("Skeleton_", StringComparison.Ordinal) || text == "Draugr" || text.StartsWith("Draugr_", StringComparison.Ordinal)) && _creatures.TryGetValue(text, out var value2))
				{
					return value2;
				}
			}
			return Array.Empty<VisualSlot>();
		}

		internal static bool SafeName(string name)
		{
			if (!string.IsNullOrWhiteSpace(name) && Regex.IsMatch(name, "^[a-zA-Z0-9_.-]+$") && name != ".")
			{
				return name != "..";
			}
			return false;
		}

		internal static VisualCatalog Read(string path)
		{
			VisualCatalog visualCatalog = new VisualCatalog();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			foreach (string item in File.ReadLines(path))
			{
				if (!string.IsNullOrWhiteSpace(item) && !item.StartsWith("#", StringComparison.Ordinal))
				{
					string[] p = item.Split('\t');
					if ((p.Length != 7 && p.Length != 8 && p.Length != 9) || !SafeName(p[0]) || !int.TryParse(p[2], out var result) || result < 0 || result > 63 || !SafeName(p[4]) || (!p[4].EndsWith(".png", StringComparison.OrdinalIgnoreCase) && !p[4].EndsWith(".color", StringComparison.OrdinalIgnoreCase)) || !Regex.IsMatch(p[5], "^[a-fA-F0-9]{64}$") || !Regex.IsMatch(p[6], "^_[a-zA-Z0-9_]+$") || (p.Length >= 8 && p[7] != "0" && p[7] != "1") || (p.Length == 9 && !SafeName(p[8])) || string.IsNullOrWhiteSpace(p[3]) || p[1].StartsWith("/", StringComparison.Ordinal) || p[1].Contains("\\") || p[1].Split('/').Any((string s) => s == ".." || s == "."))
					{
						throw new InvalidDataException("Invalid visual catalog row: " + item);
					}
					string text = p[0] + "/" + p[1] + "/" + result + "/" + p[6];
					if (!hashSet.Add(text))
					{
						throw new InvalidDataException("Duplicate renderer slot: " + text);
					}
					if (!visualCatalog._creatures.TryGetValue(p[0], out var value))
					{
						value = (visualCatalog._creatures[p[0]] = new List<VisualSlot>());
					}
					if (value.Any((VisualSlot e) => e.File == p[4] && !string.Equals(e.VanillaHash, p[5], StringComparison.OrdinalIgnoreCase)))
					{
						throw new InvalidDataException("Conflicting vanilla hashes for " + p[0] + "/" + p[4]);
					}
					string text2 = ((p.Length == 9) ? p[8] : p[0]);
					string text3 = text2 + "/" + p[4];
					if (dictionary.TryGetValue(text3, out var value2) && !string.Equals(value2, p[5], StringComparison.OrdinalIgnoreCase))
					{
						throw new InvalidDataException("Conflicting shared-folder hashes: " + text3);
					}
					dictionary[text3] = p[5];
					value.Add(new VisualSlot
					{
						Prefab = p[0],
						Folder = text2,
						RendererPath = p[1],
						Slot = result,
						Material = p[3],
						File = p[4],
						VanillaHash = p[5],
						Property = p[6],
						Linear = (p.Length >= 8 && p[7] == "1")
					});
				}
			}
			return visualCatalog;
		}

		internal static IEnumerable<string> Candidates(string season, IDictionary<string, int> modifiers)
		{
			if (modifiers == null)
			{
				return Array.Empty<string>();
			}
			string[] primary = SeasonalPolicy.Primary(season);
			int value;
			return from p in (from p in modifiers
					where SeasonalPolicy.Categories.TryGetValue(p.Key, out value) && value == p.Value && VariantScopes(season, p.Key).Any()
					orderby (!(p.Key != "Big") || ElementScope(p.Key) == null) ? ((p.Key == "SoulEater") ? 1 : ((!(p.Key == "Brutal")) ? ((!(p.Key == "Big")) ? 4 : 3) : 2)) : 0, (Array.IndexOf(primary, p.Key) < 0) ? int.MaxValue : Array.IndexOf(primary, p.Key)
					select p).ThenBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> p) => p.Key, StringComparer.Ordinal)
				select p.Key;
		}

		internal static IEnumerable<KeyValuePair<string, string>> CandidateVariants(string season, IDictionary<string, int> modifiers)
		{
			return Candidates(season, modifiers).SelectMany((string modifier) => from scope in VariantScopes(season, modifier)
				select new KeyValuePair<string, string>(modifier, scope));
		}

		internal static IEnumerable<string> VariantScopes(string season, string modifier)
		{
			if (modifier == "SoulEater" || modifier == "Brutal")
			{
				yield return "generic";
			}
			else
			{
				if (!SeasonalPolicy.Categories.ContainsKey(modifier ?? ""))
				{
					yield break;
				}
				bool knownSeason = SeasonalPolicy.Title(season) != null;
				if (knownSeason)
				{
					yield return season;
				}
				string text = ElementScope(modifier);
				if (text != null && (!knownSeason || text != season))
				{
					yield return text;
				}
				switch (modifier)
				{
				case "Lightning":
				case "Poison":
				case "ElementalChaos":
				case "PoisonNova":
					if (season != "autumn")
					{
						yield return "autumn";
					}
					break;
				}
			}
		}

		private static string ElementScope(string modifier)
		{
			switch (modifier)
			{
			case "Frost":
			case "Big":
				return "winter";
			case "Poison":
			case "ElementalChaos":
			case "PoisonNova":
				return "spring";
			case "Fire":
			case "Lightning":
			case "FireNova":
				return "summer";
			default:
				return null;
			}
		}
	}
	internal static class VisualFiles
	{
		internal static byte[] ReadEditedPng(string path, string vanillaHash, int maxDimension)
		{
			FileInfo fileInfo = new FileInfo(path);
			if (!fileInfo.Exists)
			{
				return null;
			}
			if (fileInfo.Length > 67108864)
			{
				throw new InvalidDataException("Visual PNG exceeds 64 MiB: " + path);
			}
			byte[] array = File.ReadAllBytes(path);
			using (SHA256 sHA = SHA256.Create())
			{
				if (string.Equals(BitConverter.ToString(sHA.ComputeHash(array)).Replace("-", ""), vanillaHash, StringComparison.OrdinalIgnoreCase))
				{
					return null;
				}
			}
			if (array.Length < 24 || array[0] != 137 || array[1] != 80 || array[2] != 78 || array[3] != 71 || array[4] != 13 || array[5] != 10 || array[6] != 26 || array[7] != 10 || Dimension(array, 16) < 1 || Dimension(array, 20) < 1 || Dimension(array, 16) > maxDimension || Dimension(array, 20) > maxDimension)
			{
				throw new InvalidDataException("Invalid visual PNG or dimensions exceed " + maxDimension + ": " + path);
			}
			return array;
		}

		internal static float[] ReadEditedColor(string path, string vanillaHash)
		{
			FileInfo fileInfo = new FileInfo(path);
			if (!fileInfo.Exists)
			{
				return null;
			}
			if (fileInfo.Length > 1024)
			{
				throw new InvalidDataException("Color file exceeds 1 KiB: " + path);
			}
			byte[] array = File.ReadAllBytes(path);
			using (SHA256 sHA = SHA256.Create())
			{
				if (string.Equals(BitConverter.ToString(sHA.ComputeHash(array)).Replace("-", ""), vanillaHash, StringComparison.OrdinalIgnoreCase))
				{
					return null;
				}
			}
			string[] array2 = Encoding.UTF8.GetString(array).Trim('\ufeff').Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
			if (array2.Length != 4)
			{
				throw new InvalidDataException("Expected four RGBA numbers: " + path);
			}
			float[] array3 = new float[4];
			for (int i = 0; i < 4; i++)
			{
				if (!float.TryParse(array2[i], NumberStyles.Float, CultureInfo.InvariantCulture, out array3[i]) || float.IsNaN(array3[i]) || float.IsInfinity(array3[i]) || array3[i] < 0f || array3[i] > (float)((i == 3) ? 1 : 64))
				{
					throw new InvalidDataException("RGBA requires finite RGB 0..64 and alpha 0..1: " + path);
				}
			}
			return array3;
		}

		private static uint Dimension(byte[] b, int i)
		{
			return (uint)((b[i] << 24) | (b[i + 1] << 16) | (b[i + 2] << 8) | b[i + 3]);
		}

		internal static long EstimatedTextureBytes(byte[] b)
		{
			return (long)Dimension(b, 16) * (long)Dimension(b, 20) * 4 * 4 / 3;
		}
	}
	internal sealed class VisualMaterial : IDisposable
	{
		private readonly Renderer _renderer;

		private readonly Material _original;

		private readonly Dictionary<string, Texture> _textures;

		private readonly Dictionary<string, Color> _colors;

		private readonly HashSet<string> _enabledKeywords = new HashSet<string>();

		private readonly int _slot;

		private readonly MaterialPropertyBlock _block = new MaterialPropertyBlock();

		private Material _private;

		internal VisualMaterial(Renderer renderer, int slot, string material, Texture texture)
			: this(renderer, slot, material, new Dictionary<string, Texture> { { "_MainTex", texture } }, new Dictionary<string, Color>())
		{
		}

		internal VisualMaterial(Renderer renderer, int slot, string material, IDictionary<string, Texture> textures, IDictionary<string, Color> colors)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Expected O, but got Unknown
			_renderer = renderer;
			_slot = slot;
			_textures = new Dictionary<string, Texture>(textures);
			_colors = new Dictionary<string, Color>(colors);
			Material[] sharedMaterials = renderer.sharedMaterials;
			if (slot < 0 || slot >= sharedMaterials.Length || (Object)(object)sharedMaterials[slot] == (Object)null || !MatchesMaterial(((Object)sharedMaterials[slot]).name, material))
			{
				throw new InvalidOperationException("Visual renderer/material no longer matches catalog: " + material);
			}
			if (HasConflictingPropertyBlock())
			{
				throw new InvalidOperationException("Visual property block overrides an edited property on " + material + "; keeping normal appearance.");
			}
			_original = sharedMaterials[slot];
			foreach (string key in _textures.Keys)
			{
				RequireProperty(key);
			}
			foreach (string key2 in _colors.Keys)
			{
				RequireProperty(key2);
			}
			_private = new Material(_original);
			try
			{
				if (((Object)_private).GetInstanceID() >= 0)
				{
					throw new InvalidOperationException("Visual material is not an isolated runtime instance.");
				}
				((Object)_private).name = "SSB_Visual_" + ((Object)_private).GetInstanceID();
				((Object)_private).hideFlags = (HideFlags)52;
				ApplyOverrides();
				sharedMaterials[slot] = _private;
				renderer.sharedMaterials = sharedMaterials;
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		internal void Refresh()
		{
			_private.CopyPropertiesFromMaterial(_original);
			ApplyOverrides();
		}

		private void ApplyOverrides()
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			foreach (KeyValuePair<string, Texture> texture in _textures)
			{
				_private.SetTexture(texture.Key, texture.Value);
			}
			foreach (KeyValuePair<string, Color> color2 in _colors)
			{
				_private.SetColor(color2.Key, color2.Value);
			}
			if (_textures.ContainsKey("_BumpMap"))
			{
				EnableKeyword("_NORMALMAP");
			}
			if (_textures.ContainsKey("_MetallicGlossMap"))
			{
				EnableKeyword("_METALLICGLOSSMAP");
			}
			if (_textures.ContainsKey("_DetailAlbedoMap") || _textures.ContainsKey("_DetailNormalMap"))
			{
				EnableKeyword("_DETAIL_MULX2");
			}
			if (!_textures.ContainsKey("_EmissionMap") && !_colors.ContainsKey("_EmissionColor"))
			{
				return;
			}
			EnableKeyword("_EMISSION");
			if (_textures.ContainsKey("_EmissionMap") && !_colors.ContainsKey("_EmissionColor") && _private.HasProperty("_EmissionColor"))
			{
				Color color = _private.GetColor("_EmissionColor");
				if (color.r == 0f && color.g == 0f && color.b == 0f)
				{
					_colors["_EmissionColor"] = Color.white;
					_private.SetColor("_EmissionColor", Color.white);
				}
			}
		}

		private void EnableKeyword(string keyword)
		{
			if (!_original.IsKeywordEnabled(keyword))
			{
				_enabledKeywords.Add(keyword);
			}
			_private.EnableKeyword(keyword);
		}

		private void RequireProperty(string property)
		{
			if (!_original.HasProperty(property))
			{
				throw new InvalidOperationException("Shader does not support " + property + " on " + ((Object)_original).name);
			}
		}

		private bool HasConflictingPropertyBlock()
		{
			if (!_renderer.HasPropertyBlock())
			{
				return false;
			}
			_block.Clear();
			_renderer.GetPropertyBlock(_block, _slot);
			if (_block.isEmpty)
			{
				_renderer.GetPropertyBlock(_block);
			}
			foreach (string key in _textures.Keys)
			{
				if (_block.HasProperty(key))
				{
					return true;
				}
			}
			foreach (string key2 in _colors.Keys)
			{
				if (_block.HasProperty(key2))
				{
					return true;
				}
			}
			if (_textures.ContainsKey("_EmissionMap"))
			{
				return _block.HasProperty("_EmissionColor");
			}
			return false;
		}

		private static bool MatchesMaterial(string actual, string expected)
		{
			if (!(actual == expected) && !(actual == expected + " (Instance)") && !(actual == expected + " (SLSColor)") && !(actual == expected + " (Instance) (SLSColor)") && !(actual == expected + " (SLSColor) (Instance)"))
			{
				return actual == expected + " (Instance) (SLSColor) (Instance)";
			}
			return true;
		}

		internal bool IsIntact()
		{
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_renderer == (Object)null || (Object)(object)_private == (Object)null)
			{
				return false;
			}
			Material[] sharedMaterials = _renderer.sharedMaterials;
			if (_slot >= sharedMaterials.Length || (Object)(object)sharedMaterials[_slot] != (Object)(object)_private || HasConflictingPropertyBlock())
			{
				return false;
			}
			foreach (KeyValuePair<string, Texture> texture in _textures)
			{
				if ((Object)(object)_private.GetTexture(texture.Key) != (Object)(object)texture.Value)
				{
					return false;
				}
			}
			foreach (KeyValuePair<string, Color> color in _colors)
			{
				if (_private.GetColor(color.Key) != color.Value)
				{
					return false;
				}
			}
			return true;
		}

		public void Dispose()
		{
			if ((Object)(object)_private == (Object)null)
			{
				return;
			}
			try
			{
				if (!((Object)(object)_renderer != (Object)null))
				{
					return;
				}
				Material[] sharedMaterials = _renderer.sharedMaterials;
				bool flag = false;
				for (int i = 0; i < sharedMaterials.Length; i++)
				{
					if ((Object)(object)sharedMaterials[i] == (Object)(object)_private)
					{
						sharedMaterials[i] = _original;
						flag = true;
					}
					else if ((Object)(object)sharedMaterials[i] != (Object)null && ((Object)sharedMaterials[i]).GetInstanceID() < 0 && (((Object)sharedMaterials[i]).name == ((Object)_private).name + " (SLSColor)" || ((Object)sharedMaterials[i]).name == ((Object)_private).name + " (Instance) (SLSColor)"))
					{
						RestoreInheritedOverrides(sharedMaterials[i]);
					}
				}
				if (flag)
				{
					_renderer.sharedMaterials = sharedMaterials;
				}
			}
			finally
			{
				Object.Destroy((Object)(object)_private);
				_private = null;
			}
		}

		private void RestoreInheritedOverrides(Material slsClone)
		{
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			foreach (KeyValuePair<string, Texture> texture in _textures)
			{
				if (slsClone.HasProperty(texture.Key) && (Object)(object)slsClone.GetTexture(texture.Key) == (Object)(object)texture.Value)
				{
					slsClone.SetTexture(texture.Key, _original.GetTexture(texture.Key));
				}
			}
			foreach (string enabledKeyword in _enabledKeywords)
			{
				if (!_original.IsKeywordEnabled(enabledKeyword))
				{
					slsClone.DisableKeyword(enabledKeyword);
				}
			}
			foreach (KeyValuePair<string, Color> color in _colors)
			{
				if (slsClone.HasProperty(color.Key) && slsClone.GetColor(color.Key) == color.Value)
				{
					slsClone.SetColor(color.Key, _original.GetColor(color.Key));
				}
			}
		}
	}
	internal static class VisualProviderHooks
	{
		internal static void Install(Harmony harmony)
		{
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Expected O, but got Unknown
			if (!VisualSystem.Ready || !Chainloader.PluginInfos.TryGetValue("shudnal.Seasons", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				return;
			}
			try
			{
				Type type = ((object)value.Instance).GetType().Assembly.GetType("Seasons.PrefabVariantController+MaterialVariants", throwOnError: true);
				MethodInfo[] array = new MethodInfo[2]
				{
					type.GetMethod("ReplaceSharedMaterial", new Type[3]
					{
						typeof(Renderer),
						typeof(int),
						typeof(int)
					}),
					type.GetMethod("RevertSharedMaterial", new Type[2]
					{
						typeof(Renderer),
						typeof(int)
					})
				};
				MethodInfo[] array2 = array;
				foreach (MethodInfo methodInfo in array2)
				{
					if (methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != typeof(void))
					{
						throw new MissingMethodException("Seasons material swap contract changed.");
					}
				}
				HarmonyMethod val = new HarmonyMethod(typeof(VisualProviderHooks).GetMethod("BeforeSwap", BindingFlags.Static | BindingFlags.NonPublic));
				array2 = array;
				foreach (MethodInfo methodInfo2 in array2)
				{
					harmony.Patch((MethodBase)methodInfo2, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
			catch (Exception ex)
			{
				VisualSystem.Warn("Seasons material coordination unavailable: " + ex.GetBaseException().Message + "; ordinary material conflict protection remains active.");
			}
		}

		private static void BeforeSwap(Renderer __0)
		{
			try
			{
				if ((Object)(object)__0 != (Object)null)
				{
					((Component)__0).GetComponentInParent<CreatureVisual>()?.BeforeProviderChange();
				}
			}
			catch (Exception ex)
			{
				VisualSystem.Warn("Seasons material release: " + ex.GetBaseException().Message);
			}
		}
	}
	internal static class VisualSystem
	{
		internal static bool Ready;

		internal static ConfigEntry<bool> Enabled;

		private static ConfigEntry<int> _maxDimension;

		private static ConfigEntry<int> _cacheMiB;

		private static ConfigEntry<FilterMode> _textureFilter;

		private static Plugin _plugin;

		private static VisualCatalog _catalog;

		private static string _root;

		private static long _cachedBytes;

		private static readonly Dictionary<string, Texture2D> Textures = new Dictionary<string, Texture2D>(StringComparer.Ordinal);

		private static readonly Dictionary<string, Color?> Colors = new Dictionary<string, Color?>(StringComparer.Ordinal);

		private static readonly HashSet<string> Reported = new HashSet<string>();

		internal static void Initialize(Plugin plugin)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Expected O, but got Unknown
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Invalid comparison between Unknown and I4
			_plugin = plugin;
			Ready = false;
			Enabled = ((BaseUnityPlugin)plugin).Config.Bind<bool>("Visuals", "Enabled", true, "Enable local custom creature textures. False restores the underlying game/provider appearance; gameplay and seasonal modifiers are unaffected.");
			_textureFilter = ((BaseUnityPlugin)plugin).Config.Bind<FilterMode>("Visuals", "TextureFilter", (FilterMode)0, "Point preserves vanilla pixel detail. Bilinear and Trilinear smooth textures for HD packs. Restart after changing.");
			_maxDimension = ((BaseUnityPlugin)plugin).Config.Bind<int>("Visuals", "MaxTextureDimension", 4096, new ConfigDescription("Largest accepted PNG side in pixels. Restart after changing textures or limits.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(128, 8192), Array.Empty<object>()));
			_cacheMiB = ((BaseUnityPlugin)plugin).Config.Bind<int>("Visuals", "TextureCacheMiB", 256, new ConfigDescription("Approximate RGBA+mipmap budget for custom textures. Excess variants keep normal appearance; restart to clear cache.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(32, 2048), Array.Empty<object>()));
			try
			{
				if ((int)SystemInfo.graphicsDeviceType != 4)
				{
					_root = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)plugin).Info.Location), "SeasonalTextures");
					_catalog = VisualCatalog.Read(Path.Combine(_root, "catalog.tsv"));
					Ready = true;
					plugin.VisualInfo("Catalog loaded; independent of season provider. Edited materials retain conflict checks.");
				}
			}
			catch (Exception ex)
			{
				Warn(ex.GetBaseException().Message + " Gameplay remains enabled.");
			}
		}

		internal static void Warn(string message)
		{
			if (Reported.Count < 100 && Reported.Add(message))
			{
				_plugin?.VisualWarning(message);
			}
		}

		internal static IReadOnlyList<VisualSlot> Slots(string prefab)
		{
			return _catalog?.Find(prefab, Chainloader.PluginInfos.ContainsKey("balrond.astafaraios.BalrondHumanoidRandomizer")) ?? Array.Empty<VisualSlot>();
		}

		internal static Texture2D EditedTexture(VisualSlot slot, string season, string modifier)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Expected O, but got Unknown
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			string text = Path.Combine(_root, slot.Folder, season, modifier, slot.File);
			if (Textures.TryGetValue(text, out var value))
			{
				return value;
			}
			Texture2D val = null;
			try
			{
				byte[] array = VisualFiles.ReadEditedPng(text, slot.VanillaHash, _maxDimension.Value);
				if (array != null)
				{
					long num = VisualFiles.EstimatedTextureBytes(array);
					if (_cachedBytes + num > (long)_cacheMiB.Value * 1024L * 1024)
					{
						throw new InvalidDataException("Visual cache budget exceeded: " + text);
					}
					val = new Texture2D(2, 2, (TextureFormat)4, true, slot.Linear);
					if (!ImageConversion.LoadImage(val, array, true))
					{
						throw new InvalidDataException("Cannot decode visual PNG: " + text);
					}
					((Texture)val).filterMode = (FilterMode)(Enum.IsDefined(typeof(FilterMode), _textureFilter.Value) ? ((int)_textureFilter.Value) : 0);
					((Texture)val).anisoLevel = 1;
					((Texture)val).mipMapBias = 0f;
					((Object)val).name = "SSB_" + slot.Prefab + "_" + season + "_" + modifier;
					((Object)val).hideFlags = (HideFlags)52;
					((Texture)val).wrapMode = (TextureWrapMode)0;
					_cachedBytes += num;
				}
			}
			catch (Exception ex)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}