Decompiled source of CropOptimizer v2.2.1

CropOptimizer.dll

Decompiled 6 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using CropOptimizer.Config;
using CropOptimizer.Data;
using CropOptimizer.Integration;
using CropOptimizer.Patches;
using CropOptimizer.UI;
using HarmonyLib;
using I2.Loc;
using Microsoft.CodeAnalysis;
using SunhavenMods.Shared;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Wish;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("CropOptimizer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+79057c8571b97b27a9323b455b8f88a51829b688")]
[assembly: AssemblyProduct("CropOptimizer")]
[assembly: AssemblyTitle("CropOptimizer")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 SunhavenMods.Shared
{
	public static class ConfigFileHelper
	{
		public static ConfigFile CreateNamedConfig(string pluginGuid, string configFileName, Action<string> logWarning = null)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			string text = Path.Combine(Paths.ConfigPath, configFileName);
			string text2 = Path.Combine(Paths.ConfigPath, pluginGuid + ".cfg");
			try
			{
				if (!File.Exists(text) && File.Exists(text2))
				{
					File.Copy(text2, text);
				}
			}
			catch (Exception ex)
			{
				logWarning?.Invoke("[Config] Migration to " + configFileName + " failed: " + ex.Message);
			}
			return new ConfigFile(text, true);
		}

		public static bool ReplacePluginConfig(BaseUnityPlugin plugin, ConfigFile newConfig, Action<string> logWarning = null)
		{
			if ((Object)(object)plugin == (Object)null || newConfig == null)
			{
				return false;
			}
			try
			{
				Type typeFromHandle = typeof(BaseUnityPlugin);
				PropertyInfo property = typeFromHandle.GetProperty("Config", BindingFlags.Instance | BindingFlags.Public);
				if (property != null && property.CanWrite)
				{
					property.SetValue(plugin, newConfig, null);
					return true;
				}
				FieldInfo field = typeFromHandle.GetField("<Config>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
				if (field != null)
				{
					field.SetValue(plugin, newConfig);
					return true;
				}
				FieldInfo[] fields = typeFromHandle.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (fieldInfo.FieldType == typeof(ConfigFile))
					{
						fieldInfo.SetValue(plugin, newConfig);
						return true;
					}
				}
			}
			catch (Exception ex)
			{
				logWarning?.Invoke("[Config] ReplacePluginConfig failed: " + ex.Message);
			}
			return false;
		}
	}
	public static class SharedCodeRevision
	{
		public const string Value = "2026.07.28";
	}
	public sealed class ModHealthReport
	{
		public string PluginGuid { get; set; }

		public string DisplayName { get; set; }

		public string PluginVersion { get; set; }

		public string SharedCodeRevision { get; set; }

		public string IntegrationSummary { get; set; }

		public string Mode { get; set; }

		public string CharacterName { get; set; }

		public bool? DataLoaded { get; set; }

		public string LastPersistenceOutcome { get; set; }

		public string LastError { get; set; }

		public DateTime ReportedUtc { get; set; }

		public ModHealthReport Clone()
		{
			return new ModHealthReport
			{
				PluginGuid = PluginGuid,
				DisplayName = DisplayName,
				PluginVersion = PluginVersion,
				SharedCodeRevision = SharedCodeRevision,
				IntegrationSummary = IntegrationSummary,
				Mode = Mode,
				CharacterName = CharacterName,
				DataLoaded = DataLoaded,
				LastPersistenceOutcome = LastPersistenceOutcome,
				LastError = LastError,
				ReportedUtc = ReportedUtc
			};
		}

		public void AppendDiagnosticDump(StringBuilder sb)
		{
			if (sb != null)
			{
				sb.AppendLine("guid: " + (PluginGuid ?? "—"));
				sb.AppendLine("name: " + (DisplayName ?? "—"));
				sb.AppendLine("version: " + (PluginVersion ?? "—"));
				sb.AppendLine("shared: " + (SharedCodeRevision ?? "—"));
				sb.AppendLine("integrations: " + (IntegrationSummary ?? "—"));
				sb.AppendLine("mode: " + (Mode ?? "—"));
				sb.AppendLine("character: " + (string.IsNullOrEmpty(CharacterName) ? "—" : CharacterName));
				sb.AppendLine("data loaded: " + (DataLoaded.HasValue ? DataLoaded.Value.ToString() : "—"));
				sb.AppendLine("last save: " + (LastPersistenceOutcome ?? "—"));
				sb.AppendLine("last error: " + (LastError ?? "—"));
				sb.AppendLine("reported: " + ((ReportedUtc == default(DateTime)) ? "—" : ReportedUtc.ToLocalTime().ToString("u")));
			}
		}
	}
	public static class ModDiagnostics
	{
		private static readonly Dictionary<string, ModHealthReport> ReportsByPluginGuid = new Dictionary<string, ModHealthReport>(StringComparer.OrdinalIgnoreCase);

		private static readonly object Lock = new object();

		public static void ReportStartup(ModHealthReport report)
		{
			if (report == null || string.IsNullOrWhiteSpace(report.PluginGuid))
			{
				return;
			}
			report.ReportedUtc = DateTime.UtcNow;
			if (string.IsNullOrEmpty(report.SharedCodeRevision))
			{
				report.SharedCodeRevision = "2026.07.28";
			}
			lock (Lock)
			{
				ReportsByPluginGuid[report.PluginGuid] = report.Clone();
			}
		}

		public static void ReportRuntime(string pluginGuid, Action<ModHealthReport> update)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid) || update == null)
			{
				return;
			}
			lock (Lock)
			{
				if (!ReportsByPluginGuid.TryGetValue(pluginGuid, out ModHealthReport value))
				{
					value = new ModHealthReport
					{
						PluginGuid = pluginGuid
					};
					ReportsByPluginGuid[pluginGuid] = value;
				}
				update(value);
				value.ReportedUtc = DateTime.UtcNow;
			}
		}

		public static ModHealthReport GetReport(string pluginGuid)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid))
			{
				return null;
			}
			lock (Lock)
			{
				ModHealthReport value;
				return ReportsByPluginGuid.TryGetValue(pluginGuid, out value) ? value.Clone() : null;
			}
		}

		public static IReadOnlyList<ModHealthReport> GetAllReports()
		{
			lock (Lock)
			{
				return ReportsByPluginGuid.Values.Select((ModHealthReport r) => r.Clone()).ToList();
			}
		}

		public static string FormatHealthLogLine(ModHealthReport report)
		{
			if (report == null)
			{
				return "[Health] (empty report)";
			}
			string text = (string.IsNullOrEmpty(report.DisplayName) ? report.PluginGuid : report.DisplayName);
			string text2 = (string.IsNullOrEmpty(report.PluginVersion) ? "?" : report.PluginVersion);
			string text3 = (string.IsNullOrEmpty(report.SharedCodeRevision) ? "?" : report.SharedCodeRevision);
			string text4 = (string.IsNullOrEmpty(report.IntegrationSummary) ? "—" : report.IntegrationSummary);
			string text5 = (string.IsNullOrEmpty(report.Mode) ? "—" : report.Mode);
			string text6 = (string.IsNullOrEmpty(report.CharacterName) ? "—" : report.CharacterName);
			string text7 = ((!report.DataLoaded.HasValue) ? "—" : (report.DataLoaded.Value ? "loaded" : "none"));
			string text8 = (string.IsNullOrEmpty(report.LastPersistenceOutcome) ? "—" : report.LastPersistenceOutcome);
			return "[Health] " + text + " v" + text2 + " | shared " + text3 + " | integrations: " + text4 + " | mode: " + text5 + " | character: " + text6 + " | data: " + text7 + " | save: " + text8;
		}

		public static void LogStartupHealth(ManualLogSource log, ModHealthReport report)
		{
			ReportStartup(report);
			if (log != null)
			{
				log.LogInfo((object)FormatHealthLogLine(report));
			}
		}

		public static void LogModStartup(ManualLogSource log, string pluginGuid, string displayName, string pluginVersion, string integrationSummary, string mode = "startup", bool? dataLoaded = false, string lastPersistenceOutcome = "—")
		{
			LogStartupHealth(log, new ModHealthReport
			{
				PluginGuid = pluginGuid,
				DisplayName = displayName,
				PluginVersion = pluginVersion,
				SharedCodeRevision = "2026.07.28",
				IntegrationSummary = integrationSummary,
				Mode = mode,
				DataLoaded = dataLoaded,
				LastPersistenceOutcome = lastPersistenceOutcome
			});
		}
	}
	public static class SuitePluginGuids
	{
		public const string DevTools = "com.azraelgodking.havendevtools";

		public const string SenpaisChest = "com.azraelgodking.senpaischest";

		public const string BirthdayReminder = "com.azraelgodking.squirrelsbirthdayreminder";

		public const string HavensBirthright = "com.azraelgodking.havensbirthright";

		public const string Smut = "com.azraelgodking.sunhavenmuseumutilitytracker";

		public const string SunhavenTodo = "com.azraelgodking.sunhaventodo";

		public const string TheVault = "com.azraelgodking.thevault";

		public const string HavensAlmanac = "com.azraelgodking.havensalmanac";

		public const string FasterRaces = "com.azraelgodking.fasterraces";

		public const string TrinketFortune = "com.azraelgodking.trinketfortune";

		public const string CropOptimizer = "com.azraelgodking.cropoptimizer";

		public const string HavensRespec = "com.azraelgodking.havensrespec";

		public const string GiftingAssistant = "com.azraelgodking.giftingassistant";
	}
	public static class ModHealthIntegrationSummary
	{
		public static string Build(params (string label, string pluginGuid)[] integrations)
		{
			if (integrations == null || integrations.Length == 0)
			{
				return "standalone";
			}
			Dictionary<string, PluginInfo> pluginInfos = Chainloader.PluginInfos;
			if (pluginInfos == null)
			{
				return "standalone";
			}
			List<string> list = new List<string>(integrations.Length);
			for (int i = 0; i < integrations.Length; i++)
			{
				var (text, text2) = integrations[i];
				if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2) && pluginInfos.ContainsKey(text2))
				{
					list.Add(text);
				}
			}
			if (list.Count != 0)
			{
				return string.Join(", ", list);
			}
			return "standalone";
		}
	}
	public abstract class PersistentRunnerBase : MonoBehaviour
	{
		private bool _wasInGame;

		private float _lastHeartbeat;

		private string _lastSceneName = "";

		private float _lastSceneCheckTime;

		private const float SceneCheckInterval = 0.5f;

		protected virtual float HeartbeatInterval => 0f;

		protected virtual string RunnerName => ((object)this).GetType().Name;

		protected virtual void OnUpdate()
		{
		}

		protected virtual void OnMenuTransition()
		{
		}

		protected virtual void OnGameTransition()
		{
		}

		protected virtual void Log(string message)
		{
		}

		protected virtual void LogWarning(string message)
		{
		}

		public static T CreateRunner<T>() where T : PersistentRunnerBase
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			GameObject val = new GameObject("[" + typeof(T).Name + "]")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(val);
			return val.AddComponent<T>();
		}

		protected virtual void Awake()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			_lastSceneName = ((Scene)(ref activeScene)).name;
			_wasInGame = !SceneHelpers.IsMenuScene(_lastSceneName);
			Log("[" + RunnerName + "] Initialized in scene: " + _lastSceneName);
		}

		protected virtual void Update()
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if (HeartbeatInterval > 0f)
			{
				_lastHeartbeat += Time.deltaTime;
				if (_lastHeartbeat >= HeartbeatInterval)
				{
					_lastHeartbeat = 0f;
					Log("[" + RunnerName + "] Heartbeat - still alive");
				}
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime - _lastSceneCheckTime >= 0.5f)
			{
				_lastSceneCheckTime = unscaledTime;
				Scene activeScene = SceneManager.GetActiveScene();
				string name = ((Scene)(ref activeScene)).name;
				if (name != _lastSceneName)
				{
					_lastSceneName = name;
					HandleSceneChange(name);
				}
			}
			try
			{
				OnUpdate();
			}
			catch (Exception ex)
			{
				LogWarning("[" + RunnerName + "] Error in OnUpdate: " + ex.Message);
			}
		}

		private void HandleSceneChange(string sceneName)
		{
			bool flag = SceneHelpers.IsMenuScene(sceneName);
			if (_wasInGame && flag)
			{
				Log("[" + RunnerName + "] Menu transition detected");
				try
				{
					OnMenuTransition();
				}
				catch (Exception ex)
				{
					LogWarning("[" + RunnerName + "] Error in OnMenuTransition: " + ex.Message);
				}
			}
			else if (!_wasInGame && !flag)
			{
				Log("[" + RunnerName + "] Game transition detected");
				try
				{
					OnGameTransition();
				}
				catch (Exception ex2)
				{
					LogWarning("[" + RunnerName + "] Error in OnGameTransition: " + ex2.Message);
				}
			}
			_wasInGame = !flag;
		}

		protected virtual void OnDestroy()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			string text = (((Scene)(ref activeScene)).name ?? string.Empty).ToLowerInvariant();
			if (!Application.isPlaying || text.Contains("menu") || text.Contains("title"))
			{
				Log("[" + RunnerName + "] OnDestroy during app quit/menu unload (expected).");
			}
			else
			{
				LogWarning("[" + RunnerName + "] OnDestroy outside quit/menu (unexpected).");
			}
		}
	}
	public static class VersionChecker
	{
		public class VersionCheckResult
		{
			public bool Success { get; set; }

			public bool UpdateAvailable { get; set; }

			public string CurrentVersion { get; set; }

			public string LatestVersion { get; set; }

			public string ModName { get; set; }

			public string NexusUrl { get; set; }

			public string Changelog { get; set; }

			public string ErrorMessage { get; set; }
		}

		public class ModHealthSnapshot
		{
			public string PluginGuid { get; set; }

			public DateTime LastCheckUtc { get; set; }

			public int ExceptionCount { get; set; }

			public string LastError { get; set; }
		}

		private class VersionCheckRunner : MonoBehaviour
		{
			private ManualLogSource _pluginLog;

			public void StartCheck(string pluginGuid, string currentVersion, ManualLogSource pluginLog, Action<VersionCheckResult> onComplete)
			{
				_pluginLog = pluginLog;
				((MonoBehaviour)this).StartCoroutine(CheckVersionCoroutine(pluginGuid, currentVersion, onComplete));
			}

			private void LogInfo(string message)
			{
				ManualLogSource pluginLog = _pluginLog;
				if (pluginLog != null)
				{
					pluginLog.LogInfo((object)("[VersionChecker] " + message));
				}
			}

			private void LogWarningMsg(string message)
			{
				ManualLogSource pluginLog = _pluginLog;
				if (pluginLog != null)
				{
					pluginLog.LogWarning((object)("[VersionChecker] " + message));
				}
			}

			private void LogErrorMsg(string message)
			{
				ManualLogSource pluginLog = _pluginLog;
				if (pluginLog != null)
				{
					pluginLog.LogError((object)("[VersionChecker] " + message));
				}
			}

			private IEnumerator CheckVersionCoroutine(string pluginGuid, string currentVersion, Action<VersionCheckResult> onComplete)
			{
				VersionCheckResult result = new VersionCheckResult
				{
					CurrentVersion = currentVersion
				};
				UnityWebRequest www = UnityWebRequest.Get("https://azraelgodking.github.io/SunhavenMod/versions.json");
				try
				{
					www.timeout = 10;
					yield return www.SendWebRequest();
					if ((int)www.result == 2 || (int)www.result == 3)
					{
						result.Success = false;
						result.ErrorMessage = "Network error: " + www.error;
						RecordHealthError(pluginGuid, result.ErrorMessage);
						LogWarningMsg(result.ErrorMessage);
						onComplete?.Invoke(result);
						Object.Destroy((Object)(object)((Component)this).gameObject);
						yield break;
					}
					try
					{
						string text = www.downloadHandler.text;
						Match match = GetModPattern(pluginGuid).Match(text);
						if (!match.Success)
						{
							result.Success = false;
							result.ErrorMessage = "Mod '" + pluginGuid + "' not found in versions.json";
							RecordHealthError(pluginGuid, result.ErrorMessage);
							LogWarningMsg(result.ErrorMessage);
							onComplete?.Invoke(result);
							Object.Destroy((Object)(object)((Component)this).gameObject);
							yield break;
						}
						string value = match.Groups[1].Value;
						result.LatestVersion = ExtractJsonString(value, "version");
						result.ModName = ExtractJsonString(value, "name");
						result.NexusUrl = ExtractJsonString(value, "nexus");
						result.Changelog = ExtractJsonString(value, "changelog");
						if (string.IsNullOrEmpty(result.LatestVersion))
						{
							result.Success = false;
							result.ErrorMessage = "Could not parse version from response";
							RecordHealthError(pluginGuid, result.ErrorMessage);
							LogWarningMsg(result.ErrorMessage);
							onComplete?.Invoke(result);
							Object.Destroy((Object)(object)((Component)this).gameObject);
							yield break;
						}
						result.Success = true;
						result.UpdateAvailable = CompareVersions(currentVersion, result.LatestVersion) < 0;
						if (result.UpdateAvailable)
						{
							LogInfo("Update available for " + result.ModName + ": " + currentVersion + " -> " + result.LatestVersion);
						}
						else
						{
							LogInfo(result.ModName + " is up to date (v" + currentVersion + ")");
						}
					}
					catch (Exception ex)
					{
						result.Success = false;
						result.ErrorMessage = "Parse error: " + ex.Message;
						RecordHealthError(pluginGuid, result.ErrorMessage);
						LogErrorMsg(result.ErrorMessage);
					}
				}
				finally
				{
					((IDisposable)www)?.Dispose();
				}
				onComplete?.Invoke(result);
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}

			private string ExtractJsonString(string json, string key)
			{
				Match match = ExtractFieldRegex.Match(json);
				while (match.Success)
				{
					if (string.Equals(match.Groups["key"].Value, key, StringComparison.Ordinal))
					{
						return match.Groups["value"].Value;
					}
					match = match.NextMatch();
				}
				return null;
			}
		}

		private const string VersionsUrl = "https://azraelgodking.github.io/SunhavenMod/versions.json";

		private static readonly Dictionary<string, ModHealthSnapshot> HealthByPluginGuid = new Dictionary<string, ModHealthSnapshot>(StringComparer.OrdinalIgnoreCase);

		private static readonly object HealthLock = new object();

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

		private static readonly object ModPatternCacheLock = new object();

		private static readonly Regex ExtractFieldRegex = new Regex("\"(?<key>[^\"]+)\"\\s*:\\s*(?:\"(?<value>[^\"]*)\"|null)", RegexOptions.Compiled);

		public static void CheckForUpdate(string pluginGuid, string currentVersion, ManualLogSource logger = null, Action<VersionCheckResult> onComplete = null)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			TouchHealth(pluginGuid);
			VersionCheckRunner versionCheckRunner = new GameObject("VersionChecker").AddComponent<VersionCheckRunner>();
			Object.DontDestroyOnLoad((Object)(object)((Component)versionCheckRunner).gameObject);
			SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(((Component)versionCheckRunner).gameObject);
			versionCheckRunner.StartCheck(pluginGuid, currentVersion, logger, onComplete);
		}

		public static ModHealthSnapshot GetHealthSnapshot(string pluginGuid)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid))
			{
				return null;
			}
			lock (HealthLock)
			{
				if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value))
				{
					return null;
				}
				return new ModHealthSnapshot
				{
					PluginGuid = value.PluginGuid,
					LastCheckUtc = value.LastCheckUtc,
					ExceptionCount = value.ExceptionCount,
					LastError = value.LastError
				};
			}
		}

		public static int CompareVersions(string v1, string v2)
		{
			if (string.IsNullOrEmpty(v1) || string.IsNullOrEmpty(v2))
			{
				return 0;
			}
			v1 = v1.TrimStart('v', 'V');
			v2 = v2.TrimStart('v', 'V');
			int num = v1.IndexOfAny(new char[2] { '-', '+' });
			if (num >= 0)
			{
				v1 = v1.Substring(0, num);
			}
			int num2 = v2.IndexOfAny(new char[2] { '-', '+' });
			if (num2 >= 0)
			{
				v2 = v2.Substring(0, num2);
			}
			string[] array = v1.Split(new char[1] { '.' });
			string[] array2 = v2.Split(new char[1] { '.' });
			int num3 = Math.Max(array.Length, array2.Length);
			for (int i = 0; i < num3; i++)
			{
				int result;
				int num4 = ((i < array.Length && int.TryParse(array[i], out result)) ? result : 0);
				int result2;
				int num5 = ((i < array2.Length && int.TryParse(array2[i], out result2)) ? result2 : 0);
				if (num4 < num5)
				{
					return -1;
				}
				if (num4 > num5)
				{
					return 1;
				}
			}
			return 0;
		}

		private static void TouchHealth(string pluginGuid)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid))
			{
				return;
			}
			lock (HealthLock)
			{
				if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value))
				{
					value = new ModHealthSnapshot
					{
						PluginGuid = pluginGuid
					};
					HealthByPluginGuid[pluginGuid] = value;
				}
				value.LastCheckUtc = DateTime.UtcNow;
			}
		}

		private static void RecordHealthError(string pluginGuid, string errorMessage)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid))
			{
				return;
			}
			lock (HealthLock)
			{
				if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value))
				{
					value = new ModHealthSnapshot
					{
						PluginGuid = pluginGuid
					};
					HealthByPluginGuid[pluginGuid] = value;
				}
				value.LastCheckUtc = DateTime.UtcNow;
				value.ExceptionCount++;
				value.LastError = errorMessage;
			}
		}

		private static Regex GetModPattern(string pluginGuid)
		{
			lock (ModPatternCacheLock)
			{
				if (!ModPatternCache.TryGetValue(pluginGuid, out Regex value))
				{
					value = new Regex("\"" + Regex.Escape(pluginGuid) + "\"\\s*:\\s*\\{([^}]+)\\}", RegexOptions.Compiled | RegexOptions.Singleline);
					ModPatternCache[pluginGuid] = value;
				}
				return value;
			}
		}
	}
	public static class VersionCheckerExtensions
	{
		public static void NotifyUpdateAvailable(this VersionChecker.VersionCheckResult result, ManualLogSource logger = null)
		{
			if (!result.UpdateAvailable)
			{
				return;
			}
			string text = result.ModName + " update available: v" + result.LatestVersion;
			try
			{
				Type type = ReflectionHelper.FindWishType("NotificationStack");
				if (type != null)
				{
					Type type2 = ReflectionHelper.FindType("SingletonBehaviour`1", "Wish");
					if (type2 != null)
					{
						object obj = type2.MakeGenericType(type).GetProperty("Instance")?.GetValue(null);
						if (obj != null)
						{
							MethodInfo method = type.GetMethod("SendNotification", new Type[5]
							{
								typeof(string),
								typeof(int),
								typeof(int),
								typeof(bool),
								typeof(bool)
							});
							if (method != null)
							{
								method.Invoke(obj, new object[5] { text, 0, 1, false, true });
								return;
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				if (logger != null)
				{
					logger.LogWarning((object)("Failed to send native notification: " + ex.Message));
				}
			}
			if (logger != null)
			{
				logger.LogWarning((object)("[UPDATE AVAILABLE] " + text));
			}
			if (!string.IsNullOrEmpty(result.NexusUrl) && logger != null)
			{
				logger.LogWarning((object)("Download at: " + result.NexusUrl));
			}
		}
	}
	public static class SceneHelpers
	{
		private static readonly string[] MenuScenePatterns = new string[3] { "menu", "title", "bootstrap" };

		private static readonly string[] ExactMenuScenes = new string[2] { "MainMenu", "Bootstrap" };

		public static bool IsMenuScene(string sceneName)
		{
			if (string.IsNullOrEmpty(sceneName))
			{
				return true;
			}
			string[] exactMenuScenes = ExactMenuScenes;
			foreach (string text in exactMenuScenes)
			{
				if (sceneName == text)
				{
					return true;
				}
			}
			string text2 = sceneName.ToLowerInvariant();
			exactMenuScenes = MenuScenePatterns;
			foreach (string value in exactMenuScenes)
			{
				if (text2.Contains(value))
				{
					return true;
				}
			}
			return false;
		}

		public static bool IsCurrentSceneMenu()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			return IsMenuScene(((Scene)(ref activeScene)).name);
		}

		public static bool IsInGame()
		{
			return !IsCurrentSceneMenu();
		}

		public static string GetCurrentSceneName()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			return ((Scene)(ref activeScene)).name;
		}

		public static bool IsMainMenu()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			return ((Scene)(ref activeScene)).name == "MainMenu";
		}
	}
	public static class SceneRootSurvivor
	{
		private static readonly object Lock = new object();

		private static readonly List<string> NoKillSubstrings = new List<string>();

		private static Harmony _harmony;

		public static void TryRegisterPersistentRunnerGameObject(GameObject go)
		{
			if (!((Object)(object)go == (Object)null))
			{
				TryAddNoKillListSubstring(((Object)go).name);
			}
		}

		public static void TryAddNoKillListSubstring(string nameSubstring)
		{
			if (string.IsNullOrEmpty(nameSubstring))
			{
				return;
			}
			lock (Lock)
			{
				bool flag = false;
				for (int i = 0; i < NoKillSubstrings.Count; i++)
				{
					if (string.Equals(NoKillSubstrings[i], nameSubstring, StringComparison.OrdinalIgnoreCase))
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					NoKillSubstrings.Add(nameSubstring);
				}
			}
			EnsurePatched();
		}

		private static void EnsurePatched()
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			//IL_00a3: Expected O, but got Unknown
			if (_harmony != null)
			{
				return;
			}
			lock (Lock)
			{
				if (_harmony == null)
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(Scene), "GetRootGameObjects", Type.EmptyTypes, (Type[])null);
					if (!(methodInfo == null))
					{
						string text = typeof(SceneRootSurvivor).Assembly.GetName().Name ?? "Unknown";
						Harmony val = new Harmony("SunhavenMods.SceneRootSurvivor." + text);
						val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SceneRootSurvivor), "OnGetRootGameObjectsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						_harmony = val;
					}
				}
			}
		}

		private static void OnGetRootGameObjectsPostfix(ref GameObject[] __result)
		{
			if (__result == null || __result.Length == 0)
			{
				return;
			}
			List<string> list;
			lock (Lock)
			{
				if (NoKillSubstrings.Count == 0)
				{
					return;
				}
				list = new List<string>(NoKillSubstrings);
			}
			List<GameObject> list2 = new List<GameObject>(__result);
			for (int i = 0; i < list.Count; i++)
			{
				string noKill = list[i];
				list2.RemoveAll((GameObject a) => (Object)(object)a != (Object)null && ((Object)a).name.IndexOf(noKill, StringComparison.OrdinalIgnoreCase) >= 0);
			}
			__result = list2.ToArray();
		}
	}
	public static class ReflectionHelper
	{
		public static readonly BindingFlags AllBindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;

		public static Type FindType(string typeName, params string[] namespaces)
		{
			Assembly[] assemblies;
			if (namespaces != null && namespaces.Length != 0)
			{
				string[] array = namespaces;
				for (int i = 0; i < array.Length; i++)
				{
					Type type = AccessTools.TypeByName(array[i] + "." + typeName);
					if (type != null)
					{
						return type;
					}
				}
				assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					array = namespaces;
					foreach (string text in array)
					{
						try
						{
							Type type2 = assembly.GetType(text + "." + typeName, throwOnError: false);
							if (type2 != null)
							{
								return type2;
							}
						}
						catch
						{
						}
					}
				}
				assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly2 in assemblies)
				{
					try
					{
						Type type3 = assembly2.GetTypes().FirstOrDefault((Type t) => namespaces.Any((string ns) => string.Equals(t.FullName, ns + "." + typeName, StringComparison.Ordinal) || (t.Name == typeName && string.Equals(t.Namespace, ns, StringComparison.Ordinal))));
						if (type3 != null)
						{
							return type3;
						}
					}
					catch (ReflectionTypeLoadException ex)
					{
						Type type4 = ex.Types?.FirstOrDefault((Type t) => t != null && namespaces.Any((string ns) => string.Equals(t.FullName, ns + "." + typeName, StringComparison.Ordinal) || (t.Name == typeName && string.Equals(t.Namespace, ns, StringComparison.Ordinal))));
						if (type4 != null)
						{
							return type4;
						}
					}
				}
				return null;
			}
			Type type5 = AccessTools.TypeByName(typeName);
			if (type5 != null)
			{
				return type5;
			}
			assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly3 in assemblies)
			{
				try
				{
					type5 = assembly3.GetTypes().FirstOrDefault((Type t) => t.Name == typeName || t.FullName == typeName);
					if (type5 != null)
					{
						return type5;
					}
				}
				catch (ReflectionTypeLoadException)
				{
				}
			}
			return null;
		}

		public static Type FindModPlugin(string assemblyName)
		{
			if (string.IsNullOrWhiteSpace(assemblyName))
			{
				return null;
			}
			Type type = FindType("Plugin", assemblyName);
			if (type != null && string.Equals(type.Assembly.GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase))
			{
				return type;
			}
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (!string.Equals(assembly.GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}
				Type type2 = assembly.GetType(assemblyName + ".Plugin", throwOnError: false);
				if (type2 != null)
				{
					return type2;
				}
				try
				{
					Type type3 = assembly.GetTypes().FirstOrDefault((Type t) => t.IsClass && !t.IsAbstract && t.Name == "Plugin");
					if (type3 != null)
					{
						return type3;
					}
				}
				catch (ReflectionTypeLoadException ex)
				{
					Type type4 = ex.Types?.FirstOrDefault((Type t) => t != null && t.IsClass && !t.IsAbstract && t.Name == "Plugin");
					if (type4 != null)
					{
						return type4;
					}
				}
			}
			return null;
		}

		public static MethodInfo GetStaticMethod(Type type, string methodName)
		{
			return type?.GetMethod(methodName, AllBindingFlags);
		}

		public static Type FindWishType(string typeName)
		{
			return FindType(typeName, "Wish");
		}

		public static object GetStaticValue(Type type, string memberName)
		{
			if (type == null)
			{
				return null;
			}
			try
			{
				PropertyInfo property = type.GetProperty(memberName, AllBindingFlags);
				if (property != null && property.GetMethod != null && property.GetIndexParameters().Length == 0)
				{
					return property.GetValue(null);
				}
			}
			catch (AmbiguousMatchException)
			{
				return null;
			}
			FieldInfo field = type.GetField(memberName, AllBindingFlags);
			if (field != null)
			{
				return field.GetValue(null);
			}
			return null;
		}

		public static object GetSingletonInstance(Type type)
		{
			if (type == null)
			{
				return null;
			}
			string[] array = new string[5] { "Instance", "instance", "_instance", "Singleton", "singleton" };
			foreach (string memberName in array)
			{
				object staticValue = GetStaticValue(type, memberName);
				if (staticValue != null)
				{
					return staticValue;
				}
			}
			return null;
		}

		public static object GetInstanceValue(object instance, string memberName)
		{
			if (instance == null)
			{
				return null;
			}
			Type type = instance.GetType();
			while (type != null)
			{
				PropertyInfo property = type.GetProperty(memberName, AllBindingFlags);
				if (property != null && property.GetMethod != null)
				{
					return property.GetValue(instance);
				}
				FieldInfo field = type.GetField(memberName, AllBindingFlags);
				if (field != null)
				{
					return field.GetValue(instance);
				}
				type = type.BaseType;
			}
			return null;
		}

		public static bool SetInstanceValue(object instance, string memberName, object value)
		{
			if (instance == null)
			{
				return false;
			}
			Type type = instance.GetType();
			while (type != null)
			{
				PropertyInfo property = type.GetProperty(memberName, AllBindingFlags);
				if (property != null && property.SetMethod != null)
				{
					property.SetValue(instance, value);
					return true;
				}
				FieldInfo field = type.GetField(memberName, AllBindingFlags);
				if (field != null)
				{
					field.SetValue(instance, value);
					return true;
				}
				type = type.BaseType;
			}
			return false;
		}

		public static object InvokeMethod(object instance, string methodName, params object[] args)
		{
			if (instance == null)
			{
				return null;
			}
			Type type = instance.GetType();
			Type[] array = args?.Select((object a) => a?.GetType() ?? typeof(object)).ToArray() ?? Type.EmptyTypes;
			MethodInfo methodInfo = AccessTools.Method(type, methodName, array, (Type[])null);
			if (methodInfo == null)
			{
				methodInfo = type.GetMethod(methodName, AllBindingFlags);
			}
			if (methodInfo == null)
			{
				return null;
			}
			return methodInfo.Invoke(instance, args);
		}

		public static object InvokeStaticMethod(Type type, string methodName, params object[] args)
		{
			if (type == null)
			{
				return null;
			}
			Type[] array = args?.Select((object a) => a?.GetType() ?? typeof(object)).ToArray() ?? Type.EmptyTypes;
			MethodInfo methodInfo = AccessTools.Method(type, methodName, array, (Type[])null);
			if (methodInfo == null)
			{
				methodInfo = type.GetMethod(methodName, AllBindingFlags);
			}
			if (methodInfo == null)
			{
				return null;
			}
			return methodInfo.Invoke(null, args);
		}

		public static FieldInfo[] GetAllFields(Type type)
		{
			if (type == null)
			{
				return Array.Empty<FieldInfo>();
			}
			FieldInfo[] fields = type.GetFields(AllBindingFlags);
			IEnumerable<FieldInfo> second;
			if (!(type.BaseType != null) || !(type.BaseType != typeof(object)))
			{
				second = Enumerable.Empty<FieldInfo>();
			}
			else
			{
				IEnumerable<FieldInfo> allFields = GetAllFields(type.BaseType);
				second = allFields;
			}
			return fields.Concat(second).Distinct().ToArray();
		}

		public static PropertyInfo[] GetAllProperties(Type type)
		{
			if (type == null)
			{
				return Array.Empty<PropertyInfo>();
			}
			PropertyInfo[] properties = type.GetProperties(AllBindingFlags);
			IEnumerable<PropertyInfo> second;
			if (!(type.BaseType != null) || !(type.BaseType != typeof(object)))
			{
				second = Enumerable.Empty<PropertyInfo>();
			}
			else
			{
				IEnumerable<PropertyInfo> allProperties = GetAllProperties(type.BaseType);
				second = allProperties;
			}
			return (from p in properties.Concat(second)
				group p by p.Name into g
				select g.First()).ToArray();
		}

		public static T TryGetValue<T>(object instance, string memberName, T defaultValue = default(T))
		{
			try
			{
				object instanceValue = GetInstanceValue(instance, memberName);
				if (instanceValue is T result)
				{
					return result;
				}
				if (instanceValue != null && typeof(T).IsAssignableFrom(instanceValue.GetType()))
				{
					return (T)instanceValue;
				}
				return defaultValue;
			}
			catch
			{
				return defaultValue;
			}
		}
	}
	internal static class MinimalJsonParser
	{
		internal static void WriteJsonString(StringBuilder sb, string value)
		{
			sb.Append('"');
			if (value != null)
			{
				foreach (char c in value)
				{
					switch (c)
					{
					case '"':
						sb.Append("\\\"");
						break;
					case '\\':
						sb.Append("\\\\");
						break;
					case '\n':
						sb.Append("\\n");
						break;
					case '\r':
						sb.Append("\\r");
						break;
					case '\t':
						sb.Append("\\t");
						break;
					case '\b':
						sb.Append("\\b");
						break;
					case '\f':
						sb.Append("\\f");
						break;
					default:
						sb.Append(c);
						break;
					}
				}
			}
			sb.Append('"');
		}

		internal static void SkipWhitespace(string json, ref int pos)
		{
			while (pos < json.Length && char.IsWhiteSpace(json[pos]))
			{
				pos++;
			}
		}

		internal static object ParseValue(string json, ref int pos)
		{
			SkipWhitespace(json, ref pos);
			if (pos >= json.Length)
			{
				return null;
			}
			char c = json[pos];
			switch (c)
			{
			case '"':
				return ParseString(json, ref pos);
			case '{':
				return ParseObject(json, ref pos);
			case '[':
				return ParseArray(json, ref pos);
			case 't':
				return ParseLiteral(json, ref pos, "true", true);
			case 'f':
				return ParseLiteral(json, ref pos, "false", false);
			case 'n':
				return ParseLiteral(json, ref pos, "null", null);
			default:
				if (!char.IsDigit(c))
				{
					return null;
				}
				goto case '-';
			case '-':
				return ParseNumber(json, ref pos);
			}
		}

		internal static Dictionary<string, object> ParseObject(string json, ref int pos)
		{
			SkipWhitespace(json, ref pos);
			if (pos >= json.Length || json[pos] != '{')
			{
				return null;
			}
			pos++;
			Dictionary<string, object> dictionary = new Dictionary<string, object>();
			SkipWhitespace(json, ref pos);
			if (pos < json.Length && json[pos] == '}')
			{
				pos++;
				return dictionary;
			}
			while (pos < json.Length)
			{
				SkipWhitespace(json, ref pos);
				string text = ParseString(json, ref pos);
				if (text == null)
				{
					break;
				}
				SkipWhitespace(json, ref pos);
				if (pos >= json.Length || json[pos] != ':')
				{
					break;
				}
				pos++;
				SkipWhitespace(json, ref pos);
				dictionary[text] = ParseValue(json, ref pos);
				SkipWhitespace(json, ref pos);
				if (pos >= json.Length || json[pos] != ',')
				{
					break;
				}
				pos++;
			}
			SkipWhitespace(json, ref pos);
			if (pos < json.Length && json[pos] == '}')
			{
				pos++;
			}
			return dictionary;
		}

		internal static List<object> ParseArray(string json, ref int pos)
		{
			SkipWhitespace(json, ref pos);
			if (pos >= json.Length || json[pos] != '[')
			{
				return null;
			}
			pos++;
			List<object> list = new List<object>();
			SkipWhitespace(json, ref pos);
			if (pos < json.Length && json[pos] == ']')
			{
				pos++;
				return list;
			}
			while (pos < json.Length)
			{
				SkipWhitespace(json, ref pos);
				list.Add(ParseValue(json, ref pos));
				SkipWhitespace(json, ref pos);
				if (pos >= json.Length || json[pos] != ',')
				{
					break;
				}
				pos++;
			}
			SkipWhitespace(json, ref pos);
			if (pos < json.Length && json[pos] == ']')
			{
				pos++;
			}
			return list;
		}

		internal static string ParseString(string json, ref int pos)
		{
			SkipWhitespace(json, ref pos);
			if (pos >= json.Length || json[pos] != '"')
			{
				return null;
			}
			pos++;
			StringBuilder stringBuilder = new StringBuilder();
			while (pos < json.Length)
			{
				char c = json[pos];
				if (c == '\\' && pos + 1 < json.Length)
				{
					pos++;
					switch (json[pos])
					{
					case '"':
						stringBuilder.Append('"');
						break;
					case '\\':
						stringBuilder.Append('\\');
						break;
					case '/':
						stringBuilder.Append('/');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'u':
					{
						if (pos + 4 < json.Length && ushort.TryParse(json.Substring(pos + 1, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
						{
							pos += 4;
							if (result >= 55296 && result <= 56319 && pos + 5 < json.Length && json[pos] == '\\' && json[pos + 1] == 'u' && ushort.TryParse(json.Substring(pos + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result2) && result2 >= 56320 && result2 <= 57343)
							{
								stringBuilder.Append(char.ConvertFromUtf32(char.ConvertToUtf32((char)result, (char)result2)));
								pos += 6;
							}
							else
							{
								stringBuilder.Append((char)result);
							}
						}
						else
						{
							stringBuilder.Append('u');
						}
						break;
					}
					default:
						stringBuilder.Append(json[pos]);
						break;
					}
					pos++;
				}
				else
				{
					if (c == '"')
					{
						pos++;
						return stringBuilder.ToString();
					}
					stringBuilder.Append(c);
					pos++;
				}
			}
			return stringBuilder.ToString();
		}

		internal static object ParseNumber(string json, ref int pos)
		{
			int num = pos;
			bool flag = false;
			if (pos < json.Length && json[pos] == '-')
			{
				pos++;
			}
			while (pos < json.Length && char.IsDigit(json[pos]))
			{
				pos++;
			}
			if (pos < json.Length && json[pos] == '.')
			{
				flag = true;
				pos++;
				while (pos < json.Length && char.IsDigit(json[pos]))
				{
					pos++;
				}
			}
			if (pos < json.Length && (json[pos] == 'e' || json[pos] == 'E'))
			{
				flag = true;
				pos++;
				if (pos < json.Length && (json[pos] == '+' || json[pos] == '-'))
				{
					pos++;
				}
				while (pos < json.Length && char.IsDigit(json[pos]))
				{
					pos++;
				}
			}
			string s = json.Substring(num, pos - num);
			if (flag && double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return result;
			}
			if (!flag && long.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
			{
				return result2;
			}
			return 0L;
		}

		internal static object ParseLiteral(string json, ref int pos, string literal, object result)
		{
			if (pos + literal.Length <= json.Length && json.Substring(pos, literal.Length) == literal)
			{
				pos += literal.Length;
				return result;
			}
			pos++;
			return null;
		}

		internal static int ToInt(object val)
		{
			if (val is long num)
			{
				return (int)num;
			}
			if (val is double num2)
			{
				return (int)num2;
			}
			if (val is int)
			{
				return (int)val;
			}
			return 0;
		}
	}
	public static class ModLocalization
	{
		private static readonly string[] SupportedLanguageCodes = new string[16]
		{
			"en", "da", "de", "es", "fr", "it", "ja", "ko", "nl", "pt",
			"pt-BR", "ru", "sv", "zh-CN", "zh-TW", "uk"
		};

		private static readonly Dictionary<string, string> LanguageAlias = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "pt-br", "pt-BR" },
			{ "pt_br", "pt-BR" },
			{ "zh-cn", "zh-CN" },
			{ "zh_cn", "zh-CN" },
			{ "zh-tw", "zh-TW" },
			{ "zh_tw", "zh-TW" }
		};

		private static string _modId;

		private static Dictionary<string, Dictionary<string, string>> _tables;

		private static ManualLogSource _log;

		private static bool _initialized;

		private static bool _forceEnglish;

		public static string CurrentLanguage { get; private set; } = "en";

		public static bool ForceEnglish => _forceEnglish;

		public static bool IsReady
		{
			get
			{
				if (_initialized && _tables != null)
				{
					return _tables.Count > 0;
				}
				return false;
			}
		}

		public static event Action<string> LanguageChanged
		{
			add
			{
				LanguageChangeWatcher.LanguageChanged += value;
			}
			remove
			{
				LanguageChangeWatcher.LanguageChanged -= value;
			}
		}

		public static void Init(string modId, Dictionary<string, Dictionary<string, string>> tables, Harmony harmony, ManualLogSource log)
		{
			_modId = modId ?? string.Empty;
			_tables = tables ?? new Dictionary<string, Dictionary<string, string>>();
			_log = log;
			_initialized = true;
			RefreshCurrentLanguage();
			LanguageChangeWatcher.EnsurePatched(harmony);
		}

		public static void SetForceEnglish(bool forceEnglish)
		{
			_forceEnglish = forceEnglish;
			ApplyEffectiveLanguage();
		}

		internal static void OnGameLanguageChanged(string languageCode)
		{
			if (_tables == null || _forceEnglish)
			{
				return;
			}
			string text = NormalizeLanguageCode(languageCode);
			if (!string.Equals(CurrentLanguage, text, StringComparison.OrdinalIgnoreCase))
			{
				CurrentLanguage = text;
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogDebug((object)("[" + _modId + "] Language changed to " + CurrentLanguage));
				}
			}
		}

		public static void RefreshCurrentLanguage()
		{
			ApplyEffectiveLanguage();
		}

		private static void ApplyEffectiveLanguage()
		{
			if (_forceEnglish)
			{
				CurrentLanguage = "en";
			}
			else
			{
				RefreshCurrentLanguageFromGame();
			}
		}

		private static void RefreshCurrentLanguageFromGame()
		{
			try
			{
				string currentLanguageCode = LocalizationManager.CurrentLanguageCode;
				if (!string.IsNullOrWhiteSpace(currentLanguageCode))
				{
					CurrentLanguage = NormalizeLanguageCode(currentLanguageCode);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("[" + _modId + "] Failed to read LocalizationManager.CurrentLanguageCode: " + ex.Message));
				}
				CurrentLanguage = "en";
			}
		}

		public static string T(string key)
		{
			if (!TryT(key, out string value))
			{
				return key;
			}
			return value;
		}

		public static string T(string key, params object[] args)
		{
			string text = T(key);
			if (args == null || args.Length == 0)
			{
				return text;
			}
			try
			{
				return string.Format(CultureInfo.InvariantCulture, text, args);
			}
			catch (FormatException ex)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogWarning((object)("[" + _modId + "] Format failed for key '" + key + "': " + ex.Message));
				}
				return text;
			}
		}

		public static bool TryT(string key, out string value)
		{
			value = null;
			if (string.IsNullOrEmpty(key))
			{
				return false;
			}
			if (_tables == null || !_tables.TryGetValue(key, out Dictionary<string, string> value2) || value2 == null)
			{
				return false;
			}
			if (TryGetForLanguage(value2, CurrentLanguage, out value))
			{
				return true;
			}
			if (!string.Equals(CurrentLanguage, "en", StringComparison.OrdinalIgnoreCase) && TryGetForLanguage(value2, "en", out value))
			{
				return true;
			}
			return false;
		}

		private static bool TryGetForLanguage(Dictionary<string, string> translations, string languageCode, out string value)
		{
			value = null;
			if (translations == null)
			{
				return false;
			}
			string text = NormalizeLanguageCode(languageCode);
			if (translations.TryGetValue(text, out value) && !string.IsNullOrEmpty(value))
			{
				return true;
			}
			foreach (KeyValuePair<string, string> translation in translations)
			{
				if (string.Equals(translation.Key, text, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(translation.Value))
				{
					value = translation.Value;
					return true;
				}
			}
			return false;
		}

		public static string NormalizeLanguageCode(string code)
		{
			if (string.IsNullOrWhiteSpace(code))
			{
				return "en";
			}
			string text = code.Trim();
			if (LanguageAlias.TryGetValue(text, out string value))
			{
				return value;
			}
			string[] supportedLanguageCodes = SupportedLanguageCodes;
			foreach (string text2 in supportedLanguageCodes)
			{
				if (string.Equals(text2, text, StringComparison.OrdinalIgnoreCase))
				{
					return text2;
				}
			}
			return "en";
		}

		public static Dictionary<string, Dictionary<string, string>> ParseStringsJson(string json)
		{
			Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(StringComparer.Ordinal);
			if (string.IsNullOrWhiteSpace(json))
			{
				return dictionary;
			}
			int pos = 0;
			Dictionary<string, object> dictionary2 = MinimalJsonParser.ParseObject(json, ref pos);
			if (dictionary2 == null)
			{
				return dictionary;
			}
			foreach (KeyValuePair<string, object> item in dictionary2)
			{
				if (!(item.Value is Dictionary<string, object> dictionary3))
				{
					continue;
				}
				Dictionary<string, string> dictionary4 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				foreach (KeyValuePair<string, object> item2 in dictionary3)
				{
					if (item2.Value is string value)
					{
						dictionary4[NormalizeLanguageCode(item2.Key)] = value;
					}
				}
				if (dictionary4.Count > 0)
				{
					dictionary[item.Key] = dictionary4;
				}
			}
			return dictionary;
		}

		public static Dictionary<string, Dictionary<string, string>> LoadEmbeddedStrings(Assembly assembly, string resourceName, ManualLogSource log = null)
		{
			try
			{
				using Stream stream = assembly.GetManifestResourceStream(resourceName);
				if (stream == null)
				{
					if (log != null)
					{
						log.LogError((object)("Localization resource not found: " + resourceName));
					}
					return new Dictionary<string, Dictionary<string, string>>();
				}
				using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
				return ParseStringsJson(streamReader.ReadToEnd());
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					log.LogError((object)("Failed to load localization resource '" + resourceName + "': " + ex.Message));
				}
				return new Dictionary<string, Dictionary<string, string>>();
			}
		}

		public static void Shutdown()
		{
			_log = null;
		}
	}
	public static class LanguageChangeWatcher
	{
		private static bool _patched;

		public static event Action<string> LanguageChanged;

		public static void EnsurePatched(Harmony harmony)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			if (_patched || harmony == null)
			{
				return;
			}
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(LanguageChangeWatcher), "OnSetLanguageAndCode", (Type[])null, (Type[])null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(LocalizationManager), "SetLanguageAndCode", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_patched = true;
			}
			catch (Exception innerException)
			{
				throw new InvalidOperationException("Failed to patch LocalizationManager.SetLanguageAndCode", innerException);
			}
		}

		private static void OnSetLanguageAndCode(string LanguageName, string LanguageCode)
		{
			string text = ModLocalization.NormalizeLanguageCode(string.IsNullOrWhiteSpace(LanguageCode) ? LocalizationManager.CurrentLanguageCode : LanguageCode);
			ModLocalization.OnGameLanguageChanged(text);
			LanguageChangeWatcher.LanguageChanged?.Invoke(text);
		}

		internal static void RaiseLanguageChanged(string languageCode)
		{
			string obj = ModLocalization.NormalizeLanguageCode(languageCode);
			LanguageChangeWatcher.LanguageChanged?.Invoke(obj);
		}
	}
	public static class LocalizationBootstrap
	{
		public static ConfigEntry<bool> BindForceEnglish(ConfigFile config)
		{
			ConfigEntry<bool> entry = config.Bind<bool>("Localization", "ForceEnglish", false, "Keep this mod's UI in English and ignore Sun Haven's in-game language setting.");
			ApplyForceEnglish(entry.Value);
			entry.SettingChanged += delegate
			{
				ApplyForceEnglish(entry.Value);
			};
			return entry;
		}

		private static void ApplyForceEnglish(bool forceEnglish)
		{
			ModLocalization.SetForceEnglish(forceEnglish);
			LanguageChangeWatcher.RaiseLanguageChanged(ModLocalization.CurrentLanguage);
		}

		public static void Init(string pluginGuid, Harmony harmony, ManualLogSource log, Assembly assembly = null)
		{
			if ((object)assembly == null)
			{
				assembly = Assembly.GetCallingAssembly();
			}
			Dictionary<string, Dictionary<string, string>> tables = ModLocalization.LoadEmbeddedStrings(assembly, pluginGuid + ".Localization.strings.json", log);
			ModLocalization.Init(pluginGuid, tables, harmony, log);
		}

		public static void EnsureInitialized(string pluginGuid, Harmony harmony, ManualLogSource log, Assembly assembly = null)
		{
			if (!ModLocalization.IsReady)
			{
				Init(pluginGuid, harmony, log, assembly);
			}
		}
	}
	public static class TextInputFocusGuard
	{
		private const float DefaultPollIntervalSeconds = 0.25f;

		private static float _nextPollTime = -1f;

		private static bool _cachedDefer;

		private static bool _tmpTypeLookupDone;

		private static Type _tmpInputFieldType;

		private static bool _qcLookupDone;

		private static Type _qcType;

		private static PropertyInfo _qcInstanceProp;

		private static PropertyInfo _qcIsActiveProp;

		private static FieldInfo _qcIsActiveField;

		public static bool ShouldDeferModHotkeys(ManualLogSource debugLog = null, float pollIntervalSeconds = 0.25f)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < _nextPollTime)
			{
				return _cachedDefer;
			}
			_nextPollTime = realtimeSinceStartup + Mathf.Max(0.05f, pollIntervalSeconds);
			bool flag = false;
			try
			{
				if (GUIUtility.keyboardControl != 0)
				{
					flag = true;
				}
				if (!flag)
				{
					EventSystem current = EventSystem.current;
					GameObject val = ((current != null) ? current.currentSelectedGameObject : null);
					if ((Object)(object)val != (Object)null)
					{
						if ((Object)(object)val.GetComponent<InputField>() != (Object)null)
						{
							flag = true;
						}
						else if (TryGetTmpInputField(val))
						{
							flag = true;
						}
					}
				}
				if (!flag && IsQuantumConsoleActiveInternal(debugLog))
				{
					flag = true;
				}
			}
			catch (Exception ex)
			{
				if (debugLog != null)
				{
					debugLog.LogDebug((object)("[TextInputFocusGuard] " + ex.Message));
				}
			}
			_cachedDefer = flag;
			return flag;
		}

		private static bool TryGetTmpInputField(GameObject go)
		{
			if (!_tmpTypeLookupDone)
			{
				_tmpTypeLookupDone = true;
				_tmpInputFieldType = AccessTools.TypeByName("TMPro.TMP_InputField");
			}
			if (_tmpInputFieldType == null)
			{
				return false;
			}
			return (Object)(object)go.GetComponent(_tmpInputFieldType) != (Object)null;
		}

		public static bool IsQuantumConsoleActive(ManualLogSource debugLog = null)
		{
			return IsQuantumConsoleActiveInternal(debugLog);
		}

		public static bool IsUnityUiTextInputFocused()
		{
			try
			{
				EventSystem current = EventSystem.current;
				GameObject val = ((current != null) ? current.currentSelectedGameObject : null);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				if ((Object)(object)val.GetComponent<InputField>() != (Object)null)
				{
					return true;
				}
				return TryGetTmpInputField(val);
			}
			catch
			{
				return false;
			}
		}

		private static bool IsQuantumConsoleActiveInternal(ManualLogSource debugLog)
		{
			try
			{
				if (!_qcLookupDone)
				{
					_qcLookupDone = true;
					_qcType = AccessTools.TypeByName("QFSW.QC.QuantumConsole");
					if (_qcType != null)
					{
						_qcInstanceProp = AccessTools.Property(_qcType, "Instance");
						_qcIsActiveProp = AccessTools.Property(_qcType, "IsActive");
						_qcIsActiveField = AccessTools.Field(_qcType, "isActive") ?? AccessTools.Field(_qcType, "_isActive");
					}
				}
				if (_qcType == null)
				{
					return false;
				}
				object obj = _qcInstanceProp?.GetValue(null);
				if (obj == null)
				{
					return false;
				}
				if (_qcIsActiveProp != null && _qcIsActiveProp.PropertyType == typeof(bool))
				{
					return (bool)_qcIsActiveProp.GetValue(obj);
				}
				if (_qcIsActiveField != null && _qcIsActiveField.FieldType == typeof(bool))
				{
					return (bool)_qcIsActiveField.GetValue(obj);
				}
			}
			catch (Exception ex)
			{
				if (debugLog != null)
				{
					debugLog.LogDebug((object)("[TextInputFocusGuard] Quantum Console focus check failed: " + ex.Message));
				}
			}
			return false;
		}
	}
}
namespace CropOptimizer
{
	[BepInPlugin("com.azraelgodking.cropoptimizer", "Crop Optimizer", "2.2.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		private CropOptimizerConfig _config;

		internal CropForecast _forecast;

		private CropHUD _hud;

		private VaultIntegration _vaultIntegration;

		private bool _hudVisible = true;

		private bool _applicationQuitting;

		private bool _isVaultLoadedEventSubscribed;

		private EventInfo _vaultLoadedEventInfo;

		private Delegate _vaultLoadedHandler;

		public static ManualLogSource Log { get; private set; }

		public static Plugin Instance { get; private set; }

		public static bool IsDebugLoggingEnabled
		{
			get
			{
				Plugin instance = Instance;
				if (instance == null)
				{
					return false;
				}
				return instance._config?.DebugLogging?.Value == true;
			}
		}

		private void Awake()
		{
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigFile val = CreateNamedConfig();
			ConfigFileHelper.ReplacePluginConfig((BaseUnityPlugin)(object)this, val, (Action<string>)Log.LogWarning);
			_config = new CropOptimizerConfig(val);
			if (!_config.Enabled.Value)
			{
				Log.LogInfo((object)"Crop Optimizer disabled in config.");
				ModDiagnostics.LogModStartup(Log, "com.azraelgodking.cropoptimizer", "Crop Optimizer", "2.2.1", ModHealthIntegrationSummary.Build(("DevTools", "com.azraelgodking.havendevtools"), ("Todo", "com.azraelgodking.sunhaventodo"), ("Birthday", "com.azraelgodking.squirrelsbirthdayreminder"), ("Vault", "com.azraelgodking.thevault")), "disabled", false);
				return;
			}
			_forecast = new CropForecast();
			_vaultIntegration = new VaultIntegration();
			LocalizationBootstrap.BindForceEnglish(val);
			_harmony = new Harmony("com.azraelgodking.cropoptimizer");
			LocalizationBootstrap.Init("com.azraelgodking.cropoptimizer", _harmony, Log, Assembly.GetExecutingAssembly());
			ModLocalization.LanguageChanged += OnLanguageChanged;
			CropGrowthPatch.Apply(_harmony, _forecast);
			CharacterLoadPatch.Apply(_harmony, _forecast);
			_hud = PersistentRunnerBase.CreateRunner<CropHUD>();
			_hud.Initialize(_forecast);
			_hud.SetPlacement(_config.HudPositionX.Value, _config.HudPositionY.Value);
			_hud.SetScale(_config.HudScale.Value);
			_hud.SetVisible(_config.HudEnabled.Value);
			_hudVisible = _config.HudEnabled.Value;
			_hud.PlacementChanged += OnCropHudPlacementChanged;
			_hud.SetHoverConfig(_config.HoverTooltipEnabled, _config.HoverTooltipMaxWorldDistance);
			_hud.SetHighlightConfig(_config);
			TrySubscribeVaultLoaded();
			if (_config.CheckForUpdates.Value)
			{
				VersionChecker.CheckForUpdate("com.azraelgodking.cropoptimizer", "2.2.1", Log, delegate(VersionChecker.VersionCheckResult result)
				{
					result.NotifyUpdateAvailable(Log);
				});
			}
			Log.LogInfo((object)"Crop Optimizer v2.2.1 loaded");
			ModDiagnostics.LogModStartup(Log, "com.azraelgodking.cropoptimizer", "Crop Optimizer", "2.2.1", ModHealthIntegrationSummary.Build(("DevTools", "com.azraelgodking.havendevtools"), ("Todo", "com.azraelgodking.sunhaventodo"), ("Birthday", "com.azraelgodking.squirrelsbirthdayreminder"), ("Vault", "com.azraelgodking.thevault")), "startup", false);
		}

		private void OnDestroy()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			string text = ((Scene)(ref activeScene)).name ?? string.Empty;
			string text2 = text.ToLowerInvariant();
			if (_applicationQuitting || !Application.isPlaying || text2.Contains("menu") || text2.Contains("title"))
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)("[Lifecycle] Plugin OnDestroy during expected teardown (scene: " + text + ")"));
				}
			}
			else
			{
				ManualLogSource log2 = Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[Lifecycle] Plugin OnDestroy outside expected teardown (scene: " + text + ")"));
				}
			}
			if ((Object)(object)_hud != (Object)null)
			{
				_hud.PlacementChanged -= OnCropHudPlacementChanged;
			}
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if (_isVaultLoadedEventSubscribed && _vaultLoadedEventInfo != null && (object)_vaultLoadedHandler != null)
			{
				_vaultLoadedEventInfo.RemoveEventHandler(null, _vaultLoadedHandler);
				_isVaultLoadedEventSubscribed = false;
				_vaultLoadedEventInfo = null;
				_vaultLoadedHandler = null;
			}
			ModLocalization.LanguageChanged -= OnLanguageChanged;
			ModLocalization.Shutdown();
		}

		private static void OnLanguageChanged(string _)
		{
			Instance?._hud?.RefreshLocalization();
		}

		private void OnApplicationQuit()
		{
			_applicationQuitting = true;
		}

		private void OnCropHudPlacementChanged(float x, float y)
		{
			if (_config != null)
			{
				_config.HudPositionX.Value = x;
				_config.HudPositionY.Value = y;
			}
		}

		private void Update()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if (_config != null && !((Object)(object)_hud == (Object)null) && !TextInputFocusGuard.ShouldDeferModHotkeys(Log) && (int)_config.ToggleHudKey.Value != 0 && Input.GetKeyDown(_config.ToggleHudKey.Value))
			{
				_hudVisible = !_hudVisible;
				_hud.SetVisible(_hudVisible);
			}
		}

		public static string GetHudSummary()
		{
			if (Instance?._forecast == null)
			{
				return "Not ready";
			}
			return $"Crops: {Instance._forecast.Snapshot().Count}, Value: {Instance._forecast.GetProjectedSellTotal()}g";
		}

		internal static List<CropForecast.CropTypeSummary> GetTopCrops(int count = 5)
		{
			return Instance?._forecast?.GetTopCropsByValue(count) ?? new List<CropForecast.CropTypeSummary>();
		}

		private void TrySubscribeVaultLoaded()
		{
			if (_vaultIntegration == null || !_vaultIntegration.IsAvailable)
			{
				return;
			}
			try
			{
				Type bridgeType = VaultReflection.GetBridgeType();
				if (bridgeType == null)
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogDebug((object)"[CropOptimizer] The Vault is installed but TheVault.Abstractions was not found; skipping Vault integration.");
					}
					return;
				}
				EventInfo eventInfo = bridgeType.GetEvent("OnVaultLoaded", BindingFlags.Static | BindingFlags.Public);
				if (eventInfo == null)
				{
					ManualLogSource log2 = Log;
					if (log2 != null)
					{
						log2.LogDebug((object)"[CropOptimizer] Vault bridge OnVaultLoaded event not found; skipping subscription.");
					}
					return;
				}
				MethodInfo method = ((object)this).GetType().GetMethod("OnVaultLoaded", BindingFlags.Instance | BindingFlags.NonPublic);
				if (method == null)
				{
					return;
				}
				Delegate obj = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, method, throwOnBindFailure: false);
				if ((object)obj == null)
				{
					ManualLogSource log3 = Log;
					if (log3 != null)
					{
						log3.LogWarning((object)"[CropOptimizer] Vault OnVaultLoaded handler could not be bound.");
					}
					return;
				}
				eventInfo.AddEventHandler(null, obj);
				_isVaultLoadedEventSubscribed = true;
				_vaultLoadedEventInfo = eventInfo;
				_vaultLoadedHandler = obj;
				if (VaultReflection.IsVaultReady(VaultReflection.GetBridgeInstance(bridgeType)))
				{
					_vaultIntegration.TryRegisterProjectedValueCurrency();
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log4 = Log;
				if (log4 != null)
				{
					log4.LogWarning((object)("[CropOptimizer] Failed to subscribe to Vault OnVaultLoaded: " + ex.Message));
				}
			}
		}

		private void OnVaultLoaded()
		{
			try
			{
				_vaultIntegration?.TryRegisterProjectedValueCurrency();
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)("[CropOptimizer] Vault currency registration failed on OnVaultLoaded: " + ex.Message));
				}
			}
		}

		private static ConfigFile CreateNamedConfig()
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			string text = Path.Combine(Paths.ConfigPath, "CropOptimizer.cfg");
			string text2 = Path.Combine(Paths.ConfigPath, "com.azraelgodking.cropoptimizer.cfg");
			try
			{
				if (!File.Exists(text) && File.Exists(text2))
				{
					File.Copy(text2, text);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)("[Config] Migration to CropOptimizer.cfg failed: " + ex.Message));
				}
			}
			return new ConfigFile(text, true);
		}
	}
	internal static class PluginInfo
	{
		public const string PLUGIN_GUID = "com.azraelgodking.cropoptimizer";

		public const string PLUGIN_NAME = "Crop Optimizer";

		public const string PLUGIN_VERSION = "2.2.1";
	}
}
namespace CropOptimizer.UI
{
	internal enum CropHighlightKind
	{
		NeedsWater,
		NeedsFertilizer
	}
	internal readonly struct CropHighlightTarget
	{
		public readonly Vector3 Center;

		public readonly Vector2Int Tile;

		public readonly CropHighlightKind Kind;

		public CropHighlightTarget(Vector3 center, Vector2Int tile, CropHighlightKind kind)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			Center = center;
			Tile = tile;
			Kind = kind;
		}
	}
	internal static class CropFieldHighlightScanner
	{
		private const int MaxTargets = 600;

		private const float CacheRefreshSeconds = 0.85f;

		private static readonly List<CropHighlightTarget> _scratch = new List<CropHighlightTarget>(128);

		public static IReadOnlyList<CropHighlightTarget> Scan(bool includeDry, bool includeUnfertilized)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			_scratch.Clear();
			if (!includeDry && !includeUnfertilized)
			{
				return _scratch;
			}
			Object[] crops = CropSceneCache.GetCrops(0.85f);
			if (crops == null || crops.Length == 0)
			{
				return _scratch;
			}
			Object[] array = crops;
			foreach (Object val in array)
			{
				if (_scratch.Count >= 600)
				{
					break;
				}
				Component val2 = (Component)(object)((val is Component) ? val : null);
				if (val2 == null || !CropPresence.IsPresent(val2))
				{
					continue;
				}
				object cropInstance = val2;
				if (GameFarmCoords.TryGetCropFarmTile(val2, out var farmTile))
				{
					Vector3 selectionWorldPosition = GameFarmCoords.GetSelectionWorldPosition(farmTile);
					bool fullyGrown;
					bool fertilized;
					if (includeDry && !CropTileReflection.IsCropTileWatered(val2))
					{
						_scratch.Add(new CropHighlightTarget(selectionWorldPosition, farmTile, CropHighlightKind.NeedsWater));
					}
					else if (includeUnfertilized && (!CropGrowthPatch.TryGetTooltipFullyGrown(cropInstance, out fullyGrown) || !fullyGrown) && CropGrowthPatch.TryGetTooltipFertilized(cropInstance, out fertilized) && !fertilized)
					{
						_scratch.Add(new CropHighlightTarget(selectionWorldPosition, farmTile, CropHighlightKind.NeedsFertilizer));
					}
				}
			}
			return _scratch;
		}

		public static void InvalidateCache()
		{
			CropSceneCache.Invalidate();
		}
	}
	internal static class CropHoverQuery
	{
		private static Type _cropType;

		private static Camera _cachedGameplayCamera;

		private static float _nextGameplayCameraSearchTime;

		private static Vector3 _lastHoverMouseScreen;

		private static Component _lastHoverCrop;

		private static float _nextHoverFullScanTime;

		internal const float CropCacheRefreshSeconds = 1.5f;

		private const float HoverRescanMinInterval = 0.055f;

		private const float MouseMoveSkipScanPxSq = 9f;

		private const float GameplayCameraSearchCooldown = 2f;

		private static readonly string[] WaterMemberNames = new string[10] { "isWatered", "IsWatered", "watered", "Watered", "needsWater", "NeedsWater", "water", "Water", "hasWater", "HasWater" };

		private static readonly string[] FertilizerMemberNames = new string[10] { "fertilizer", "Fertilizer", "fertilized", "Fertilized", "hasFertilizer", "HasFertilizer", "fertilizerType", "FertilizerType", "soilFertility", "SoilFertility" };

		private static bool _dumpedCropMembers;

		private static bool _loggedTileProbe;

		private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;

		private static Type CropType => _cropType ?? (_cropType = AccessTools.TypeByName("Wish.Crop"));

		public static Camera ResolveGameplayCamera()
		{
			if ((Object)(object)Camera.main != (Object)null && ((Behaviour)Camera.main).enabled)
			{
				_cachedGameplayCamera = Camera.main;
				return Camera.main;
			}
			if ((Object)(object)_cachedGameplayCamera != (Object)null && ((Behaviour)_cachedGameplayCamera).enabled && ((Component)_cachedGameplayCamera).gameObject.activeInHierarchy)
			{
				return _cachedGameplayCamera;
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < _nextGameplayCameraSearchTime)
			{
				return _cachedGameplayCamera;
			}
			Camera[] array = Object.FindObjectsOfType<Camera>();
			Camera val = null;
			Camera[] array2 = array;
			foreach (Camera val2 in array2)
			{
				if (!((Object)(object)val2 == (Object)null) && ((Behaviour)val2).enabled && ((Component)val2).gameObject.activeInHierarchy && ((Object)(object)val == (Object)null || val2.depth > val.depth))
				{
					val = val2;
				}
			}
			_cachedGameplayCamera = val;
			_nextGameplayCameraSearchTime = unscaledTime + (((Object)(object)val != (Object)null) ? 2f : 0.25f);
			return val;
		}

		public static void InvalidateGameplayCameraCache()
		{
			_cachedGameplayCamera = null;
			_nextGameplayCameraSearchTime = 0f;
		}

		public static void InvalidateCropCache()
		{
			CropSceneCache.Invalidate();
		}

		public static void InvalidateHoverAssist()
		{
			_lastHoverCrop = null;
			_nextHoverFullScanTime = 0f;
		}

		public static bool TryMouseWorldOnPlane(Camera camera, float planeZ, out Vector3 world)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: 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)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			world = default(Vector3);
			if ((Object)(object)camera == (Object)null)
			{
				return false;
			}
			Ray val = camera.ScreenPointToRay(Input.mousePosition);
			Plane val2 = default(Plane);
			((Plane)(ref val2))..ctor(Vector3.forward, new Vector3(0f, 0f, planeZ));
			float num = default(float);
			if (((Plane)(ref val2)).Raycast(val, ref num))
			{
				world = ((Ray)(ref val)).GetPoint(num);
				return true;
			}
			Vector3 mousePosition = Input.mousePosition;
			mousePosition.z = Mathf.Max(0.01f, Mathf.Abs(((Component)camera).transform.position.z));
			world = camera.ScreenToWorldPoint(mousePosition);
			world.z = planeZ;
			return true;
		}

		public static bool TryGetClosestCropNearMouse(Camera camera, float maxWorldDistance, out Component crop)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: 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_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			crop = null;
			if ((Object)(object)camera == (Object)null)
			{
				return false;
			}
			Object[] crops = CropSceneCache.GetCrops(1.5f);
			if (crops == null || crops.Length == 0)
			{
				return false;
			}
			float planeZ = 0f;
			Object[] array = crops;
			foreach (Object obj in array)
			{
				Component val = (Component)(object)((obj is Component) ? obj : null);
				if (val != null && (Object)(object)val != (Object)null)
				{
					planeZ = val.transform.position.z;
					break;
				}
			}
			if (!TryMouseWorldOnPlane(camera, planeZ, out var world))
			{
				return false;
			}
			Vector2Int mouseFarmTile = GameFarmCoords.GetMouseFarmTile();
			Vector3 mousePosition = Input.mousePosition;
			float unscaledTime = Time.unscaledTime;
			float num = maxWorldDistance * maxWorldDistance;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(world.x, world.y);
			Vector3 val3 = mousePosition - _lastHoverMouseScreen;
			bool num2 = ((Vector3)(ref val3)).sqrMagnitude <= 9f;
			_lastHoverMouseScreen = mousePosition;
			if (num2 && unscaledTime < _nextHoverFullScanTime)
			{
				if (!((Object)(object)_lastHoverCrop != (Object)null))
				{
					crop = null;
					return false;
				}
				Component lastHoverCrop = _lastHoverCrop;
				if ((Object)(object)lastHoverCrop != (Object)null && CropPresence.IsPresent(lastHoverCrop) && GameFarmCoords.IsCropOnFarmTile(lastHoverCrop, mouseFarmTile))
				{
					crop = lastHoverCrop;
					return true;
				}
			}
			_nextHoverFullScanTime = unscaledTime + 0.055f;
			Component val4 = null;
			float num3 = num;
			array = crops;
			foreach (Object obj2 in array)
			{
				Component val5 = (Component)(object)((obj2 is Component) ? obj2 : null);
				if (val5 != null && !((Object)(object)val5 == (Object)null) && CropPresence.IsPresent(val5))
				{
					if (GameFarmCoords.IsCropOnFarmTile(val5, mouseFarmTile))
					{
						crop = val5;
						_lastHoverCrop = val5;
						return true;
					}
					Vector2 val6 = new Vector2(val5.transform.position.x, val5.transform.position.y) - val2;
					float sqrMagnitude = ((Vector2)(ref val6)).sqrMagnitude;
					if (sqrMagnitude < num3)
					{
						num3 = sqrMagnitude;
						val4 = val5;
					}
				}
			}
			if ((Object)(object)val4 == (Object)null)
			{
				_lastHoverCrop = null;
				return false;
			}
			crop = val4;
			_lastHoverCrop = val4;
			return true;
		}

		public static string FormatWaterGuess(object cropInstance)
		{
			return FormatMemberGuess(cropInstance, WaterMemberNames);
		}

		public static string FormatFertilizerGuess(object cropInstance)
		{
			return FormatMemberGuess(cropInstance, FertilizerMemberNames);
		}

		private static void LogTileDebugOnce(Component crop, Vector2Int tile)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (_loggedTileProbe)
			{
				return;
			}
			_loggedTileProbe = true;
			try
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)CropTileReflection.BuildDebugSnapshot(crop, tile));
				}
			}
			catch
			{
			}
		}

		private static void DumpCropMembersOnce(object cropInstance)
		{
			if (_dumpedCropMembers || cropInstance == null)
			{
				return;
			}
			_dumpedCropMembers = true;
			try
			{
				Plugin instance = Plugin.Instance;
				if (instance != null)
				{
					_ = ((BaseUnityPlugin)instance).Config;
				}
				if (!((Object)(object)Plugin.Instance != (Object)null) || !IsDebugLogEnabled())
				{
					return;
				}
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					Type type = cropInstance.GetType();
					log.LogInfo((object)("[HoverDebug] Dumping members of " + type.FullName));
					FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
					foreach (FieldInfo fieldInfo in fields)
					{
						log.LogInfo((object)("[HoverDebug]   field  " + fieldInfo.FieldType.Name + " " + fieldInfo.Name));
					}
					PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
					foreach (PropertyInfo propertyInfo in properties)
					{
						log.LogInfo((object)("[HoverDebug]   prop   " + propertyInfo.PropertyType.Name + " " + propertyInfo.Name));
					}
				}
			}
			catch
			{
			}
		}

		private static bool IsDebugLogEnabled()
		{
			try
			{
				return (Object)(object)Plugin.Instance != (Object)null && Plugin.IsDebugLoggingEnabled;
			}
			catch
			{
				return false;
			}
		}

		private static string FormatMemberGuess(object instance, string[] names)
		{
			if (instance == null)
			{
				return "?";
			}
			try
			{
				Type type = instance.GetType();
				while (type != null)
				{
					foreach (string name in names)
					{
						FieldInfo fieldInfo = null;
						PropertyInfo propertyInfo = null;
						try
						{
							fieldInfo = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
						}
						catch
						{
						}
						try
						{
							propertyInfo = ((fieldInfo == null) ? type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) : null);
						}
						catch
						{
						}
						if (!(fieldInfo == null) || !(propertyInfo == null))
						{
							object obj3 = ((fieldInfo != null) ? fieldInfo.GetValue(instance) : propertyInfo.GetValue(instance, null));
							if (obj3 != null)
							{
								return FormatPrimitiveGuess(obj3);
							}
						}
					}
					type = type.BaseType;
				}
			}
			catch
			{
			}
			return "?";
		}

		private static string FormatPrimitiveGuess(object raw)
		{
			if (raw is bool)
			{
				if (!(bool)raw)
				{
					return ModLocalization.T("crop.guess.no");
				}
				return ModLocalization.T("crop.guess.yes");
			}
			if (raw is int num)
			{
				if (num == 0)
				{
					return ModLocalization.T("crop.guess.no");
				}
				return ModLocalization.T("crop.guess.yesValue", num);
			}
			if (raw is float value)
			{
				if (!(Math.Abs(value) > 0.0001f))
				{
					return ModLocalization.T("crop.guess.no");
				}
				return ModLocalization.T("crop.guess.yesValue", value.ToString("0.##"));
			}
			if (raw is double value2)
			{
				if (!(Math.Abs(value2) > 0.0001))
				{
					return ModLocalization.T("crop.guess.no");
				}
				return ModLocalization.T("crop.guess.yesValue", value2.ToString("0.##"));
			}
			string text = raw.ToString();
			if (string.IsNullOrWhiteSpace(text))
			{
				return "?";
			}
			if (text.Length > 48)
			{
				return text.Substring(0, 45) + "...";
			}
			return text;
		}

		public static TooltipContent BuildTooltipContent(Component crop, CropForecast forecast)
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: 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_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_027c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_032b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0334: Unknown result type (might be due to invalid IL or missing references)
			//IL_033a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0310: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03db: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_042d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0433: Unknown result type (might be due to invalid IL or missing references)
			//IL_0395: Unknown result type (might be due to invalid IL or missing references)
			//IL_038e: Unknown result type (might be due to invalid IL or missing references)
			//IL_039e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a4: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)crop == (Object)null)
			{
				return null;
			}
			DumpCropMembersOnce(crop);
			TooltipContent tooltipContent = new TooltipContent();
			int itemId = 0;
			string title = ModLocalization.T("crop.tooltip.crop");
			if (CropGrowthPatch.TryGetTooltipHarvestItemId(crop, out itemId) && itemId > 0)
			{
				title = ((!CropGrowthPatch.TryGetItemDisplayName(itemId, out string displayName) || string.IsNullOrWhiteSpace(displayName)) ? ModLocalization.T("crop.tooltip.itemId", itemId) : displayName);
			}
			tooltipContent.Title = title;
			bool fullyGrown = false;
			CropGrowthPatch.TryGetTooltipFullyGrown(crop, out fullyGrown);
			if (fullyGrown)
			{
				tooltipContent.HeaderTag = ModLocalization.T("crop.tooltip.headerTag.ready");
			}
			if (CropGrowthPatch.TryGetTooltipQualityInfo(crop, out string label, out float multiplier) && !string.IsNullOrEmpty(label))
			{
				tooltipContent.QualityColor = QualityColorFor(label);
				tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Quality, tooltipContent.QualityColor, ModLocalization.T("crop.tooltip.quality", label, "#B8A078", multiplier)));
			}
			if (CropGrowthPatch.TryGetTooltipGrowthStageInfo(crop, out string stageText, out float grownRatio) && !string.IsNullOrEmpty(stageText))
			{
				tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Sprout, UiStyle.Sprout, ModLocalization.T("crop.tooltip.growth", stageText)));
			}
			float etaHours;
			bool resolvedFromReflection;
			CropForecast.CropState state;
			if (fullyGrown)
			{
				tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Ready, UiStyle.Fertilizer, ModLocalization.T("crop.tooltip.readyNow", "#F7D982")));
			}
			else if (CropGrowthPatch.TryGetTooltipEtaHours(crop, out etaHours, out resolvedFromReflection) && resolvedFromReflection)
			{
				tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Clock, UiStyle.Clock, ModLocalization.T("crop.tooltip.readyIn", "#F7D982", Mathf.Max(0f, etaHours))));
			}
			else if (forecast != null && forecast.TryGetState(((Object)crop).GetInstanceID(), out state))
			{
				tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Clock, UiStyle.Clock, ModLocalization.T("crop.tooltip.readyInCached", "#F7D982", Mathf.Max(0f, state.NextHarvestEtaHours), "#B8A078")));
			}
			else
			{
				tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Clock, UiStyle.Clock, ModLocalization.T("crop.tooltip.etaUnknown", "#B8A078")));
			}
			if (CropGrowthPatch.TryGetTooltipProjectedGold(crop, out var projectedGold, out grownRatio) && projectedGold > 0)
			{
				tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Coin, UiStyle.Coin, ModLocalization.T("crop.tooltip.projectedGold", "#F7D982", projectedGold)));
			}
			Vector2Int tile = default(Vector2Int);
			bool num = CropTileReflection.TryGetTileCoordForCrop(crop, out tile);
			string raw = null;
			if (num)
			{
				raw = CropTileReflection.DescribeFarmingTileState(tile, crop.transform.position, haveWorldPos: true, out var _);
				if (IsDebugLogEnabled())
				{
					LogTileDebugOnce(crop, tile);
				}
			}
			var (text, iconColor) = DescribeWaterState(raw);
			tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Water, iconColor, text));
			if (CropGrowthPatch.TryGetTooltipFertilized(crop, out var fertilized))
			{
				string obj = (fertilized ? ModLocalization.T("crop.tooltip.fertilized") : ModLocalization.T("crop.tooltip.notFertilized"));
				tooltipContent.Rows.Add(RowSpec.Make(text: obj, icon: UiStyle.IconKind.Fertilizer, iconColor: (Color32)(fertilized ? UiStyle.Fertilizer : new Color32((byte)154, (byte)136, (byte)96, byte.MaxValue))));
			}
			if (CropGrowthPatch.TryGetTooltipManaInfused(crop, out var manaInfused) && manaInfused)
			{
				tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Mana, UiStyle.Mana, ModLocalization.T("crop.tooltip.manaInfused")));
			}
			if (num)
			{
				tooltipContent.Rows.Add(RowSpec.Make(UiStyle.IconKind.Tile, UiStyle.Tile, ModLocalization.T("crop.tooltip.tile", "#B8A078", ((Vector2Int)(ref tile)).x, ((Vector2Int)(ref tile)).y)));
			}
			if (itemId > 0)
			{
				List<string> list = new List<string>();
				CropGrowthPatch.AppendItemExtraLines(itemId, list);
				if (list.Count > 0)
				{
					tooltipContent.Extras = string.Join(" · ", list);
				}
			}
			return tooltipContent;
		}

		private static Color32 QualityColorFor(string label)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(label))
			{
				return UiStyle.QualityNormal;
			}
			string text = label.ToLowerInvariant();
			if (text.Contains("gold") || text.Contains("iridium"))
			{
				return UiStyle.QualityGold;
			}
			if (text.Contains("silver"))
			{
				return UiStyle.QualitySilver;
			}
			return UiStyle.QualityNormal;
		}

		private static (string text, Color32 color) DescribeWaterState(string raw)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(raw))
			{
				return (text: ModLocalization.T("crop.water.unknown"), color: UiStyle.Water);
			}
			string text = raw.ToLowerInvariant();
			if (text.Contains("water"))
			{
				return (text: ModLocalization.T("crop.water.watered"), color: UiStyle.Water);
			}
			if (text.Contains("hoed"))
			{
				return (text: ModLocalization.T("crop.water.hoedDry"), color: new Color32((byte)201, (byte)160, (byte)112, byte.MaxValue));
			}
			return (text: ModLocalization.T("crop.water.label", raw), color: UiStyle.Water);
		}
	}
	internal sealed class CropHUD : PersistentRunnerBase
	{
		private CropForecast _forecast;

		private Canvas _canvas;

		private GameObject _canvasGo;

		private CropHudView _hudView;

		private CropTooltipView _tooltipView;

		private float _scale = 1f;

		private bool _isVisible = true;

		private float _initialX = 24f;

		private float _initialY = 80f;

		private bool _hasInitialPlacement;

		private ConfigEntry<bool> _hoverTooltipEnabled;

		private ConfigEntry<float> _hoverTooltipMaxWorldDistance;

		private CropOptimizerConfig _config;

		private GameSelectionHighlightRenderer _fieldHighlights;

		private float _nextHighlightScanTime;

		private IReadOnlyList<CropHighlightTarget> _lastHighlightTargets = Array.Empty<CropHighlightTarget>();

		private Component _tooltipContentCrop;

		private TooltipContent _tooltipContentCache;

		private float _nextTooltipContentRebuildTime;

		private int _lastHudTrackedCount = -1;

		private long _lastHudProjectedGold = long.MinValue;

		private bool? _lastHudTooltipShownInUi;

		private const float ForecastReconcileSeconds = 1.5f;

		private const float TooltipContentRefreshSeconds = 0.22f;

		private float _nextCropScanTime;

		protected override string RunnerName => "CropHUD";

		public event Action<float, float> PlacementChanged;

		public void Initialize(CropForecast forecast)
		{
			_forecast = forecast;
		}

		public void SetHoverConfig(ConfigEntry<bool> enabled, ConfigEntry<float> maxWorldDistance)
		{
			_hoverTooltipEnabled = enabled;
			_hoverTooltipMaxWorldDistance = maxWorldDistance;
		}

		public void SetHighlightConfig(CropOptimizerConfig config)
		{
			_config = config;
		}

		public void SetPlacement(float x, float y)
		{
			_initialX = x;
			_initialY = y;
			_hasInitialPlacement = true;
			_hudView?.SetPlacement(x, y);
		}

		public void SetScale(float scale)
		{
			_scale = Mathf.Clamp(scale, 0.5f, 2.5f);
			_hudView?.SetScale(_scale);
			_tooltipView?.SetScale(_scale);
		}

		public void SetVisible(bool visible)
		{
			_isVisible = visible;
			_hudView?.SetVisible(visible && IsCharacterSessionActive());
		}

		public void RefreshLocalization()
		{
			if (_hudView != null)
			{
				bool tooltipEnabled = _hoverTooltipEnabled != null && _hoverTooltipEnabled.Value;
				_hudView.RefreshLocalization(tooltipEnabled);
				_lastHudTooltipShownInUi = null;
			}
		}

		private static bool IsCharacterSessionActive()
		{
			if (!SceneHelpers.IsInGame())
			{
				return false;
			}
			try
			{
				GameSave instance = SingletonBehaviour<GameSave>.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return false;
				}
				GameSaveData currentSave = instance.CurrentSave;
				if (currentSave == null)
				{
					return false;
				}
				return currentSave.characterData != null;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[CropHUD] Failed to determine character session state: " + ex.Message));
				}
				return false;
			}
		}

		private void EnsureCanvas()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_canvas != (Object)null) || !((Object)(object)_canvasGo != (Object)null))
			{
				_canvasGo = new GameObject("CropOptimizer_HUDCanvas", new Type[3]
				{
					typeof(Canvas),
					typeof(CanvasScaler),
					typeof(GraphicRaycaster)
				});
				Object.DontDestroyOnLoad((Object)(object)_canvasGo);
				_canvas = _canvasGo.GetComponent<Canvas>();
				_canvas.renderMode = (RenderMode)0;
				_canvas.sortingOrder = 5000;
				CanvasScaler component = _canvasGo.GetComponent<CanvasScaler>();
				component.uiScaleMode = (ScaleMode)1;
				component.referenceResolution = new Vector2(1920f, 1080f);
				component.matchWidthOrHeight = 0.5f;
				_hudView = new CropHudView(_canvasGo.transform);
				_hudView.PlacementChanged += delegate(float x, float y)
				{
					this.PlacementChanged?.Invoke(x, y);
				};
				_hudView.TooltipToggleClicked += OnTooltipToggleClicked;
				_hudView.SetScale(_scale);
				if (_hasInitialPlacement)
				{
					_hudView.SetPlacement(_initialX, _initialY);
				}
				_hudView.SetVisible(_isVisible);
				_hudView.SetTooltipEnabled(_hoverTooltipEnabled != null && _hoverTooltipEnabled.Value);
				_tooltipView = new CropTooltipView(_canvasGo.transform);
				_tooltipView.SetScale(_scale);
				_tooltipView.SetVisible(visible: false);
				_fieldHighlights = new GameSelectionHighlightRenderer();
				_fieldHighlights.EnsureCreated(_canvasGo.transform);
			}
		}

		private void RebuildCanvas()
		{
			_fieldHighlights?.Destroy();
			_fieldHighlights = null;
			if ((Object)(object)_canvasGo != (Object)null)
			{
				Object.Destroy((Object)(object)_canvasGo);
				_canvasGo = null;
				_canvas = null;
				_hudView = null;
				_tooltipView = null;
			}
			EnsureCanvas();
		}

		protected override void OnUpdate()
		{
			if (_forecast == null)
			{
				return;
			}
			EnsureCanvas();
			bool flag = IsCharacterSessionActive();
			if (flag)
			{
				float unscaledTime = Time.unscaledTime;
				if (unscaledTime >= _nextCropScanTime)
				{
					_nextCropScanTime = unscaledTime + 1.5f;
					CropSceneCache.GetCrops(1.5f);
				}
			}
			if (_hudView != null)
			{
				bool flag2 = _isVisible && flag;
				_hudView.SetVisible(flag2);
				if (flag2)
				{
					int count = _forecast.Snapshot().Count;
					long num = _forecast.GetProjectedSellTotal();
					if (count != _lastHudTrackedCount || num != _lastHudProjectedGold)
					{
						_lastHudTrackedCount = count;
						_lastHudProjectedGold = num;
						_hudView.UpdateStats(count, num);
					}
					bool flag3 = _hoverTooltipEnabled != null && _hoverTooltipEnabled.Value;
					if (!_lastHudTooltipShownInUi.HasValue || flag3 != _lastHudTooltipShownInUi.Value)
					{
						_lastHudTooltipShownInUi = flag3;
						_hudView.SetTooltipEnabled(flag3);
					}
				}
				else
				{
					_lastHudTrackedCount = -1;
					_lastHudProjectedGold = long.MinValue;
					_lastHudTooltipShownInUi = null;
				}
			}
			UpdateHoverTooltip(flag);
			UpdateFieldHighlights(flag);
		}

		private void UpdateFieldHighlights(bool sessionLive)
		{
			if (_fieldHighlights == null || _config == null)
			{
				return;
			}
			if (!sessionLive || (!_config.HighlightDryTiles.Value && !_config.HighlightUnfertilizedTiles.Value))
			{
				_fieldHighlights.SetVisible(visible: false);
				return;
			}
			bool value = _config.HighlightOnlyWhenHoldingTool.Value;
			bool value2 = _config.HighlightRequireMouseButton.Value;
			bool mouseButton = Input.GetMouseButton(0);
			bool flag = _config.HighlightDryTiles.Value && (!value || HeldItemProbe.IsWateringCanSelected()) && (!value || !value2 || mouseButton);
			bool flag2 = _config.HighlightUnfertilizedTiles.Value && (!value || HeldItemProbe.IsFertilizerSelected()) && (!value || !value2 || mouseButton);
			if (!flag && !flag2)
			{
				_fieldHighlights.SetVisible(visible: false);
				return;
			}
			_fieldHighlights.SetVisible(visible: true);
			float unscaledTime = Time.unscaledTime;
			float nu