Decompiled source of ComfortMe v1.3.1

ComfortMe.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ComfortMe")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Hammer helper that marks comfort pieces which would raise the room you are standing in.")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyInformationalVersion("1.3.1+dc2c013a7936dbe0b0d6920547a60dad7684f06a")]
[assembly: AssemblyProduct("ComfortMe")]
[assembly: AssemblyTitle("ComfortMe")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ComfortMe
{
	[BepInPlugin("hardwire99.comfortme", "ComfortMe", "1.3.1")]
	public sealed class ComfortMePlugin : BaseUnityPlugin
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__2_0;

			internal void <Awake>b__2_0(ConsoleEventArgs _)
			{
				GroupCatalogPanel.Dump();
			}
		}

		internal static ManualLogSource Log;

		private Harmony harmony;

		private void Awake()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected O, but got Unknown
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: 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: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			if (!ModConfig.Enabled.Value)
			{
				Log.LogInfo((object)"ComfortMe 1.3.1 is disabled in config.");
				return;
			}
			harmony = new Harmony("hardwire99.comfortme");
			try
			{
				harmony.PatchAll(typeof(ComfortMePlugin).Assembly);
				Log.LogInfo((object)$"Harmony applied {harmony.GetPatchedMethods().Count()} patch(es).");
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Harmony PatchAll failed: {arg}");
			}
			ModLocalization.Hook();
			Log.LogInfo((object)("ComfortMe 1.3.1 loaded (1.3.1, dll " + GetDllTimestamp() + ")."));
			object obj = <>c.<>9__2_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate
				{
					GroupCatalogPanel.Dump();
				};
				<>c.<>9__2_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("comfortme", "Dump ComfortMe catalog HUD diagnostics", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}

		private static string GetDllTimestamp()
		{
			string location = Assembly.GetExecutingAssembly().Location;
			if (!File.Exists(location))
			{
				return "unknown";
			}
			return File.GetLastWriteTime(location).ToString("yyyy-MM-dd HH:mm:ss");
		}
	}
	internal enum ComfortVerdict
	{
		Skip,
		NewGroup,
		Upgrade,
		Redundant,
		Unlit,
		Downgrade
	}
	internal readonly struct ComfortClassification
	{
		internal ComfortVerdict Verdict { get; }

		internal ComfortGroup Group { get; }

		internal int Current { get; }

		internal int Candidate { get; }

		internal int Delta { get; }

		internal bool IsUpgrade
		{
			get
			{
				if (Verdict != ComfortVerdict.Upgrade)
				{
					return Verdict == ComfortVerdict.NewGroup;
				}
				return true;
			}
		}

		internal ComfortClassification(ComfortVerdict verdict, ComfortGroup group, int current, int candidate)
		{
			//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)
			Verdict = verdict;
			Group = group;
			Current = current;
			Candidate = candidate;
			Delta = candidate - current;
		}

		internal static int ShopRank(ComfortVerdict verdict)
		{
			switch (verdict)
			{
			case ComfortVerdict.NewGroup:
			case ComfortVerdict.Upgrade:
				return 0;
			case ComfortVerdict.Unlit:
				return 1;
			case ComfortVerdict.Redundant:
				return 2;
			case ComfortVerdict.Downgrade:
				return 3;
			default:
				return 4;
			}
		}
	}
	internal sealed class ComfortSnapshot
	{
		private const float CacheSeconds = 0.2f;

		private const float CacheMoveSqr = 0.0625f;

		private static ComfortSnapshot cached;

		private static Vector3 cachedPos;

		private static float cachedTime = -999f;

		private static bool cachedShelter;

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

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

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

		internal static ComfortSnapshot Empty { get; } = new ComfortSnapshot();

		internal int Total { get; private set; }

		internal bool InShelter { get; private set; }

		internal Dictionary<ComfortGroup, int> GroupMax { get; } = new Dictionary<ComfortGroup, int>();

		internal Dictionary<ComfortGroup, string> GroupSource { get; } = new Dictionary<ComfortGroup, string>();

		internal List<UniqueComfort> Uniques { get; } = new List<UniqueComfort>();

		internal List<Piece> CountingPieces { get; } = new List<Piece>();

		internal static ComfortSnapshot Capture(Player player)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: 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_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: 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)
			if ((Object)(object)player == (Object)null)
			{
				return Empty;
			}
			Vector3 position = ((Component)player).transform.position;
			bool flag = player.InShelter();
			float time = Time.time;
			if (cached != null && time - cachedTime < 0.2f)
			{
				Vector3 val = position - cachedPos;
				if (((Vector3)(ref val)).sqrMagnitude < 0.0625f && flag == cachedShelter)
				{
					return cached;
				}
			}
			ComfortSnapshot comfortSnapshot = new ComfortSnapshot
			{
				InShelter = flag,
				Total = SE_Rested.CalculateComfortLevel(player)
			};
			List<Piece> list = new List<Piece>();
			Piece.GetAllComfortPiecesInRadius(position, 10f, list);
			Dictionary<ComfortGroup, Piece> dictionary = new Dictionary<ComfortGroup, Piece>();
			for (int i = 0; i < list.Count; i++)
			{
				Piece val2 = list[i];
				if ((Object)(object)val2 == (Object)null || val2.m_comfort <= 0)
				{
					continue;
				}
				AddIdentity(comfortSnapshot.placedKeys, val2);
				int comfort = val2.GetComfort();
				if (comfort > 0)
				{
					AddIdentity(comfortSnapshot.activeKeys, val2);
					ComfortGroup comfortGroup = val2.m_comfortGroup;
					string local = PieceDiscovery.LocalName(val2);
					int value;
					if ((int)comfortGroup == 0)
					{
						if (!comfortSnapshot.Uniques.Exists((UniqueComfort u) => u.Name == local))
						{
							comfortSnapshot.Uniques.Add(new UniqueComfort(local, comfort));
							comfortSnapshot.CountingPieces.Add(val2);
						}
					}
					else if (!comfortSnapshot.GroupMax.TryGetValue(comfortGroup, out value) || comfort > value)
					{
						comfortSnapshot.GroupMax[comfortGroup] = comfort;
						comfortSnapshot.GroupSource[comfortGroup] = local;
						dictionary[comfortGroup] = val2;
					}
				}
				else
				{
					AddIdentity(comfortSnapshot.inactiveKeys, val2);
				}
			}
			foreach (KeyValuePair<ComfortGroup, Piece> item in dictionary)
			{
				comfortSnapshot.CountingPieces.Add(item.Value);
			}
			cached = comfortSnapshot;
			cachedPos = position;
			cachedTime = time;
			cachedShelter = flag;
			return comfortSnapshot;
		}

		internal int CurrentFor(ComfortGroup group)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if ((int)group == 0)
			{
				return 0;
			}
			if (!GroupMax.TryGetValue(group, out var value))
			{
				return 0;
			}
			return value;
		}

		internal bool HasPlaced(Piece piece)
		{
			return Matches(placedKeys, piece);
		}

		internal bool HasActive(Piece piece)
		{
			return Matches(activeKeys, piece);
		}

		internal bool IsInactivePresent(Piece piece)
		{
			if (Matches(inactiveKeys, piece))
			{
				return !Matches(activeKeys, piece);
			}
			return false;
		}

		private static void AddIdentity(HashSet<string> keys, Piece piece)
		{
			if (!string.IsNullOrEmpty(piece.m_name))
			{
				keys.Add(piece.m_name);
			}
			string text = PrefabName(piece);
			if (!string.IsNullOrEmpty(text))
			{
				keys.Add(text);
			}
		}

		private static bool Matches(HashSet<string> keys, Piece piece)
		{
			if ((Object)(object)piece == (Object)null || keys.Count == 0)
			{
				return false;
			}
			if (!string.IsNullOrEmpty(piece.m_name) && keys.Contains(piece.m_name))
			{
				return true;
			}
			string text = PrefabName(piece);
			if (!string.IsNullOrEmpty(text))
			{
				return keys.Contains(text);
			}
			return false;
		}

		private static string PrefabName(Piece piece)
		{
			if (!((Object)(object)((Component)piece).gameObject != (Object)null))
			{
				return string.Empty;
			}
			return Utils.GetPrefabName(((Component)piece).gameObject);
		}
	}
	internal readonly struct UniqueComfort
	{
		internal string Name { get; }

		internal int Comfort { get; }

		internal UniqueComfort(string name, int comfort)
		{
			Name = name;
			Comfort = comfort;
		}
	}
	internal readonly struct DiscoveredPiece
	{
		internal Piece Piece { get; }

		internal PieceCategory Category { get; }

		internal DiscoveredPiece(Piece piece, PieceCategory category)
		{
			//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)
			Piece = piece;
			Category = category;
		}
	}
	internal static class PieceDiscovery
	{
		private static readonly FieldInfo BuildPiecesField = AccessTools.Field(typeof(Player), "m_buildPieces");

		private static readonly FieldInfo AvailableByCategoryField = AccessTools.Field(typeof(PieceTable), "m_availablePiecesByCategory");

		private static readonly UsageTagFlags[] UsageFlags = (UsageTagFlags[])Enum.GetValues(typeof(UsageTagFlags));

		internal static PieceTable GetTable(Player player)
		{
			if (!((Object)(object)player == (Object)null))
			{
				object? obj = BuildPiecesField?.GetValue(player);
				return (PieceTable)((obj is PieceTable) ? obj : null);
			}
			return null;
		}

		internal static List<DiscoveredPiece> AllComfort(Player player)
		{
			List<DiscoveredPiece> list = new List<DiscoveredPiece>();
			PieceTable table = GetTable(player);
			if ((Object)(object)table == (Object)null)
			{
				return list;
			}
			HashSet<Piece> seen = new HashSet<Piece>();
			if (table.m_availablePieces != null)
			{
				foreach (Piece availablePiece in table.m_availablePieces)
				{
					AddComfort(list, seen, availablePiece);
				}
			}
			if (list.Count == 0)
			{
				AddFromCategoryLists(list, seen, table);
			}
			return list;
		}

		internal static int BestUpgradeComfort(Player player, ComfortGroup group, ComfortSnapshot snapshot)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			List<DiscoveredPiece> list = AllComfort(player);
			for (int i = 0; i < list.Count; i++)
			{
				Piece piece = list[i].Piece;
				if (piece.m_comfortGroup == group && UpgradeClassifier.Classify(piece, snapshot).IsUpgrade && piece.m_comfort > num)
				{
					num = piece.m_comfort;
				}
			}
			return num;
		}

		internal static string CategoryLabel(PieceTable table, PieceCategory category)
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)table != (Object)null && table.m_categories != null && table.m_categoryLabels != null)
			{
				int num = table.m_categories.IndexOf(category);
				if (num >= 0 && num < table.m_categoryLabels.Count)
				{
					string text = table.m_categoryLabels[num];
					if (!string.IsNullOrEmpty(text) && Localization.instance != null)
					{
						return Localization.instance.Localize(text);
					}
					if (!string.IsNullOrEmpty(text))
					{
						return text;
					}
				}
			}
			return CategoryFallback(category);
		}

		internal static string AppearanceSummary(Piece piece, PieceTable table)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)piece == (Object)null)
			{
				return string.Empty;
			}
			List<string> list = new List<string>();
			AddUnique(list, CategoryLabel(table, piece.m_category));
			UsageTagFlags usage = piece.m_usage;
			for (int i = 0; i < UsageFlags.Length; i++)
			{
				UsageTagFlags val = UsageFlags[i];
				if ((int)val != 0 && (UsageTagFlags)(usage & val) == val)
				{
					AddUnique(list, UsageLabel(val));
				}
			}
			return string.Join(", ", list);
		}

		internal static string LocalName(Piece piece)
		{
			if ((Object)(object)piece == (Object)null || string.IsNullOrEmpty(piece.m_name))
			{
				return string.Empty;
			}
			if (Localization.instance == null)
			{
				return piece.m_name;
			}
			return Localization.instance.Localize(piece.m_name);
		}

		internal unsafe static string GroupLabel(ComfortGroup group)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected I4, but got Unknown
			string key;
			switch ((int)group)
			{
			case 1:
				key = "comfortme_group_fire";
				break;
			case 2:
				key = "comfortme_group_bed";
				break;
			case 3:
				key = "comfortme_group_banner";
				break;
			case 4:
				key = "comfortme_group_chair";
				break;
			case 5:
				key = "comfortme_group_table";
				break;
			case 6:
				key = "comfortme_group_carpet";
				break;
			case 7:
				key = "comfortme_group_display";
				break;
			case 8:
				key = "comfortme_group_decor";
				break;
			case 9:
				key = "comfortme_group_garland";
				break;
			case 10:
				key = "comfortme_group_lantern";
				break;
			case 11:
				key = "comfortme_group_leisure";
				break;
			case 0:
				key = "comfortme_group_none";
				break;
			default:
				return ((object)(*(ComfortGroup*)(&group))/*cast due to .constrained prefix*/).ToString();
			}
			return ModLocalization.T(key);
		}

		private static void AddFromCategoryLists(List<DiscoveredPiece> result, HashSet<Piece> seen, PieceTable table)
		{
			if (!(AvailableByCategoryField?.GetValue(table) is IList list))
			{
				return;
			}
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i] is IList list2)
				{
					for (int j = 0; j < list2.Count; j++)
					{
						object? obj = list2[j];
						AddComfort(result, seen, (Piece)((obj is Piece) ? obj : null));
					}
				}
			}
		}

		private static void AddComfort(List<DiscoveredPiece> result, HashSet<Piece> seen, Piece piece)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)piece == (Object)null) && piece.m_comfort > 0 && seen.Add(piece))
			{
				result.Add(new DiscoveredPiece(piece, piece.m_category));
			}
		}

		private static void AddUnique(List<string> parts, string label)
		{
			if (string.IsNullOrEmpty(label))
			{
				return;
			}
			for (int i = 0; i < parts.Count; i++)
			{
				if (string.Equals(parts[i], label, StringComparison.OrdinalIgnoreCase))
				{
					return;
				}
			}
			parts.Add(label);
		}

		private unsafe static string UsageLabel(UsageTagFlags flag)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			string text = Utils.GetAttributeOfType<DisplayNameAttribute>((Enum)(object)flag)?.DisplayName;
			if (!string.IsNullOrEmpty(text) && Localization.instance != null)
			{
				return Localization.instance.Localize(text);
			}
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return ((object)(*(UsageTagFlags*)(&flag))/*cast due to .constrained prefix*/).ToString();
		}

		private unsafe static string CategoryFallback(PieceCategory category)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected I4, but got Unknown
			return (int)category switch
			{
				0 => "Misc", 
				1 => "Crafting", 
				2 => "Building", 
				3 => "Stonecutter", 
				4 => "Furniture", 
				5 => "Deep North", 
				6 => "Feasts", 
				7 => "Food", 
				8 => "Meads", 
				_ => ((object)(*(PieceCategory*)(&category))/*cast due to .constrained prefix*/).ToString(), 
			};
		}
	}
	internal static class UpgradeClassifier
	{
		internal static ComfortClassification Classify(Piece piece, ComfortSnapshot snapshot)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: 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_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)piece == (Object)null || snapshot == null || piece.m_comfort <= 0)
			{
				return new ComfortClassification(ComfortVerdict.Skip, (ComfortGroup)0, 0, 0);
			}
			ComfortGroup comfortGroup = piece.m_comfortGroup;
			int comfort = piece.m_comfort;
			if ((int)comfortGroup == 0)
			{
				if (snapshot.HasActive(piece))
				{
					return new ComfortClassification(ComfortVerdict.Redundant, comfortGroup, comfort, comfort);
				}
				if (snapshot.HasPlaced(piece))
				{
					return new ComfortClassification(ComfortVerdict.Unlit, comfortGroup, 0, comfort);
				}
				return new ComfortClassification(ComfortVerdict.NewGroup, comfortGroup, 0, comfort);
			}
			int num = snapshot.CurrentFor(comfortGroup);
			if (comfort > num)
			{
				if (snapshot.IsInactivePresent(piece))
				{
					return new ComfortClassification(ComfortVerdict.Unlit, comfortGroup, num, comfort);
				}
				return new ComfortClassification((num == 0) ? ComfortVerdict.NewGroup : ComfortVerdict.Upgrade, comfortGroup, num, comfort);
			}
			if (comfort == num)
			{
				return new ComfortClassification(ComfortVerdict.Redundant, comfortGroup, num, comfort);
			}
			return new ComfortClassification(ComfortVerdict.Downgrade, comfortGroup, num, comfort);
		}

		internal static bool ShouldBadge(Player player, Piece piece, ComfortClassification classification, ComfortSnapshot snapshot)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if (!classification.IsUpgrade)
			{
				return false;
			}
			if (ModConfig.RequireMaterials.Value && (Object)(object)player != (Object)null && !player.HaveRequirements(piece, (RequirementMode)0))
			{
				return false;
			}
			HighlightPolicy value = ModConfig.Policy.Value;
			if (value == HighlightPolicy.UpgradeOnly && classification.Verdict == ComfortVerdict.NewGroup)
			{
				return false;
			}
			if (value == HighlightPolicy.BestAvailable && (int)classification.Group != 0 && (Object)(object)player != (Object)null)
			{
				int num = PieceDiscovery.BestUpgradeComfort(player, classification.Group, snapshot);
				if (num > 0)
				{
					return piece.m_comfort >= num;
				}
				return false;
			}
			return true;
		}
	}
	internal static class BuildHudBadge
	{
		private const string BadgeName = "ComfortMeBadge";

		private static readonly Color ChipBackground = new Color(0.05f, 0.07f, 0.06f, 0.88f);

		private static readonly Color UpgradeGreen = new Color(0.35f, 0.85f, 0.55f, 1f);

		private static readonly Color EqualGrey = new Color(0.72f, 0.72f, 0.72f, 1f);

		private static readonly Color LowerRed = new Color(0.85f, 0.36f, 0.36f, 1f);

		private static readonly Color UnlitAmber = new Color(0.95f, 0.72f, 0.28f, 1f);

		private static readonly FieldInfo PieceIconsField = AccessTools.Field(typeof(Hud), "m_pieceIcons");

		private static readonly FieldInfo IconGoField = AccessTools.Field(AccessTools.Inner(typeof(Hud), "PieceIconData"), "m_go");

		private static readonly FieldInfo BuildUiButtonsField = AccessTools.Field(typeof(BuildUi), "m_pieceButtons");

		private static readonly FieldInfo BuildUiSpecialField = AccessTools.Field(typeof(BuildUi), "m_specialPieceButton");

		private static readonly FieldInfo PieceButtonIconField = AccessTools.Field(typeof(BuildUiPieceButton), "m_icon");

		private static bool loggedBadgeSweep;

		internal static void Clear()
		{
		}

		internal static void Refresh(Hud hud, ComfortSnapshot snapshot)
		{
			if ((Object)(object)hud == (Object)null || snapshot == null || !ModConfig.ShowValueBadge.Value || !HudUi.BuildMenuOpen(hud))
			{
				HideAll(hud);
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				HideAll(hud);
				return;
			}
			TMP_FontAsset font = HudUi.FontFrom(hud);
			Material fontMaterial = HudUi.FontMaterialFrom(hud);
			RefreshLegacyGrid(hud, snapshot, localPlayer, font, fontMaterial);
			RefreshBuildUi(hud, snapshot, localPlayer, font, fontMaterial);
		}

		internal static void OnPieceButtonsRebuilt(Hud hud, ComfortSnapshot snapshot)
		{
			if ((Object)(object)hud != (Object)null && (Object)(object)hud.m_buildUi != (Object)null)
			{
				CleanupStrayBadges(hud.m_buildUi);
			}
			Refresh(hud, snapshot);
		}

		internal static void ApplyToButton(BuildUiPieceButton button, ComfortSnapshot snapshot)
		{
			if (!((Object)(object)button == (Object)null))
			{
				Hud instance = Hud.instance;
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)instance == (Object)null || snapshot == null || (Object)(object)localPlayer == (Object)null || !ModConfig.ShowValueBadge.Value)
				{
					HideOn(BadgeHost(button));
					HideOn(((Component)button).gameObject);
				}
				else
				{
					Apply(BadgeHost(button), button.Piece, snapshot, localPlayer, HudUi.FontFrom(instance), HudUi.FontMaterialFrom(instance));
				}
			}
		}

		internal static void ApplyToImage(Image image, Piece piece, ComfortSnapshot snapshot)
		{
			Hud instance = Hud.instance;
			Player localPlayer = Player.m_localPlayer;
			ApplyToImage(image, piece, snapshot, localPlayer, HudUi.FontFrom(instance), HudUi.FontMaterialFrom(instance));
		}

		private static void RefreshBuildUi(Hud hud, ComfortSnapshot snapshot, Player player, TMP_FontAsset font, Material fontMaterial)
		{
			if ((Object)(object)hud.m_buildUi == (Object)null)
			{
				return;
			}
			if (BuildUiButtonsField?.GetValue(hud.m_buildUi) is IList list)
			{
				int num = 0;
				for (int i = 0; i < list.Count; i++)
				{
					object? obj = list[i];
					BuildUiPieceButton val = (BuildUiPieceButton)((obj is BuildUiPieceButton) ? obj : null);
					if (val != null && (Object)(object)val != (Object)null)
					{
						if ((Object)(object)val.Piece != (Object)null && val.Piece.m_comfort > 0)
						{
							num++;
						}
						Apply(BadgeHost(val), val.Piece, snapshot, player, font, fontMaterial);
					}
				}
				if (!loggedBadgeSweep)
				{
					loggedBadgeSweep = true;
					ManualLogSource log = ComfortMePlugin.Log;
					if (log != null)
					{
						log.LogInfo((object)$"BuildUi chips: {list.Count} icons, {num} comfort pieces.");
					}
				}
			}
			object? obj2 = BuildUiSpecialField?.GetValue(hud.m_buildUi);
			BuildUiPieceButton val2 = (BuildUiPieceButton)((obj2 is BuildUiPieceButton) ? obj2 : null);
			if (val2 != null && (Object)(object)val2 != (Object)null)
			{
				Apply(BadgeHost(val2), val2.Piece, snapshot, player, font, fontMaterial);
			}
		}

		private static void RefreshLegacyGrid(Hud hud, ComfortSnapshot snapshot, Player player, TMP_FontAsset font, Material fontMaterial)
		{
			if ((Object)(object)hud.m_pieceListRoot != (Object)null && !((Component)hud.m_pieceListRoot).gameObject.activeInHierarchy)
			{
				return;
			}
			List<Piece> buildPieces = player.GetBuildPieces();
			List<GameObject> iconObjects = GetIconObjects(hud);
			if (buildPieces == null || iconObjects.Count == 0)
			{
				return;
			}
			int num = Mathf.Min(buildPieces.Count, iconObjects.Count);
			for (int i = 0; i < iconObjects.Count; i++)
			{
				GameObject val = iconObjects[i];
				if (!((Object)(object)val == (Object)null))
				{
					if (i >= num)
					{
						HideOn(val);
					}
					else
					{
						Apply(val, buildPieces[i], snapshot, player, font, fontMaterial);
					}
				}
			}
		}

		private static void ApplyToImage(Image image, Piece piece, ComfortSnapshot snapshot, Player player, TMP_FontAsset font, Material fontMaterial)
		{
			if (!((Object)(object)image == (Object)null))
			{
				Apply(((Component)image).gameObject, piece, snapshot, player, font, fontMaterial);
			}
		}

		private static void Apply(GameObject iconGo, Piece piece, ComfortSnapshot snapshot, Player player, TMP_FontAsset font, Material fontMaterial)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)iconGo == (Object)null)
			{
				return;
			}
			ComfortClassification classification = UpgradeClassifier.Classify(piece, snapshot);
			bool flag;
			Color color;
			if (!ModConfig.ShowValueBadge.Value || classification.Verdict == ComfortVerdict.Skip)
			{
				flag = false;
				color = UpgradeGreen;
			}
			else if (ModConfig.BadgeUpgradesOnly.Value)
			{
				flag = UpgradeClassifier.ShouldBadge(player, piece, classification, snapshot);
				color = UpgradeGreen;
			}
			else
			{
				flag = true;
				if (ModConfig.RequireMaterials.Value && (Object)(object)player != (Object)null && !player.HaveRequirements(piece, (RequirementMode)0))
				{
					flag = false;
				}
				if (ModConfig.Policy.Value == HighlightPolicy.UpgradeOnly && classification.Verdict == ComfortVerdict.NewGroup)
				{
					flag = false;
				}
				color = ColorFor(classification.Verdict);
			}
			string label = ((flag && (Object)(object)piece != (Object)null) ? ("+" + piece.m_comfort) : string.Empty);
			SetBadge(iconGo, font, fontMaterial, flag, label, color);
		}

		private static Color ColorFor(ComfortVerdict verdict)
		{
			//IL_001e: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			switch (verdict)
			{
			case ComfortVerdict.NewGroup:
			case ComfortVerdict.Upgrade:
				return UpgradeGreen;
			case ComfortVerdict.Downgrade:
				return LowerRed;
			case ComfortVerdict.Unlit:
				return UnlitAmber;
			default:
				return EqualGrey;
			}
		}

		private static void HideAll(Hud hud)
		{
			if ((Object)(object)hud == (Object)null)
			{
				return;
			}
			List<GameObject> iconObjects = GetIconObjects(hud);
			for (int i = 0; i < iconObjects.Count; i++)
			{
				HideOn(iconObjects[i]);
			}
			if ((Object)(object)hud.m_buildUi != (Object)null && BuildUiButtonsField?.GetValue(hud.m_buildUi) is IList list)
			{
				for (int j = 0; j < list.Count; j++)
				{
					object? obj = list[j];
					BuildUiPieceButton val = (BuildUiPieceButton)((obj is BuildUiPieceButton) ? obj : null);
					if (val != null && (Object)(object)val != (Object)null)
					{
						HideOn(BadgeHost(val));
						HideOn(((Component)val).gameObject);
					}
				}
			}
			if ((Object)(object)hud.m_buildUi != (Object)null)
			{
				object? obj2 = BuildUiSpecialField?.GetValue(hud.m_buildUi);
				BuildUiPieceButton val2 = (BuildUiPieceButton)((obj2 is BuildUiPieceButton) ? obj2 : null);
				if (val2 != null && (Object)(object)val2 != (Object)null)
				{
					HideOn(BadgeHost(val2));
					HideOn(((Component)val2).gameObject);
				}
			}
			if ((Object)(object)hud.m_buildIcon != (Object)null)
			{
				HideOn(((Component)hud.m_buildIcon).gameObject);
			}
		}

		private static GameObject BadgeHost(BuildUiPieceButton button)
		{
			if ((Object)(object)button == (Object)null)
			{
				return null;
			}
			object? obj = PieceButtonIconField?.GetValue(button);
			Image val = (Image)((obj is Image) ? obj : null);
			if (val != null && (Object)(object)val != (Object)null)
			{
				return ((Component)val).gameObject;
			}
			return ((Component)button).gameObject;
		}

		private static void HideOn(GameObject go)
		{
			Transform val = FindBadge(((Object)(object)go != (Object)null) ? go.transform : null);
			if ((Object)(object)val != (Object)null)
			{
				((Component)val).gameObject.SetActive(false);
			}
		}

		private static Transform FindBadge(Transform root)
		{
			if ((Object)(object)root == (Object)null)
			{
				return null;
			}
			Transform val = root.Find("ComfortMeBadge");
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			for (int i = 0; i < root.childCount; i++)
			{
				Transform val2 = FindBadge(root.GetChild(i));
				if ((Object)(object)val2 != (Object)null)
				{
					return val2;
				}
			}
			return null;
		}

		private static List<GameObject> GetIconObjects(Hud hud)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			List<GameObject> list = new List<GameObject>();
			if (PieceIconsField?.GetValue(hud) is IList list2 && IconGoField != null)
			{
				for (int i = 0; i < list2.Count; i++)
				{
					object? value = IconGoField.GetValue(list2[i]);
					GameObject val = (GameObject)((value is GameObject) ? value : null);
					if (val != null)
					{
						list.Add(val);
					}
				}
				return list;
			}
			if ((Object)(object)hud != (Object)null && (Object)(object)hud.m_pieceListRoot != (Object)null)
			{
				foreach (Transform item in (Transform)hud.m_pieceListRoot)
				{
					Transform val2 = item;
					if ((Object)(object)val2 != (Object)null && ((Object)val2).name != "ComfortMeBadge")
					{
						list.Add(((Component)val2).gameObject);
					}
				}
			}
			return list;
		}

		private static void CleanupStrayBadges(BuildUi ui)
		{
			if ((Object)(object)ui == (Object)null)
			{
				return;
			}
			Transform[] componentsInChildren = ((Component)ui).GetComponentsInChildren<Transform>(true);
			foreach (Transform val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null || ((Object)val).name != "ComfortMeBadge")
				{
					continue;
				}
				if ((Object)(object)((Component)val).GetComponent<Canvas>() != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)((Component)val).gameObject);
					continue;
				}
				BuildUiPieceButton componentInParent = ((Component)val).GetComponentInParent<BuildUiPieceButton>();
				GameObject val2 = (((Object)(object)componentInParent != (Object)null) ? BadgeHost(componentInParent) : null);
				if ((Object)(object)val2 == (Object)null || (Object)(object)val.parent != (Object)(object)val2.transform)
				{
					Object.DestroyImmediate((Object)(object)((Component)val).gameObject);
				}
			}
		}

		private static void SetBadge(GameObject iconGo, TMP_FontAsset font, Material fontMaterial, bool visible, string label, Color color)
		{
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			Transform val = FindBadge(iconGo.transform);
			if ((Object)(object)val != (Object)null && ((Object)(object)((Component)val).GetComponent<Canvas>() != (Object)null || (Object)(object)val.parent != (Object)(object)iconGo.transform))
			{
				Object.DestroyImmediate((Object)(object)((Component)val).gameObject);
				val = null;
			}
			if (!visible)
			{
				if ((Object)(object)val != (Object)null)
				{
					((Component)val).gameObject.SetActive(false);
				}
				return;
			}
			Image val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<Image>() : CreateBadge(iconGo, font, fontMaterial));
			if (!((Object)(object)val2 == (Object)null))
			{
				((MaskableGraphic)val2).maskable = true;
				((Component)val2).gameObject.SetActive(true);
				((Component)val2).transform.SetAsLastSibling();
				TextMeshProUGUI componentInChildren = ((Component)val2).GetComponentInChildren<TextMeshProUGUI>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					HudUi.ApplyVanillaFont((TMP_Text)(object)componentInChildren, Hud.instance);
					((TMP_Text)componentInChildren).text = label;
					((Graphic)componentInChildren).color = color;
					((MaskableGraphic)componentInChildren).maskable = true;
				}
			}
		}

		private static Image CreateBadge(GameObject iconGo, TMP_FontAsset font, Material fontMaterial)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			Image obj = HudUi.CreatePanel(iconGo.transform, "ComfortMeBadge", ChipBackground);
			RectTransform rectTransform = ((Graphic)obj).rectTransform;
			rectTransform.anchorMin = new Vector2(1f, 0f);
			rectTransform.anchorMax = new Vector2(1f, 0f);
			rectTransform.pivot = new Vector2(1f, 0f);
			rectTransform.sizeDelta = new Vector2(28f, 16f);
			rectTransform.anchoredPosition = new Vector2(-2f, 2f);
			((Transform)rectTransform).SetAsLastSibling();
			TextMeshProUGUI obj2 = HudUi.CreateText(((Component)obj).transform, "Text", font, fontMaterial, 11f);
			((TMP_Text)obj2).alignment = (TextAlignmentOptions)514;
			((Graphic)obj2).color = UpgradeGreen;
			RectTransform rectTransform2 = ((TMP_Text)obj2).rectTransform;
			rectTransform2.anchorMin = Vector2.zero;
			rectTransform2.anchorMax = Vector2.one;
			rectTransform2.offsetMin = Vector2.zero;
			rectTransform2.offsetMax = Vector2.zero;
			return obj;
		}
	}
	internal static class ComfortHudCursor
	{
		internal static bool ShouldFreeMouse()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.Enabled.Value)
			{
				return false;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !((Character)localPlayer).InPlaceMode())
			{
				return false;
			}
			if (Hud.IsPieceSelectionVisible())
			{
				return false;
			}
			KeyboardShortcut value = ModConfig.HudCursorKey.Value;
			if ((int)((KeyboardShortcut)(ref value)).MainKey != 0)
			{
				return ((KeyboardShortcut)(ref value)).IsPressed();
			}
			return false;
		}

		internal static void FreeCursor()
		{
			ZCursor.LockState = (CursorLockMode)0;
			ZCursor.Show();
		}
	}
	internal static class ComfortLinks
	{
		private static readonly FieldInfo StatusEffectsField = AccessTools.Field(typeof(Hud), "m_statusEffects");

		private static readonly FieldInfo PlacementGhostField = AccessTools.Field(typeof(Player), "m_placementGhost");

		private static readonly List<GameObject> beams = new List<GameObject>();

		private static readonly List<RectTransform> restedIcons = new List<RectTransform>();

		private static GameObject prefab;

		private static Transform beamRoot;

		private static bool loggedPrefab;

		private static bool loggedMissing;

		private static int suppressFrame = -1;

		private static bool suppressCached;

		internal static void Clear()
		{
			Hide();
			restedIcons.Clear();
			prefab = null;
			loggedPrefab = false;
			loggedMissing = false;
			suppressFrame = -1;
			if ((Object)(object)beamRoot != (Object)null && Object.op_Implicit((Object)(object)beamRoot))
			{
				Object.Destroy((Object)(object)((Component)beamRoot).gameObject);
			}
			beamRoot = null;
		}

		internal static void BindRestedIcons(Hud hud, List<StatusEffect> effects)
		{
			restedIcons.Clear();
			if ((Object)(object)hud == (Object)null || effects == null || !(StatusEffectsField?.GetValue(hud) is List<RectTransform> list))
			{
				return;
			}
			int num = Mathf.Min(list.Count, effects.Count);
			for (int i = 0; i < num; i++)
			{
				if (effects[i] is SE_Rested)
				{
					RectTransform val = list[i];
					if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy)
					{
						restedIcons.Add(val);
					}
				}
			}
		}

		internal static bool SuppressVanillaStationLines()
		{
			int frameCount = Time.frameCount;
			if (frameCount == suppressFrame)
			{
				return suppressCached;
			}
			suppressFrame = frameCount;
			suppressCached = ComputeSuppressVanillaStationLines();
			return suppressCached;
		}

		internal static void Tick(Hud hud)
		{
			if (!ModConfig.Enabled.Value || !ModConfig.ShowComfortLinks.Value)
			{
				Hide();
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				Hide();
				return;
			}
			Piece placementGhostPiece = GetPlacementGhostPiece(localPlayer);
			bool flag = ShouldDrawGhostLine(localPlayer, placementGhostPiece);
			bool flag2 = GroupCatalogPanel.IsHovered() || IsRestedIconHovered();
			if (!flag2 && !flag)
			{
				Hide();
				return;
			}
			ComfortSnapshot snapshot = (flag2 ? ComfortSnapshot.Capture(localPlayer) : null);
			Show(localPlayer, snapshot, flag ? placementGhostPiece : null);
		}

		internal static void Hide()
		{
			for (int i = 0; i < beams.Count; i++)
			{
				GameObject val = beams[i];
				if ((Object)(object)val != (Object)null)
				{
					val.SetActive(false);
				}
			}
		}

		private static bool ComputeSuppressVanillaStationLines()
		{
			if (!ModConfig.Enabled.Value || !ModConfig.ShowComfortLinks.Value)
			{
				return false;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return false;
			}
			Piece placementGhostPiece = GetPlacementGhostPiece(localPlayer);
			if (IsStationExtension(placementGhostPiece))
			{
				return false;
			}
			if (ShouldDrawGhostLine(localPlayer, placementGhostPiece))
			{
				return true;
			}
			if (!GroupCatalogPanel.IsHovered())
			{
				return IsRestedIconHovered();
			}
			return true;
		}

		private static bool IsRestedIconHovered()
		{
			//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)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			Vector2 screen = Vector2.op_Implicit(Input.mousePosition);
			for (int i = 0; i < restedIcons.Count; i++)
			{
				RectTransform val = restedIcons[i];
				if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy && ContainsScreenPoint(val, screen))
				{
					return true;
				}
			}
			return false;
		}

		private static bool ContainsScreenPoint(RectTransform rt, Vector2 screen)
		{
			//IL_0011: 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)
			Canvas componentInParent = ((Component)rt).GetComponentInParent<Canvas>();
			Camera val = (((Object)(object)componentInParent != (Object)null && (int)componentInParent.renderMode != 0) ? componentInParent.worldCamera : null);
			return RectTransformUtility.RectangleContainsScreenPoint(rt, screen, val);
		}

		private static void Show(Player player, ComfortSnapshot snapshot, Piece ghost)
		{
			//IL_000e: 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_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: 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_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			if (!ResolvePrefab())
			{
				return;
			}
			Vector3 to = ((Component)player).transform.position + Vector3.up * 1.1f;
			int num = 0;
			if (snapshot != null)
			{
				List<Piece> countingPieces = snapshot.CountingPieces;
				for (int i = 0; i < countingPieces.Count; i++)
				{
					Piece val = countingPieces[i];
					if (IsComfortLinkSource(val))
					{
						num = PlaceBeam(num, ((Component)val).transform.position + Vector3.up * 0.4f, to);
					}
				}
			}
			if (IsComfortLinkSource(ghost))
			{
				num = PlaceBeam(num, ((Component)ghost).transform.position + Vector3.up * 0.4f, to);
			}
			for (int j = num; j < beams.Count; j++)
			{
				if ((Object)(object)beams[j] != (Object)null)
				{
					beams[j].SetActive(false);
				}
			}
		}

		private static bool IsComfortLinkSource(Piece piece)
		{
			if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).gameObject == (Object)null || !((Component)piece).gameObject.activeInHierarchy)
			{
				return false;
			}
			if (piece.m_comfort <= 0)
			{
				return false;
			}
			return !IsStationPiece(piece);
		}

		private static bool IsStationPiece(Piece piece)
		{
			if ((Object)(object)piece == (Object)null)
			{
				return false;
			}
			return (Object)(object)((Component)piece).GetComponent<StationExtension>() != (Object)null;
		}

		private static bool IsStationExtension(Piece piece)
		{
			if ((Object)(object)piece != (Object)null)
			{
				return (Object)(object)((Component)piece).GetComponent<StationExtension>() != (Object)null;
			}
			return false;
		}

		private static int PlaceBeam(int used, Vector3 from, Vector3 to)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//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_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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_0035: 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)
			Vector3 val = to - from;
			float magnitude = ((Vector3)(ref val)).magnitude;
			if (magnitude < 0.05f)
			{
				return used;
			}
			GameObject beam = GetBeam(used);
			Transform transform = beam.transform;
			transform.position = from;
			transform.rotation = Quaternion.LookRotation(val / magnitude);
			transform.localScale = new Vector3(1f, 1f, magnitude);
			beam.SetActive(true);
			PlayBeam(beam);
			return used + 1;
		}

		private static Piece GetPlacementGhostPiece(Player player)
		{
			if ((Object)(object)player == (Object)null || PlacementGhostField == null)
			{
				return null;
			}
			object value = PlacementGhostField.GetValue(player);
			Piece val = (Piece)((value is Piece) ? value : null);
			if (val != null)
			{
				if (!((Object)(object)((Component)val).gameObject != (Object)null) || !((Component)val).gameObject.activeInHierarchy)
				{
					return null;
				}
				return val;
			}
			GameObject val2 = (GameObject)((value is GameObject) ? value : null);
			if ((Object)(object)val2 == (Object)null || !val2.activeInHierarchy)
			{
				return null;
			}
			return val2.GetComponent<Piece>() ?? val2.GetComponentInChildren<Piece>();
		}

		private static bool ShouldDrawGhostLine(Player player, Piece ghost)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || !IsComfortLinkSource(ghost))
			{
				return false;
			}
			return Vector3.Distance(((Component)ghost).transform.position, ((Component)player).transform.position) <= 10f;
		}

		private static GameObject GetBeam(int index)
		{
			while (index >= beams.Count)
			{
				beams.Add(null);
			}
			GameObject val = beams[index];
			if ((Object)(object)val == (Object)null)
			{
				EnsureBeamRoot();
				val = Object.Instantiate<GameObject>(prefab);
				((Object)val).name = "ComfortMeLink";
				val.transform.SetParent(beamRoot, true);
				StripStationScripts(val);
				PrepareBeamVfx(val);
				beams[index] = val;
			}
			return val;
		}

		private static void EnsureBeamRoot()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			if (!((Object)(object)beamRoot != (Object)null) || !Object.op_Implicit((Object)(object)beamRoot))
			{
				GameObject val = new GameObject("ComfortMeLinks");
				Object.DontDestroyOnLoad((Object)val);
				beamRoot = val.transform;
			}
		}

		private static void StripStationScripts(GameObject beam)
		{
			StationExtension[] componentsInChildren = beam.GetComponentsInChildren<StationExtension>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				Object.Destroy((Object)(object)componentsInChildren[i]);
			}
			CraftingStation[] componentsInChildren2 = beam.GetComponentsInChildren<CraftingStation>(true);
			for (int j = 0; j < componentsInChildren2.Length; j++)
			{
				Object.Destroy((Object)(object)componentsInChildren2[j]);
			}
			Component[] componentsInChildren3 = beam.GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren3)
			{
				if ((Object)(object)val != (Object)null && ((object)val).GetType().Name == "TimedDestruction")
				{
					Object.Destroy((Object)(object)val);
				}
			}
		}

		private static void PrepareBeamVfx(GameObject beam)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			ParticleSystem[] componentsInChildren = beam.GetComponentsInChildren<ParticleSystem>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				MainModule main = componentsInChildren[i].main;
				((MainModule)(ref main)).loop = true;
				((MainModule)(ref main)).playOnAwake = true;
			}
		}

		private static void PlayBeam(GameObject beam)
		{
			ParticleSystem[] componentsInChildren = beam.GetComponentsInChildren<ParticleSystem>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				if (!componentsInChildren[i].isPlaying)
				{
					componentsInChildren[i].Play(true);
				}
			}
		}

		internal static string Describe()
		{
			Piece placementGhostPiece = GetPlacementGhostPiece(Player.m_localPlayer);
			int num = 0;
			for (int i = 0; i < beams.Count; i++)
			{
				if ((Object)(object)beams[i] != (Object)null && beams[i].activeSelf)
				{
					num++;
				}
			}
			string text = (((Object)(object)placementGhostPiece != (Object)null) ? (PieceDiscovery.LocalName(placementGhostPiece) + " +" + placementGhostPiece.m_comfort) : "none");
			return "ComfortMe links: prefab=" + (((Object)(object)prefab != (Object)null) ? ((Object)prefab).name : "null") + " beams=" + num + "/" + beams.Count + " restedIcons=" + restedIcons.Count + " hoverCatalog=" + GroupCatalogPanel.IsHovered() + " hoverRested=" + IsRestedIconHovered() + " ghost=" + text + " suppressStation=" + SuppressVanillaStationLines();
		}

		private static bool ResolvePrefab()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)prefab != (Object)null)
			{
				return true;
			}
			GameObject val = null;
			int num = int.MinValue;
			StationExtension[] array = Resources.FindObjectsOfTypeAll<StationExtension>();
			foreach (StationExtension val2 in array)
			{
				if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.m_connectionPrefab == (Object)null))
				{
					GameObject connectionPrefab = val2.m_connectionPrefab;
					int num2 = 0;
					Scene scene = connectionPrefab.scene;
					if (!((Scene)(ref scene)).IsValid())
					{
						num2 += 10;
					}
					if (val2.m_continousConnection)
					{
						num2 += 50;
					}
					string name = ((Object)connectionPrefab).name;
					if (name.IndexOf("mage", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						num2 += 40;
					}
					if (name.IndexOf("ExtensionConnection", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						num2 += 5;
					}
					if (num2 > num)
					{
						num = num2;
						val = connectionPrefab;
					}
				}
			}
			prefab = val;
			if ((Object)(object)prefab != (Object)null)
			{
				if (!loggedPrefab)
				{
					loggedPrefab = true;
					ManualLogSource log = ComfortMePlugin.Log;
					if (log != null)
					{
						log.LogInfo((object)$"Comfort links using '{((Object)prefab).name}' (score {num}).");
					}
				}
				return true;
			}
			if (!loggedMissing)
			{
				loggedMissing = true;
				ManualLogSource log2 = ComfortMePlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)"Comfort links: no station connection prefab found yet.");
				}
			}
			return false;
		}
	}
	internal static class ComfortUsageTag
	{
		private const int FirstId = -100;

		private const int ShowAllId = -1;

		private const int FavoriteUiId = -10;

		private static readonly FieldInfo AvailableTagsField = AccessTools.Field(typeof(ByUsagePieceList), "m_availableTags");

		private static readonly FieldInfo UsageTagsField = AccessTools.Field(typeof(ByUsagePieceList), "m_usageTags");

		private static readonly FieldInfo CurrentTagIdField = AccessTools.Field(typeof(BuildUi), "m_currentTagId");

		private static readonly MethodInfo SetCurrentTagMethod = AccessTools.Method(typeof(BuildUi), "SetCurrentTag", new Type[2]
		{
			typeof(int),
			typeof(bool)
		}, (Type[])null);

		private static int allocatedId;

		private static int previousId;

		private static bool loggedInsert;

		internal static string DisplayName => ModLocalization.T("comfortme_category");

		internal static void Clear()
		{
			allocatedId = 0;
			previousId = 0;
			loggedInsert = false;
		}

		internal static bool IsAllocated(int tagId)
		{
			if (tagId != 0)
			{
				if (tagId != allocatedId)
				{
					return tagId == previousId;
				}
				return true;
			}
			return false;
		}

		internal static bool IsComfortTabSelected()
		{
			if (!ModConfig.ShowComfortCategory.Value || allocatedId == 0)
			{
				return false;
			}
			Hud instance = Hud.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_buildUi == (Object)null || !((Component)instance.m_buildUi).gameObject.activeInHierarchy)
			{
				return false;
			}
			if (CurrentTagIdField?.GetValue(instance.m_buildUi) is int tagId)
			{
				return IsAllocated(tagId);
			}
			return false;
		}

		internal static bool IsComfortRow(ByUsagePieceList list, int index)
		{
			List<int> list2 = AvailableTags(list);
			if (list2 != null && index >= 0 && index < list2.Count)
			{
				return IsAllocated(list2[index]);
			}
			return false;
		}

		internal static void Inject(ByUsagePieceList list, PieceTable table)
		{
			if (!ModConfig.Enabled.Value || !ModConfig.ShowComfortCategory.Value)
			{
				allocatedId = 0;
				return;
			}
			List<int> list2 = AvailableTags(list);
			if (list2 == null || !HasComfortPiece(table))
			{
				allocatedId = 0;
				return;
			}
			int num = allocatedId;
			int num2 = ClaimId(list2);
			int num3 = InsertIndex(list, list2);
			list2.Insert(num3, num2);
			previousId = num;
			allocatedId = num2;
			if (!loggedInsert)
			{
				loggedInsert = true;
				ManualLogSource log = ComfortMePlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"Comfort category row id={num2} at list index {num3}.");
				}
			}
		}

		internal static void RetargetSelection()
		{
			BuildUi val = (((Object)(object)Hud.instance != (Object)null) ? Hud.instance.m_buildUi : null);
			if ((Object)(object)val == (Object)null || CurrentTagIdField == null || allocatedId == 0)
			{
				previousId = allocatedId;
				return;
			}
			if (CurrentTagIdField.GetValue(val) is int num && previousId != 0 && num == previousId && num != allocatedId)
			{
				SetCurrentTagMethod?.Invoke(val, new object[2] { allocatedId, false });
			}
			previousId = allocatedId;
		}

		internal static void Fill(PieceTable table, IList<Piece> resultOut)
		{
			if (resultOut == null)
			{
				return;
			}
			resultOut.Clear();
			if ((Object)(object)table == (Object)null || table.m_availablePieces == null)
			{
				return;
			}
			List<Piece> list = new List<Piece>();
			foreach (Piece availablePiece in table.m_availablePieces)
			{
				if (!((Object)(object)availablePiece == (Object)null) && availablePiece.m_comfort > 0 && !availablePiece.m_repairPiece && !availablePiece.m_removePiece)
				{
					list.Add(availablePiece);
				}
			}
			ComfortSnapshot snapshot = ComfortSnapshot.Capture(Player.m_localPlayer);
			list.Sort((Piece a, Piece b) => CompareComfort(a, b, snapshot));
			for (int num = 0; num < list.Count; num++)
			{
				resultOut.Add(list[num]);
			}
		}

		private static List<int> AvailableTags(ByUsagePieceList list)
		{
			if (list != null)
			{
				return AvailableTagsField?.GetValue(list) as List<int>;
			}
			return null;
		}

		private static int ClaimId(List<int> tags)
		{
			int num = -100;
			while (num == -1 || num == -10 || tags.Contains(num))
			{
				num--;
				if (num < -1000000)
				{
					break;
				}
			}
			return num;
		}

		private static int InsertIndex(ByUsagePieceList list, List<int> tags)
		{
			if (!(UsageTagsField?.GetValue(list) is UsageTagFlags[] array))
			{
				return tags.Count;
			}
			int num = -1;
			for (int i = 0; i < array.Length; i++)
			{
				if ((int)array[i] == 128)
				{
					num = i;
					break;
				}
			}
			if (num < 0)
			{
				return tags.Count;
			}
			int num2 = tags.IndexOf(num);
			if (num2 < 0)
			{
				return tags.Count;
			}
			return num2 + 1;
		}

		private static bool HasComfortPiece(PieceTable table)
		{
			if ((Object)(object)table == (Object)null || table.m_availablePieces == null)
			{
				return false;
			}
			foreach (Piece availablePiece in table.m_availablePieces)
			{
				if ((Object)(object)availablePiece != (Object)null && availablePiece.m_comfort > 0 && !availablePiece.m_repairPiece && !availablePiece.m_removePiece)
				{
					return true;
				}
			}
			return false;
		}

		private static int CompareComfort(Piece a, Piece b, ComfortSnapshot snapshot)
		{
			int num = ComfortClassification.ShopRank(UpgradeClassifier.Classify(a, snapshot).Verdict).CompareTo(ComfortClassification.ShopRank(UpgradeClassifier.Classify(b, snapshot).Verdict));
			if (num != 0)
			{
				return num;
			}
			int num2 = b.m_comfort.CompareTo(a.m_comfort);
			if (num2 != 0)
			{
				return num2;
			}
			return string.CompareOrdinal(PieceDiscovery.LocalName(a), PieceDiscovery.LocalName(b));
		}
	}
	internal static class GroupCatalogPanel
	{
		private const string PanelName = "ComfortMeCatalog";

		private const int OverlaySorting = 5000;

		private static readonly Color PanelBackground = new Color(0.04f, 0.05f, 0.05f, 0.92f);

		private static readonly Color HeaderColor = new Color(0.92f, 0.93f, 0.9f, 1f);

		private static Image panel;

		private static TextMeshProUGUI header;

		private static TextMeshProUGUI body;

		private static Canvas overlay;

		private static string lastText;

		private static bool loggedCreate;

		private static bool loggedPlace;

		internal static void Hide()
		{
			if ((Object)(object)panel != (Object)null)
			{
				((Component)panel).gameObject.SetActive(false);
			}
			if ((Object)(object)overlay != (Object)null && Object.op_Implicit((Object)(object)overlay))
			{
				((Component)overlay).gameObject.SetActive(false);
			}
			lastText = null;
		}

		internal static bool IsHovered()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)panel == (Object)null || !((Component)panel).gameObject.activeInHierarchy)
			{
				return false;
			}
			Canvas canvas = ((Graphic)panel).canvas;
			Camera val = (((Object)(object)canvas != (Object)null && (int)canvas.renderMode != 0) ? canvas.worldCamera : null);
			return RectTransformUtility.RectangleContainsScreenPoint(((Graphic)panel).rectTransform, Vector2.op_Implicit(Input.mousePosition), val);
		}

		internal static void Clear()
		{
			lastText = null;
			loggedCreate = false;
			loggedPlace = false;
			header = null;
			body = null;
			if ((Object)(object)panel != (Object)null && Object.op_Implicit((Object)(object)panel))
			{
				Object.Destroy((Object)(object)((Component)panel).gameObject);
			}
			panel = null;
			if ((Object)(object)overlay != (Object)null && Object.op_Implicit((Object)(object)overlay))
			{
				Object.Destroy((Object)(object)((Component)overlay).gameObject);
			}
			overlay = null;
		}

		internal static void InvalidateText()
		{
			lastText = null;
		}

		internal static void Dump()
		{
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: 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_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			string text;
			if ((Object)(object)panel == (Object)null || !Object.op_Implicit((Object)(object)panel))
			{
				text = "ComfortMe catalog: panel is null (hammer menu may be closed).";
			}
			else
			{
				RectTransform rectTransform = ((Graphic)panel).rectTransform;
				Transform parent = ((Component)panel).transform.parent;
				string text2 = (((Object)(object)body != (Object)null && !string.IsNullOrEmpty(((TMP_Text)body).text)) ? ((TMP_Text)body).text.Replace('\n', '|') : "");
				if (text2.Length > 160)
				{
					text2 = text2.Substring(0, 160);
				}
				string[] obj = new string[25]
				{
					"ComfortMe catalog: active=",
					((Component)panel).gameObject.activeInHierarchy.ToString(),
					" overlay=",
					((Object)(object)overlay != (Object)null && Object.op_Implicit((Object)(object)overlay)) ? overlay.sortingOrder.ToString() : "null",
					" parent=",
					((Object)(object)parent != (Object)null) ? ((Object)parent).name : "null",
					" anchors=",
					((object)rectTransform.anchorMin/*cast due to .constrained prefix*/).ToString(),
					"->",
					((object)rectTransform.anchorMax/*cast due to .constrained prefix*/).ToString(),
					" pos=",
					((object)rectTransform.anchoredPosition/*cast due to .constrained prefix*/).ToString(),
					" size=",
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null
				};
				Rect rect = rectTransform.rect;
				obj[13] = ((object)((Rect)(ref rect)).size/*cast due to .constrained prefix*/).ToString();
				obj[14] = " screen=";
				obj[15] = Screen.width.ToString();
				obj[16] = "x";
				obj[17] = Screen.height.ToString();
				obj[18] = " font=";
				obj[19] = (((Object)(object)header != (Object)null && (Object)(object)((TMP_Text)header).font != (Object)null) ? ((Object)((TMP_Text)header).font).name : "null");
				obj[20] = " header='";
				obj[21] = (((Object)(object)header != (Object)null) ? ((TMP_Text)header).text : "");
				obj[22] = "' body='";
				obj[23] = text2;
				obj[24] = "'";
				text = string.Concat(obj);
			}
			ManualLogSource log = ComfortMePlugin.Log;
			if (log != null)
			{
				log.LogInfo((object)text);
			}
			if ((Object)(object)Console.instance != (Object)null)
			{
				Console.instance.Print(text);
			}
			string text3 = ComfortLinks.Describe();
			ManualLogSource log2 = ComfortMePlugin.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)text3);
			}
			if ((Object)(object)Console.instance != (Object)null)
			{
				Console.instance.Print(text3);
			}
			ComfortSnapshot comfortSnapshot = ComfortSnapshot.Capture(Player.m_localPlayer);
			if (comfortSnapshot == null || comfortSnapshot.CountingPieces.Count == 0)
			{
				return;
			}
			List<string> list = new List<string>(comfortSnapshot.CountingPieces.Count);
			for (int i = 0; i < comfortSnapshot.CountingPieces.Count; i++)
			{
				Piece val = comfortSnapshot.CountingPieces[i];
				if (!((Object)(object)val == (Object)null))
				{
					list.Add(PieceDiscovery.LocalName(val));
				}
			}
			string text4 = "ComfortMe counting: " + string.Join(", ", list.ToArray());
			ManualLogSource log3 = ComfortMePlugin.Log;
			if (log3 != null)
			{
				log3.LogInfo((object)text4);
			}
			if ((Object)(object)Console.instance != (Object)null)
			{
				Console.instance.Print(text4);
			}
		}

		internal static void RefreshFromHud(Hud hud, ComfortSnapshot snapshot)
		{
			Refresh(hud, snapshot);
		}

		internal static void Refresh(Hud hud, ComfortSnapshot snapshot)
		{
			if ((Object)(object)hud == (Object)null || snapshot == null || !ModConfig.Enabled.Value || !ModConfig.ShowGroupCatalog.Value || !HudUi.HoldingPlaceTool())
			{
				Hide();
				return;
			}
			EnsurePanel(hud);
			if ((Object)(object)panel == (Object)null)
			{
				return;
			}
			if ((Object)(object)overlay != (Object)null && Object.op_Implicit((Object)(object)overlay))
			{
				((Component)overlay).gameObject.SetActive(true);
			}
			PlaceBesideBuildUi(hud);
			HudUi.ApplyVanillaFont((TMP_Text)(object)header, hud);
			HudUi.ApplyVanillaFont((TMP_Text)(object)body, hud);
			((Component)panel).gameObject.SetActive(true);
			ModLocalization.EnsureApplied();
			StringBuilder stringBuilder = new StringBuilder(256);
			stringBuilder.Append(ModLocalization.T("comfortme_this_room")).Append(": ").Append(snapshot.Total);
			if (!snapshot.InShelter)
			{
				stringBuilder.Append("  ").Append(ModLocalization.T("comfortme_unsheltered"));
			}
			stringBuilder.Append('\n');
			stringBuilder.Append(ModLocalization.T("comfortme_base")).Append("  +1");
			if (snapshot.InShelter)
			{
				stringBuilder.Append('\n').Append(ModLocalization.T("comfortme_shelter")).Append("  +1");
			}
			else
			{
				stringBuilder.Append('\n').Append(ModLocalization.T("comfortme_shelter")).Append("  ")
					.Append(ModLocalization.T("comfortme_missing"));
			}
			AppendCounting(stringBuilder, snapshot);
			string text = stringBuilder.ToString();
			if (!(text == lastText))
			{
				lastText = text;
				int num = text.IndexOf('\n');
				if ((Object)(object)header != (Object)null)
				{
					((TMP_Text)header).text = ((num >= 0) ? text.Substring(0, num) : text);
				}
				if ((Object)(object)body != (Object)null)
				{
					((TMP_Text)body).text = ((num >= 0) ? text.Substring(num + 1) : string.Empty);
					((Component)body).gameObject.SetActive(true);
				}
				LayoutRebuilder.ForceRebuildLayoutImmediate(((Graphic)panel).rectTransform);
			}
		}

		private static void AppendCounting(StringBuilder builder, ComfortSnapshot snapshot)
		{
			//IL_0043: 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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			List<KeyValuePair<ComfortGroup, int>> list = new List<KeyValuePair<ComfortGroup, int>>(snapshot.GroupMax);
			list.Sort((KeyValuePair<ComfortGroup, int> a, KeyValuePair<ComfortGroup, int> b) => b.Value.CompareTo(a.Value));
			for (int num = 0; num < list.Count; num++)
			{
				ComfortGroup key = list[num].Key;
				int value = list[num].Value;
				snapshot.GroupSource.TryGetValue(key, out var value2);
				builder.Append('\n');
				builder.Append(string.IsNullOrEmpty(value2) ? PieceDiscovery.GroupLabel(key) : value2);
				builder.Append("  +").Append(value);
				builder.Append("  (").Append(PieceDiscovery.GroupLabel(key)).Append(')');
			}
			for (int num2 = 0; num2 < snapshot.Uniques.Count; num2++)
			{
				UniqueComfort uniqueComfort = snapshot.Uniques[num2];
				builder.Append('\n');
				builder.Append(uniqueComfort.Name).Append("  +").Append(uniqueComfort.Comfort);
			}
		}

		private static void EnsurePanel(Hud hud)
		{
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)panel != (Object)null && !Object.op_Implicit((Object)(object)panel))
			{
				panel = null;
				header = null;
				body = null;
				lastText = null;
			}
			Transform val = ResolveParent(hud);
			if ((Object)(object)panel != (Object)null && (Object)(object)val != (Object)null && (Object)(object)((Component)panel).transform.parent != (Object)(object)val)
			{
				Object.Destroy((Object)(object)((Component)panel).gameObject);
				panel = null;
				header = null;
				body = null;
				lastText = null;
			}
			if ((Object)(object)panel != (Object)null || (Object)(object)val == (Object)null)
			{
				return;
			}
			TMP_FontAsset font = HudUi.FontFrom(hud);
			panel = HudUi.CreatePanel(val, "ComfortMeCatalog", PanelBackground);
			((MaskableGraphic)panel).maskable = false;
			RectTransform rectTransform = ((Graphic)panel).rectTransform;
			rectTransform.sizeDelta = new Vector2(320f, 0f);
			((Transform)rectTransform).SetAsLastSibling();
			VerticalLayoutGroup obj = ((Component)panel).gameObject.AddComponent<VerticalLayoutGroup>();
			((LayoutGroup)obj).padding = new RectOffset(10, 10, 8, 8);
			((HorizontalOrVerticalLayoutGroup)obj).spacing = 3f;
			((LayoutGroup)obj).childAlignment = (TextAnchor)0;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
			ContentSizeFitter obj2 = ((Component)panel).gameObject.AddComponent<ContentSizeFitter>();
			obj2.horizontalFit = (FitMode)0;
			obj2.verticalFit = (FitMode)2;
			header = HudUi.CreateText(((Component)panel).transform, "Header", font, null, 16f);
			((TMP_Text)header).alignment = (TextAlignmentOptions)257;
			((Graphic)header).color = HeaderColor;
			((MaskableGraphic)header).maskable = false;
			((TMP_Text)header).textWrappingMode = (TextWrappingModes)1;
			LayoutElement obj3 = ((Component)header).gameObject.AddComponent<LayoutElement>();
			obj3.minWidth = 280f;
			obj3.minHeight = 22f;
			HudUi.ApplyVanillaFont((TMP_Text)(object)header, hud);
			body = HudUi.CreateText(((Component)panel).transform, "Body", font, null, 14f);
			((TMP_Text)body).alignment = (TextAlignmentOptions)257;
			((Graphic)body).color = HeaderColor;
			((MaskableGraphic)body).maskable = false;
			((TMP_Text)body).textWrappingMode = (TextWrappingModes)1;
			LayoutElement obj4 = ((Component)body).gameObject.AddComponent<LayoutElement>();
			obj4.minWidth = 280f;
			obj4.minHeight = 40f;
			HudUi.ApplyVanillaFont((TMP_Text)(object)body, hud);
			if (!loggedCreate)
			{
				loggedCreate = true;
				ManualLogSource log = ComfortMePlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"Catalog shown on overlay canvas (room breakdown).");
				}
			}
			PlaceBesideBuildUi(hud);
		}

		private static void PlaceBesideBuildUi(Hud hud)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: 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)
			if (!((Object)(object)panel == (Object)null) && !((Object)(object)hud == (Object)null))
			{
				RectTransform rectTransform = ((Graphic)panel).rectTransform;
				Transform parent = ((Transform)rectTransform).parent;
				RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
				if (!((Object)(object)val == (Object)null))
				{
					rectTransform.sizeDelta = new Vector2(320f, 0f);
					((Transform)rectTransform).SetAsLastSibling();
					rectTransform.pivot = new Vector2(0f, 1f);
					rectTransform.anchorMin = new Vector2(0.84f, 0.78f);
					rectTransform.anchorMax = new Vector2(0.84f, 0.78f);
					rectTransform.anchoredPosition = Vector2.zero;
					string name = ((Object)val).name;
					Rect rect = val.rect;
					LogPlaceOnce($"Catalog dock 0.84,0.78 parent={name} size={((Rect)(ref rect)).size}");
				}
			}
		}

		private static void LogPlaceOnce(string message)
		{
			if (!loggedPlace)
			{
				loggedPlace = true;
				ManualLogSource log = ComfortMePlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)message);
				}
			}
		}

		private static Transform ResolveParent(Hud hud)
		{
			EnsureOverlay(hud);
			if (!((Object)(object)overlay != (Object)null))
			{
				return null;
			}
			return ((Component)overlay).transform;
		}

		private static void EnsureOverlay(Hud hud)
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)overlay != (Object)null && !Object.op_Implicit((Object)(object)overlay))
			{
				overlay = null;
			}
			if ((Object)(object)overlay != (Object)null)
			{
				((Component)overlay).gameObject.SetActive(true);
				return;
			}
			Canvas val = null;
			if ((Object)(object)hud.m_rootObject != (Object)null)
			{
				val = hud.m_rootObject.GetComponent<Canvas>() ?? hud.m_rootObject.GetComponentInParent<Canvas>();
			}
			Transform val2 = (((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform.parent != (Object)null) ? ((Component)val).transform.parent : ((Component)hud).transform);
			GameObject val3 = new GameObject("ComfortMeOverlay")
			{
				layer = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject.layer : 5)
			};
			val3.transform.SetParent(val2, false);
			overlay = val3.AddComponent<Canvas>();
			overlay.renderMode = (RenderMode)0;
			overlay.overrideSorting = true;
			overlay.sortingOrder = 5000;
			overlay.pixelPerfect = false;
			CanvasScaler val4 = val3.AddComponent<CanvasScaler>();
			CanvasScaler val5 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<CanvasScaler>() : null);
			if ((Object)(object)val5 != (Object)null)
			{
				val4.uiScaleMode = val5.uiScaleMode;
				val4.referenceResolution = val5.referenceResolution;
				val4.screenMatchMode = val5.screenMatchMode;
				val4.matchWidthOrHeight = val5.matchWidthOrHeight;
				val4.referencePixelsPerUnit = val5.referencePixelsPerUnit;
				val4.scaleFactor = val5.scaleFactor;
			}
			else
			{
				val4.uiScaleMode = (ScaleMode)1;
				val4.referenceResolution = new Vector2(1920f, 1080f);
				val4.screenMatchMode = (ScreenMatchMode)0;
				val4.matchWidthOrHeight = 1f;
			}
			ManualLogSource log = ComfortMePlugin.Log;
			if (log != null)
			{
				log.LogInfo((object)string.Format("Catalog overlay canvas sorting={0} parent={1} hudCanvas={2}", 5000, ((Object)val2).name, ((Object)(object)val != (Object)null) ? ((Object)val).name : "null"));
			}
		}
	}
	internal static class GroupHoverHighlight
	{
		private const string OverlayName = "ComfortMeGroupPlate";

		private static readonly Color Lift = new Color(1f, 1f, 1f, 0.28f);

		private static readonly FieldInfo HoveredField = AccessTools.Field(typeof(BuildUi), "m_currentHoveredPieceButton");

		private static readonly FieldInfo ButtonsField = AccessTools.Field(typeof(BuildUi), "m_pieceButtons");

		private static readonly FieldInfo ButtonField = AccessTools.Field(typeof(BuildUiPieceButton), "m_button");

		private static readonly FieldInfo IconField = AccessTools.Field(typeof(BuildUiPieceButton), "m_icon");

		private static readonly FieldInfo UpgradeArrowField = AccessTools.Field(typeof(BuildUiPieceButton), "m_upgradeArrow");

		private static readonly FieldInfo FavoriteStarField = AccessTools.Field(typeof(BuildUiPieceButton), "m_favoriteStar");

		private static bool loggedPlate;

		internal static void Clear()
		{
			loggedPlate = false;
			HideAll();
		}

		internal static void Tick()
		{
			//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_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Invalid comparison between Unknown and I4
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.Enabled.Value || !ModConfig.ShowGroupHoverHighlight.Value || !ComfortUsageTag.IsComfortTabSelected())
			{
				HideAll();
				return;
			}
			Hud instance = Hud.instance;
			BuildUi val = (((Object)(object)instance != (Object)null) ? instance.m_buildUi : null);
			if ((Object)(object)val == (Object)null)
			{
				HideAll();
				return;
			}
			object? obj = HoveredField?.GetValue(val);
			BuildUiPieceButton val2 = (BuildUiPieceButton)((obj is BuildUiPieceButton) ? obj : null);
			Piece val3 = (((Object)(object)val2 != (Object)null) ? val2.Piece : null);
			ComfortGroup val4 = (ComfortGroup)(((Object)(object)val3 != (Object)null) ? ((int)val3.m_comfortGroup) : 0);
			bool flag = (Object)(object)val3 != (Object)null && val3.m_comfort > 0 && (int)val4 > 0;
			if (!(ButtonsField?.GetValue(val) is IList list))
			{
				return;
			}
			for (int i = 0; i < list.Count; i++)
			{
				object? obj2 = list[i];
				BuildUiPieceButton val5 = (BuildUiPieceButton)((obj2 is BuildUiPieceButton) ? obj2 : null);
				if (val5 != null && !((Object)(object)val5 == (Object)null))
				{
					Piece piece = val5.Piece;
					bool lifted = flag && (Object)(object)piece != (Object)null && piece.m_comfort > 0 && piece.m_comfortGroup == val4;
					SetLift(val5, lifted);
				}
			}
		}

		private static void HideAll()
		{
			Hud instance = Hud.instance;
			BuildUi val = (((Object)(object)instance != (Object)null) ? instance.m_buildUi : null);
			if ((Object)(object)val == (Object)null || !(ButtonsField?.GetValue(val) is IList list))
			{
				return;
			}
			for (int i = 0; i < list.Count; i++)
			{
				object? obj = list[i];
				BuildUiPieceButton val2 = (BuildUiPieceButton)((obj is BuildUiPieceButton) ? obj : null);
				if (val2 != null && (Object)(object)val2 != (Object)null)
				{
					SetLift(val2, lifted: false);
				}
			}
		}

		private static void SetLift(BuildUiPieceButton button, bool lifted)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: 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_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			Image val = ResolvePlate(button);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Transform val2 = ((Component)val).transform.Find("ComfortMeGroupPlate");
			if (!lifted)
			{
				if ((Object)(object)val2 != (Object)null)
				{
					((Component)val2).gameObject.SetActive(false);
				}
				return;
			}
			Image val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent<Image>() : null);
			if ((Object)(object)val3 == (Object)null)
			{
				val3 = HudUi.CreatePanel(((Component)val).transform, "ComfortMeGroupPlate", Lift);
				RectTransform rectTransform = ((Graphic)val3).rectTransform;
				rectTransform.anchorMin = Vector2.zero;
				rectTransform.anchorMax = Vector2.one;
				rectTransform.offsetMin = Vector2.zero;
				rectTransform.offsetMax = Vector2.zero;
				((Graphic)val3).raycastTarget = false;
				((MaskableGraphic)val3).maskable = true;
				if (!loggedPlate)
				{
					loggedPlate = true;
					ManualLogSource log = ComfortMePlugin.Log;
					if (log != null)
					{
						log.LogInfo((object)("Group hover plate '" + ((Object)val).name + "' on '" + ((Object)button).name + "'."));
					}
				}
			}
			val3.sprite = (((Object)(object)val.sprite != (Object)null) ? val.sprite : HudUi.WhiteSprite);
			val3.type = val.type;
			((Graphic)val3).color = Lift;
			((Component)val3).gameObject.SetActive(true);
			((Component)val3).transform.SetAsFirstSibling();
		}

		private static Image ResolvePlate(BuildUiPieceButton button)
		{
			object? obj = IconField?.GetValue(button);
			Image icon = (Image)((obj is Image) ? obj : null);
			object? obj2 = ButtonField?.GetValue(button);
			Button val = (Button)((obj2 is Button) ? obj2 : null);
			if (val != null)
			{
				Graphic targetGraphic = ((Selectable)val).targetGraphic;
				Image val2 = (Image)(object)((targetGraphic is Image) ? targetGraphic : null);
				if (val2 != null && !IsForbidden(val2, button, icon))
				{
					return val2;
				}
			}
			Image component = ((Component)button).GetComponent<Image>();
			if ((Object)(object)component != (Object)null && !IsForbidden(component, button, icon))
			{
				return component;
			}
			Image[] componentsInChildren = ((Component)button).GetComponentsInChildren<Image>(true);
			foreach (Image val3 in componentsInChildren)
			{
				if (!IsForbidden(val3, button, icon))
				{
					return val3;
				}
			}
			return null;
		}

		private static bool IsForbidden(Image image, BuildUiPieceButton button, Image icon)
		{
			if ((Object)(object)image == (Object)null)
			{
				return true;
			}
			if ((Object)(object)image == (Object)(object)icon || ((Object)image).name == "ComfortMeGroupPlate" || ((Object)image).name == "ComfortMeBadge")
			{
				return true;
			}
			object? obj = UpgradeArrowField?.GetValue(button);
			Image val = (Image)((obj is Image) ? obj : null);
			if (val != null && (Object)(object)image == (Object)(object)val)
			{
				return true;
			}
			object? obj2 = FavoriteStarField?.GetValue(button);
			Image val2 = (Image)((obj2 is Image) ? obj2 : null);
			if (val2 != null && (Object)(object)image == (Object)(object)val2)
			{
				return true;
			}
			return false;
		}
	}
	internal static class HudUi
	{
		private static Sprite whiteSprite;

		private static TMP_FontAsset cachedFont;

		internal static Sprite WhiteSprite
		{
			get
			{
				//IL_002c: 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)
				if ((Object)(object)whiteSprite == (Object)null)
				{
					Texture2D whiteTexture = Texture2D.whiteTexture;
					whiteSprite = Sprite.Create(whiteTexture, new Rect(0f, 0f, (float)((Texture)whiteTexture).width, (float)((Texture)whiteTexture).height), new Vector2(0.5f, 0.5f), 100f);
				}
				return whiteSprite;
			}
		}

		internal static TMP_FontAsset FontFrom(Hud hud)
		{
			Resolve(hud);
			return cachedFont;
		}

		internal static Material FontMaterialFrom(Hud hud)
		{
			return null;
		}

		internal static bool BuildMenuOpen(Hud hud)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !((Character)localPlayer).InPlaceMode())
			{
				return false;
			}
			if ((Object)(object)hud != (Object)null && (Object)(object)hud.m_buildUi != (Object)null && ((Component)hud.m_buildUi).gameObject.activeInHierarchy)
			{
				return true;
			}
			if (Hud.IsPieceSelectionVisible())
			{
				return true;
			}
			if ((Object)(object)hud != (Object)null && (Object)(object)hud.m_buildHud != (Object)null)
			{
				return hud.m_buildHud.activeInHierarchy;
			}
			return false;
		}

		internal static bool HoldingPlaceTool()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				return ((Character)localPlayer).InPlaceMode();
			}
			return false;
		}

		internal static void ApplyVanillaFont(TMP_Text dest, Hud hud)
		{
			if (!((Object)(object)dest == (Object)null))
			{
				Resolve(hud);
				if ((Object)(object)cachedFont != (Object)null)
				{
					dest.font = cachedFont;
				}
			}
		}

		internal static TextMeshProUGUI CreateText(Transform parent, string name, TMP_FontAsset font, Material fontMaterial, float size)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) })
			{
				layer = ((Component)parent).gameObject.layer
			};
			val.transform.SetParent(parent, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			TMP_FontAsset val3 = (((Object)(object)font != (Object)null) ? font : cachedFont);
			if ((Object)(object)val3 != (Object)null)
			{
				((TMP_Text)val2).font = val3;
			}
			((TMP_Text)val2).fontSize = size;
			((Graphic)val2).raycastTarget = false;
			((MaskableGraphic)val2).maskable = true;
			((TMP_Text)val2).overflowMode = (TextOverflowModes)0;
			((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)val2).richText = true;
			return val2;
		}

		internal static Image CreatePanel(Transform parent, string name, Color color)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image)
			})
			{
				layer = ((Component)parent).gameObject.layer
			};
			val.transform.SetParent(parent, false);
			Image component = val.GetComponent<Image>();
			component.sprite = WhiteSprite;
			((Graphic)component).color = color;
			((Graphic)component).raycastTarget = false;
			((MaskableGraphic)component).maskable = true;
			return component;
		}

		internal static string Hex(Color color)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return ColorUtility.ToHtmlStringRGB(color);
		}

		private static void Resolve(Hud hud)
		{
			if ((Object)(object)cachedFont != (Object)null)
			{
				return;
			}
			TMP_Text val = FindSource(hud);
			if ((Object)(object)val != (Object)null && (Object)(object)val.font != (Object)null)
			{
				cachedFont = val.font;
				ManualLogSource log = ComfortMePlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("Using HUD font '" + ((Object)cachedFont).name + "'."));
				}
				return;
			}
			TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
			foreach (TMP_FontAsset val2 in array)
			{
				if (!((Object)(object)val2 == (Object)null) && (((Object)val2).name ?? string.Empty).IndexOf("Liberation", StringComparison.OrdinalIgnoreCase) < 0)
				{
					cachedFont = val2;
					ManualLogSource log2 = ComfortMePlugin.Log;
					if (log2 != null)
					{
						log2.LogInfo((object)("Using fallback font '" + ((Object)cachedFont).name + "'."));
					}
					break;
				}
			}
		}

		private static TMP_Text FindSource(Hud hud)
		{
			if ((Object)(object)hud == (Object)null)
			{
				return null;
			}
			if (IsUsable(hud.m_buildSelection))
			{
				return hud.m_buildSelection;
			}
			if (IsUsable(hud.m_pieceDescription))
			{
				return hud.m_pieceDescription;
			}
			TMP_Text[] componentsInChildren = ((Component)hud).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				if (IsUsable(val) && !IsOurs(val))
				{
					return val;
				}
			}
			return null;
		}

		private static bool IsUsable(TMP_Text text)
		{
			if ((Object)(object)text != (Object)null)
			{
				return (Object)(object)text.font != (Object)null;
			}
			return false;
		}

		private static bool IsOurs(TMP_Text text)
		{
			string name = ((Object)text).name;
			if (!(name == "Header") && !(name == "Body"))
			{
				return name == "Text";
			}
			return true;
		}
	}
	internal static class ModLocalization
	{
		private const string FolderName = "translations";

		private const int DownloadTimeoutMs = 8000;

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

		private static readonly object downloadLock = new object();

		private static readonly HashSet<string> queuedDownloads = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static string appliedLanguage;

		private static bool hooked;

		private static bool loggedFolder;

		private static volatile bool pendingReload;

		private static volatile bool englishFetched;

		internal static void Hook()
		{
			if (!hooked)
			{
				hooked = true;
				Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(ApplySelected));
				ApplySelected();
			}
		}

		internal static void EnsureApplied()
		{
			if (Localization.instance == null)
			{
				return;
			}
			if (pendingReload)
			{
				pendingReload = false;
				english.Clear();
				appliedLanguage = null;
				ApplySelected();
			}
			else
			{
				string text = CurrentLanguage();
				if (!(appliedLanguage == text) || !HasOurWord())
				{
					ApplySelected();
				}
			}
		}

		internal static string T(string key)
		{
			EnsureApplied();
			string result = Fallback(key);
			if (Localization.instance == null)
			{
				return result;
			}
			string text = Localization.instance.Localize("$" + key);
			if (string.IsNullOrEmpty(text) || text == "$" + key)
			{
				return result;
			}
			return text;
		}

		internal static void ApplySelected()
		{
			QueueDownload();
			string text = CurrentLanguage();
			Dictionary<string, string> dictionary = LoadMerged(text);
			Localization instance = Localization.instance;
			if (instance == null)
			{
				return;
			}
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				SetWord(instance, item.Key, item.Value);
			}
			appliedLanguage = text;
			GroupCatalogPanel.InvalidateText();
			ManualLogSource log = ComfortMePlugin.Log;
			if (log != null)
			{
				log.LogInfo((object)$"Localization applied ({text}, {dictionary.Count} string(s)).");
			}
		}

		private static string CurrentLanguage()
		{
			if (Localization.instance == null)
			{
				return "English";
			}
			string selectedLanguage = Localization.instance.GetSelectedLanguage();
			if (!string.IsNullOrEmpty(selectedLanguage))
			{
				return selectedLanguage;
			}
			return "English";
		}

		private static Dictionary<string, string> LoadMerged(string language)
		{
			if (english.Count == 0)
			{
				LoadFile("English", english);
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>(english, StringComparer.Ordinal);
			string text = MapLanguageFile(language);
			if (!string.Equals(text, "English", StringComparison.OrdinalIgnoreCase))
			{
				Dictionary<string, string> dictionary2 = new Dictionary<string, string>(StringComparer.Ordinal);
				LoadFile(text, dictionary2);
				foreach (KeyValuePair<string, string> item in dictionary2)
				{
					dictionary[item.Key] = item.Value;
				}
			}
			return dictionary;
		}

		private static string MapLanguageFile(string language)
		{
			if (string.Equals(language, "Chinese_Simplified", StringComparison.OrdinalIgnoreCase) || string.Equals(language, "ChineseSimplified", StringComparison.OrdinalIgnoreCase) || string.Equals(language, "SChinese", StringComparison.OrdinalIgnoreCase))
			{
				return "Chinese";
			}
			return language;
		}

		private static void QueueDownload()
		{
			if (ModConfig.DownloadTranslations == null || !ModConfig.DownloadTranslations.Value)
			{
				return;
			}
			string text = ((ModConfig.TranslationsUrl != null) ? ModConfig.TranslationsUrl.Value : null);
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string language = MapLanguageFile(CurrentLanguage());
			if (!IsSafeLanguageName(language))
			{
				return;
			}
			lock (downloadLock)
			{
				if (!queuedDownloads.Add(language))
				{
					return;
				}
			}
			string url = text.TrimEnd(new char[1] { '/' }) + "/";
			ThreadPool.QueueUserWorkItem(delegate
			{
				FetchFromGitHub(url, language);
			});
		}

		private static void FetchFromGitHub(string baseUrl, string language)
		{
			try
			{
				TryEnableTls12();
				bool flag = false;
				if (!englishFetched && TryDownloadFile(baseUrl, "English"))
				{
					englishFetched = true;
					flag = true;
				}
				if (!string.Equals(language, "English", StringComparison.OrdinalIgnoreCase) && TryDownloadFile(baseUrl, language))
				{
					flag = true;
				}
				if (flag)
				{
					pendingReload = true;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = ComfortMePlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("Translation download skipped: " + ex.Message));
				}
			}
		}

		private static bool TryDownloadFile(string baseUrl, string language)
		{
			if (!IsSafeLanguageName(language))
			{
				return false;
			}
			string requestUriString = baseUrl + language + ".json";
			try
			{
				HttpWebRequest obj = (HttpWebRequest)WebRequest.Create(requestUriString);
				obj.Method = "GET";
				obj.UserAgent = "ComfortMe/1.3.1";
				obj.Timeout = 8000;
				obj.ReadWriteTimeout = 8000;
				obj.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
				using HttpWebResponse httpWebResponse = (HttpWebResponse)obj.GetResponse();
				using Stream stream = httpWebResponse.GetResponseStream();
				if (httpWebResponse.StatusCode != HttpStatusCode.OK || stream == null)
				{
					return false;
				}
				string text;
				using (StreamReader streamReader = new StreamReader(stream, Encoding.UTF8))
				{
					text = streamReader.ReadToEnd();
				}
				Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
				ParseObject(text, dictionary);
				if (dictionary.Count == 0)
				{
					ManualLogSource log = ComfortMePlugin.Log;
					if (log != null)
					{
						log.LogInfo((object)("GitHub " + language + ".json was empty or invalid; keeping local file."));
					}
					return false;
				}
				string text2 = CacheDirectory();
				Directory.CreateDirectory(text2);
				File.WriteAllText(Path.Combine(text2, language + ".json"), text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				ManualLogSource log2 = ComfortMePlugin.Log;
				if (log2 != null)
				{
					log2.LogInfo((object)$"Updated translations from GitHub: {language}.json ({dictionary.Count} string(s)).");
				}
				return true;
			}
			catch (WebException ex)
			{
				if (ex.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound })
				{
					ManualLogSource log3 = ComfortMePlugin.Log;
					if (log3 != null)
					{
						log3.LogInfo((object)("No GitHub translations for " + language + " (using local files)."));
					}
				}
				else
				{
					ManualLogSource log4 = ComfortMePlugin.Log;
					if (log4 != null)
					{
						log4.LogInfo((object)("Translation download skipped (" + language + "): " + ex.Message));
					}
				}
				return false;
			}
			catch (Exception ex2)
			{
				ManualLogSource log5 = ComfortMePlugin.Log;
				if (log5 != null)
				{
					log5.LogInfo((object)("Translation download skipped (" + language + "): " + ex2.Message));
				}
				return false;
			}
		}

		private static string CacheDirectory()
		{
			return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "translations");
		}

		private static bool IsSafeLanguageName(string language)
		{
			if (string.IsNullOrEmpty(language) || language.Length > 64)
			{
				return false;
			}
			foreach (char c in language)
			{
				if (!char.IsLetterOrDigit(c) && c != '_' && c != '-')
				{
					return false;
				}
			}
			return true;
		}

		private static void TryEnableTls12()
		{
			try
			{
				ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
			}
			catch
			{
			}
		}

		private static void LoadFile(string language, Dictionary<string, string> dest)
		{
			dest.Clear();
			string text = FindFile(language);
			if (text == null)
			{
				if (!string.Equals(language, "English", StringComparison.OrdinalIgnoreCase))
				{
					ManualLogSource log = ComfortMePlugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("Localization file '" + language + ".json' was not found."));
					}
				}
			}
			else
			{
				ParseObject(File.ReadAllText(text, Encoding.UTF8), dest);
			}
		}

		private static string FindFile(string language)
		{
			string path = language + ".json";
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			string[] array = new string[3]
			{
				directoryName,
				Path.Combine(directoryName ?? string.Empty, "translations"),
				Path.GetFullPath(Path.Combine(directoryName ?? ".", "..", "..", "..", "translations"))
			};
			string text = null;
			foreach (string text2 in array)
			{
				if (!string.IsNullOrEmpty(text2) && Directory.Exists(text2))
				{
					if (!loggedFolder)
					{
						text = text2;
					}
					string text3 = Path.Combine(text2, path);
					if (File.Exists(text3))
					{
						loggedFolder = true;
						return text3;
					}
				}
			}
			if (!loggedFolder && text != null)
			{
				loggedFolder = true;
				ManualLogSource log = ComfortMePlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("Localization folder: " + text));
				}
			}
			return null;
		}

		private static void SetWord(Localization loc, string key, string value)
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(Localization), "AddWord", new Type[2]
			{
				typeof(string),
				typeof(string)
			}, (Type[])null);
			if (methodInfo != null)
			{
				methodInfo.Invoke(loc, new object[2] { key, value });
				return;
			}
			Dictionary<string, string> dictionary = null;
			foreach (FieldInfo declaredField in AccessTools.GetDeclaredFields(typeof(Localization)))
			{
				if (!(declaredField.FieldType != typeof(Dictionary<string, string>)) && declaredField.GetValue(loc) is Dictionary<string, string> dictionary2 && (dictionary == null || dictionary2.Count > dictionary.Count))
				{
					dictionary = dictionary2;
				}
			}