Decompiled source of ComfortAudit v0.4.4

plugins/ComfortAudit/ComfortAudit.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ComfortAudit.Core;
using ComfortAudit.L10n;
using ComfortAudit.Model;
using ComfortAudit.UI;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ComfortAudit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Shows which furniture is feeding your Comfort level and what is being ignored.")]
[assembly: AssemblyFileVersion("0.4.4.0")]
[assembly: AssemblyInformationalVersion("0.4.4+a9b12586004ac57f154e8f370540f3a4c213614f")]
[assembly: AssemblyProduct("ComfortAudit")]
[assembly: AssemblyTitle("ComfortAudit")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.4.4.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 ComfortAudit
{
	[BepInPlugin("com.jumpingmushroom.comfortaudit", "ComfortAudit", "0.4.4")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim.x86_64")]
	public sealed class ComfortAuditPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.jumpingmushroom.comfortaudit";

		public const string PluginName = "ComfortAudit";

		public const string PluginVersion = "0.4.4";

		internal static ManualLogSource Log;

		private readonly ComfortPanel _panel = new ComfortPanel();

		private Harmony _harmony;

		private ComfortSnapshot _snapshot = new ComfortSnapshot();

		private float _nextScan;

		private bool _guiReady;

		private Player _lastPlayer;

		private bool _hadPlayer;

		private void Awake()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			Strings.Register();
			CostTable.Load();
			ConsoleCommands.Register();
			_harmony = new Harmony("com.jumpingmushroom.comfortaudit");
			_harmony.PatchAll(typeof(ComfortAuditPlugin).Assembly);
			GUIManager.OnCustomGUIAvailable += OnGuiAvailable;
			PluginConfig.LayoutChanged += OnLayoutChanged;
			PluginConfig.ContentChanged += OnContentChanged;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"ComfortAudit 0.4.4 loaded.");
		}

		private void OnDestroy()
		{
			GUIManager.OnCustomGUIAvailable -= OnGuiAvailable;
			PluginConfig.LayoutChanged -= OnLayoutChanged;
			PluginConfig.ContentChanged -= OnContentChanged;
			_panel.Destroy();
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}

		private void OnGuiAvailable()
		{
			if (!GUIManager.IsHeadless())
			{
				if ((Object)(object)Player.m_localPlayer == (Object)null)
				{
					_panel.SetOpen(open: false);
				}
				_panel.Create();
				_guiReady = _panel.Created;
				if (PluginConfig.Verbose.Value)
				{
					Log.LogDebug((object)("panel created: " + _guiReady));
				}
			}
		}

		private void OnLayoutChanged()
		{
			_panel.ApplyLayout();
		}

		private void OnContentChanged()
		{
			_panel.InvalidateText();
		}

		private void LocalPlayerGone()
		{
			_snapshot = new ComfortSnapshot();
			SnapshotService.Clear();
			_panel.SetOpen(open: false);
			_nextScan = 0f;
			PieceCatalog.Invalidate();
			ComfortScanner.ResetHistory();
			Diagnostics.Reset();
		}

		private void LocalPlayerArrived()
		{
			_snapshot = new ComfortSnapshot();
			SnapshotService.Clear();
			_nextScan = 0f;
			PieceCatalog.Invalidate();
			ComfortScanner.ResetHistory();
			Diagnostics.Reset();
		}

		private void Update()
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			if (!_guiReady)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			bool flag = (Object)(object)localPlayer != (Object)null;
			bool flag2 = localPlayer == _lastPlayer;
			if (!flag && _hadPlayer)
			{
				LocalPlayerGone();
			}
			else if (flag && (!_hadPlayer || !flag2))
			{
				LocalPlayerArrived();
			}
			_hadPlayer = flag;
			_lastPlayer = localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			KeyboardShortcut value = PluginConfig.ToggleKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown() && !InputBlocked())
			{
				_panel.Toggle();
			}
			if (!_panel.IsOpen)
			{
				return;
			}
			if (Time.time >= _nextScan)
			{
				_nextScan = Time.time + Mathf.Max(0.1f, PluginConfig.ScanInterval.Value);
				_snapshot = SnapshotService.Get(Mathf.Max(0.1f, PluginConfig.ScanInterval.Value));
				Diagnostics.ReportOnce(_snapshot);
				Diagnostics.ReportEmptyRecommendations(_snapshot);
				if (PluginConfig.Verbose.Value)
				{
					Log.LogDebug((object)$"comfort={_snapshot.ComfortLevel} vanilla={_snapshot.VanillaComfortLevel} shelter={_snapshot.InShelter} cover={_snapshot.CoverPercentage:0.00} pieces={_snapshot.PieceCount}");
				}
			}
			if (PluginConfig.ShowPlacementPreview.Value)
			{
				_panel.SetPreview(PlacementPreview.Compute(localPlayer, _snapshot));
			}
			else
			{
				_panel.SetPreview(null);
			}
			_panel.Render(_snapshot);
		}

		private static bool InputBlocked()
		{
			if (Console.IsVisible())
			{
				return true;
			}
			if (Menu.IsVisible())
			{
				return true;
			}
			if (TextInput.IsVisible())
			{
				return true;
			}
			if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())
			{
				return true;
			}
			return false;
		}
	}
	public enum RecommendationFilter
	{
		KnownOnly,
		KnownAndStationInRange,
		Buildable
	}
	public static class PluginConfig
	{
		public static ConfigEntry<KeyboardShortcut> ToggleKey;

		public static ConfigEntry<Vector2> PanelPosition;

		public static ConfigEntry<float> PanelWidth;

		public static ConfigEntry<float> ScanInterval;

		public static ConfigEntry<bool> ShowDistances;

		public static ConfigEntry<bool> ShowPrefabNames;

		public static ConfigEntry<bool> Verbose;

		public static ConfigEntry<RecommendationFilter> Filter;

		public static ConfigEntry<int> MaxRecommendations;

		public static ConfigEntry<int> MaxIgnored;

		public static ConfigEntry<bool> ShowMaterials;

		public static ConfigEntry<bool> ShowCeiling;

		public static ConfigEntry<bool> ShowComfortOnIcon;

		public static ConfigEntry<bool> ShowPlacementPreview;

		public static readonly Vector2 DefaultPosition = Vector2.zero;

		public static event Action LayoutChanged;

		public static event Action ContentChanged;

		private static ConfigurationManagerAttributes Attr(int order, bool advanced = false)
		{
			//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_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			return new ConfigurationManagerAttributes
			{
				Order = order,
				IsAdvanced = advanced
			};
		}

		public static void Bind(ConfigFile cfg)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Expected O, but got Unknown
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Expected O, but got Unknown
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Expected O, but got Unknown
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Expected O, but got Unknown
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Expected O, but got Unknown
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0200: Expected O, but got Unknown
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Expected O, but got Unknown
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Expected O, but got Unknown
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Expected O, but got Unknown
			//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Expected O, but got Unknown
			//IL_02fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0308: Expected O, but got Unknown
			//IL_0330: Unknown result type (might be due to invalid IL or missing references)
			//IL_033a: Expected O, but got Unknown
			//IL_0362: Unknown result type (might be due to invalid IL or missing references)
			//IL_036c: Expected O, but got Unknown
			ToggleKey = cfg.Bind<KeyboardShortcut>("General", "ToggleKey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), new ConfigDescription("Key that shows and hides the Comfort Audit panel.", (AcceptableValueBase)null, new object[1] { Attr(100) }));
			ScanInterval = cfg.Bind<float>("General", "ScanInterval", 0.5f, new ConfigDescription("Seconds between rescans while the panel is open. The game itself only recomputes comfort every 2 s, so values below that show fresher numbers than the vanilla HUD.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), new object[1] { Attr(90) }));
			PanelPosition = cfg.Bind<Vector2>("Panel", "Position", DefaultPosition, new ConfigDescription("Panel offset from the top-left of the screen, in pixels, measured to the panel's top-left corner. (0, 0) means 'centre on screen' and is what the reset button restores. Dragging the panel updates this value.", (AcceptableValueBase)null, new object[1] { Attr(80) }));
			PanelWidth = cfg.Bind<float>("Panel", "Width", 420f, new ConfigDescription("Panel width in pixels.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(280f, 900f), new object[1] { Attr(70) }));
			cfg.Bind<bool>("Panel", "ResetPosition", false, new ConfigDescription("", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				Order = 60,
				HideSettingName = true,
				HideDefaultButton = true,
				CustomDrawer = DrawResetButton
			} }));
			ShowDistances = cfg.Bind<bool>("Panel", "ShowDistances", false, new ConfigDescription("Show each piece's distance from you, for checking the 10 m radius.", (AcceptableValueBase)null, new object[1] { Attr(50) }));
			ShowPrefabNames = cfg.Bind<bool>("Panel", "ShowPrefabNames", false, new ConfigDescription("Show prefab names alongside display names. Useful when diagnosing modded pieces.", (AcceptableValueBase)null, new object[1] { Attr(40, advanced: true) }));
			Filter = cfg.Bind<RecommendationFilter>("Recommendations", "Filter", RecommendationFilter.KnownAndStationInRange, new ConfigDescription("Which pieces count as available to you. KnownOnly is the broadest. KnownAndStationInRange matches what your hammer would actually let you place here. Buildable additionally requires the materials on hand, which is the most actionable but often empties the list.", (AcceptableValueBase)null, new object[1] { Attr(35) }));
			MaxRecommendations = cfg.Bind<int>("Recommendations", "MaxShown", 5, new ConfigDescription("How many suggestions to list.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 15), new object[1] { Attr(30) }));
			ShowMaterials = cfg.Bind<bool>("Recommendations", "ShowMaterials", true, new ConfigDescription("List each suggestion's material cost, and whether you have enough.", (AcceptableValueBase)null, new object[1] { Attr(25) }));
			ShowCeiling = cfg.Bind<bool>("Panel", "ShowCeiling", true, new ConfigDescription("Show the highest comfort reachable at a sheltered spot: what you could build today, and what exists once everything is unlocked.", (AcceptableValueBase)null, new object[1] { Attr(45) }));
			MaxIgnored = cfg.Bind<int>("Panel", "MaxIgnoredShown", 8, new ConfigDescription("How many ignored pieces to list before collapsing the rest into a count. This is the only list with no natural bound, and it is what made the panel outgrow the screen in a hall full of duplicate chairs.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), new object[1] { Attr(48) }));
			ShowComfortOnIcon = cfg.Bind<bool>("Panel", "ShowComfortOnIcon", true, new ConfigDescription("Append current comfort and your ceiling to the Rested icon's label. This is the only always-visible surface: Valheim locks the cursor during play, so a hover tooltip would only be reachable with the inventory or map open.", (AcceptableValueBase)null, new object[1] { Attr(18) }));
			ShowPlacementPreview = cfg.Bind<bool>("Panel", "ShowPlacementPreview", true, new ConfigDescription("While building, show what the held piece would add if placed where the ghost is, and what already beats it if the answer is nothing.", (AcceptableValueBase)null, new object[1] { Attr(55) }));
			Verbose = cfg.Bind<bool>("Logging", "Verbose", false, new ConfigDescription("Log scan details to the BepInEx log. Off by default; nothing is logged per tick.", (AcceptableValueBase)null, new object[1] { Attr(10, advanced: true) }));
			PanelPosition.SettingChanged += delegate
			{
				Raise(PluginConfig.LayoutChanged);
			};
			PanelWidth.SettingChanged += delegate
			{
				Raise(PluginConfig.LayoutChanged);
			};
			ShowDistances.SettingChanged += delegate
			{
				Raise(PluginConfig.ContentChanged);
			};
			Filter.SettingChanged += delegate
			{
				Raise(PluginConfig.ContentChanged);
			};
			MaxRecommendations.SettingChanged += delegate
			{
				Raise(PluginConfig.ContentChanged);
			};
			ShowMaterials.SettingChanged += delegate
			{
				Raise(PluginConfig.ContentChanged);
			};
			ShowCeiling.SettingChanged += delegate
			{
				Raise(PluginConfig.ContentChanged);
			};
			ShowPlacementPreview.SettingChanged += delegate
			{
				Raise(PluginConfig.ContentChanged);
			};
			ShowPrefabNames.SettingChanged += delegate
			{
				Raise(PluginConfig.ContentChanged);
			};
			MaxIgnored.SettingChanged += delegate
			{
				Raise(PluginConfig.ContentChanged);
			};
			Verbose.SettingChanged += delegate
			{
				Raise(PluginConfig.ContentChanged);
			};
		}

		private static void DrawResetButton(ConfigEntryBase entry)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if (GUILayout.Button("Centre panel on screen", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }))
			{
				PanelPosition.Value = DefaultPosition;
			}
		}

		private static void Raise(Action a)
		{
			if (a == null)
			{
				return;
			}
			try
			{
				a();
			}
			catch (Exception ex)
			{
				ComfortAuditPlugin.Log.LogWarning((object)("config apply failed: " + ex.Message));
			}
		}
	}
}
namespace ComfortAudit.UI
{
	public sealed class ComfortPanel
	{
		private const float Padding = 18f;

		private const float BottomSlack = 10f;

		private GameObject _panel;

		private RectTransform _panelRect;

		private RectTransform _bodyRect;

		private TextMeshProUGUI _body;

		private string _lastText;

		private bool _dirty = true;

		private int _builtRevision = -1;

		private Vector2 _lastSeenPosition;

		private float _positionSettleAt;

		private bool _suppressLayoutApply;

		private bool _needsCentre;

		private const float PositionSettleDelay = 0.4f;

		private PlacementPreviewResult _preview;

		private const float HeightMargin = 24f;

		private static readonly Color BodyColor = Color32.op_Implicit(new Color32((byte)242, (byte)233, (byte)216, byte.MaxValue));

		private const string Dim = "<color=#BCAF97>";

		private const string Reset = "</color>";

		public bool IsOpen { get; private set; }

		public bool Created => (Object)(object)_panel != (Object)null;

		public void Create()
		{
			//IL_0041: 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_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Expected O, but got Unknown
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0200: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_026c: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_panel != (Object)null) && !((Object)(object)GUIManager.CustomGUIFront == (Object)null))
			{
				float value = PluginConfig.PanelWidth.Value;
				_panel = GUIManager.Instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, new Vector2(0f, 1f), new Vector2(0f, 1f), PluginConfig.PanelPosition.Value, value, 200f, true);
				((Object)_panel).name = "ComfortAuditPanel";
				_panelRect = _panel.GetComponent<RectTransform>();
				_panelRect.pivot = new Vector2(0f, 1f);
				if ((Object)(object)_panel.GetComponent<RectMask2D>() == (Object)null)
				{
					_panel.AddComponent<RectMask2D>();
				}
				GameObject val = new GameObject("Backdrop", new Type[2]
				{
					typeof(RectTransform),
					typeof(Image)
				});
				val.transform.SetParent(_panel.transform, false);
				RectTransform component = val.GetComponent<RectTransform>();
				component.anchorMin = Vector2.zero;
				component.anchorMax = Vector2.one;
				component.offsetMin = new Vector2(9f, 9f);
				component.offsetMax = new Vector2(-9f, -9f);
				Image component2 = val.GetComponent<Image>();
				((Graphic)component2).color = new Color(0.04f, 0.03f, 0.02f, 0.82f);
				((Graphic)component2).raycastTarget = false;
				GameObject val2 = new GameObject("Body", new Type[2]
				{
					typeof(RectTransform),
					typeof(TextMeshProUGUI)
				});
				val2.transform.SetParent(_panel.transform, false);
				RectTransform obj = (_bodyRect = val2.GetComponent<RectTransform>());
				obj.anchorMin = new Vector2(0f, 1f);
				obj.anchorMax = new Vector2(0f, 1f);
				obj.pivot = new Vector2(0f, 1f);
				obj.anchoredPosition = new Vector2(18f, -18f);
				obj.sizeDelta = new Vector2(value - 36f, 0f);
				_body = val2.GetComponent<TextMeshProUGUI>();
				((TMP_Text)_body).font = GUIManager.Instance.TMP_Norse;
				((TMP_Text)_body).fontSize = 15f;
				((Graphic)_body).color = BodyColor;
				((Graphic)_body).raycastTarget = false;
				((TMP_Text)_body).richText = true;
				((TMP_Text)_body).alignment = (TextAlignmentOptions)257;
				((TMP_Text)_body).textWrappingMode = (TextWrappingModes)1;
				((TMP_Text)_body).overflowMode = (TextOverflowModes)0;
				_panel.SetActive(IsOpen);
				_lastText = null;
				_dirty = true;
				ApplyLayout();
			}
		}

		public void ApplyLayout()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: 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)
			if (!((Object)(object)_panelRect == (Object)null) && !_suppressLayoutApply)
			{
				if (PluginConfig.PanelPosition.Value == Vector2.zero)
				{
					_needsCentre = true;
				}
				float value = PluginConfig.PanelWidth.Value;
				_panelRect.sizeDelta = new Vector2(value, _panelRect.sizeDelta.y);
				if ((Object)(object)_bodyRect != (Object)null)
				{
					_bodyRect.sizeDelta = new Vector2(value - 36f, 0f);
				}
				_panelRect.anchoredPosition = ClampToCanvas(PluginConfig.PanelPosition.Value, value);
				_lastSeenPosition = _panelRect.anchoredPosition;
				_positionSettleAt = 0f;
				_lastText = null;
				_dirty = true;
			}
		}

		private Vector2 CanvasSize()
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = (RectTransform)(((Object)(object)_panelRect != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null);
			if ((Object)(object)val != (Object)null)
			{
				Rect rect = val.rect;
				if (((Rect)(ref rect)).width > 1f)
				{
					rect = val.rect;
					if (((Rect)(ref rect)).height > 1f)
					{
						rect = val.rect;
						float width = ((Rect)(ref rect)).width;
						rect = val.rect;
						return new Vector2(width, ((Rect)(ref rect)).height);
					}
				}
			}
			return new Vector2((float)Screen.width, (float)Screen.height);
		}

		private Vector2 ClampToCanvas(Vector2 pos, float width)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = CanvasSize();
			return new Vector2(Mathf.Clamp(pos.x, 60f - width, Mathf.Max(60f - width, val.x - 60f)), Mathf.Clamp(pos.y, 0f - Mathf.Max(0f, val.y - 60f), 0f));
		}

		private void CentreOnCanvas()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_panelRect == (Object)null)
			{
				return;
			}
			Vector2 val = CanvasSize();
			Vector2 sizeDelta = _panelRect.sizeDelta;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(Mathf.Round((val.x - sizeDelta.x) * 0.5f), 0f - Mathf.Round((val.y - sizeDelta.y) * 0.5f));
			_panelRect.anchoredPosition = val2;
			_lastSeenPosition = val2;
			_positionSettleAt = 0f;
			_needsCentre = false;
			_suppressLayoutApply = true;
			try
			{
				PluginConfig.PanelPosition.Value = val2;
			}
			finally
			{
				_suppressLayoutApply = false;
			}
		}

		private void PersistPositionWhenSettled()
		{
			//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_001b: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_panelRect == (Object)null)
			{
				return;
			}
			Vector2 anchoredPosition = _panelRect.anchoredPosition;
			if (anchoredPosition != _lastSeenPosition)
			{
				_lastSeenPosition = anchoredPosition;
				_positionSettleAt = Time.unscaledTime + 0.4f;
			}
			else
			{
				if (_positionSettleAt <= 0f || Time.unscaledTime < _positionSettleAt)
				{
					return;
				}
				_positionSettleAt = 0f;
				if (anchoredPosition != PluginConfig.PanelPosition.Value)
				{
					_suppressLayoutApply = true;
					try
					{
						PluginConfig.PanelPosition.Value = anchoredPosition;
					}
					finally
					{
						_suppressLayoutApply = false;
					}
				}
			}
		}

		public void InvalidateText()
		{
			_dirty = true;
		}

		public void Toggle()
		{
			SetOpen(!IsOpen);
		}

		public void SetOpen(bool open)
		{
			IsOpen = open;
			if ((Object)(object)_panel != (Object)null)
			{
				_panel.SetActive(open);
			}
		}

		public void Destroy()
		{
			if ((Object)(object)_panel != (Object)null)
			{
				Object.Destroy((Object)(object)_panel);
			}
			_panel = null;
			_panelRect = null;
			_bodyRect = null;
			_body = null;
			_lastText = null;
			IsOpen = false;
		}

		public void SetPreview(PlacementPreviewResult preview)
		{
			if (!Same(_preview, preview))
			{
				_preview = preview;
				_dirty = true;
			}
		}

		private static bool Same(PlacementPreviewResult a, PlacementPreviewResult b)
		{
			if (a == b)
			{
				return true;
			}
			if (a == null || b == null)
			{
				return false;
			}
			if (!a.Active && !b.Active)
			{
				return true;
			}
			if (a.Active == b.Active && a.Delta == b.Delta && a.InRange == b.InRange && a.NewTotal == b.NewTotal && a.DisplayName == b.DisplayName && a.BlockedBy == b.BlockedBy)
			{
				return Mathf.Abs(a.Distance - b.Distance) < 0.05f;
			}
			return false;
		}

		public void Render(ComfortSnapshot snap)
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_body == (Object)null || !IsOpen)
			{
				return;
			}
			PersistPositionWhenSettled();
			int revision = SnapshotService.Revision;
			if (!_dirty && revision == _builtRevision)
			{
				return;
			}
			_dirty = false;
			_builtRevision = revision;
			string text = Build(snap);
			if (text == _lastText)
			{
				return;
			}
			_lastText = text;
			((TMP_Text)_body).text = text;
			float value = PluginConfig.PanelWidth.Value;
			float y = ((TMP_Text)_body).GetPreferredValues(text, value - 36f, 0f).y;
			if (!((Object)(object)_panelRect != (Object)null))
			{
				return;
			}
			Vector2 val = CanvasSize();
			float num = Mathf.Min(y + 36f + 10f, val.y - 24f);
			_panelRect.sizeDelta = new Vector2(value, num);
			if (_needsCentre)
			{
				CentreOnCanvas();
				return;
			}
			Vector2 anchoredPosition = _panelRect.anchoredPosition;
			float num2 = Mathf.Min(0f, num - val.y);
			if (anchoredPosition.y < num2)
			{
				anchoredPosition.y = num2;
				_panelRect.anchoredPosition = anchoredPosition;
				_lastSeenPosition = anchoredPosition;
			}
		}

		private static string Orange(string s)
		{
			//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_0010: Unknown result type (might be due to invalid IL or missing references)
			Color valheimOrange = GUIManager.Instance.ValheimOrange;
			return $"<color=#{ColorUtility.ToHtmlStringRGB(valheimOrange)}>{s}</color>";
		}

		private static string Good(string s)
		{
			return "<color=#9BD97A>" + s + "</color>";
		}

		private static string Bad(string s)
		{
			return "<color=#FF8A6B>" + s + "</color>";
		}

		private string Build(ComfortSnapshot snap)
		{
			StringBuilder stringBuilder = new StringBuilder(1024);
			stringBuilder.Append(Orange("<b>" + Strings.Get("$comfortaudit_panel_title") + "</b>"));
			stringBuilder.Append('\n');
			if (!snap.Valid)
			{
				stringBuilder.Append("<color=#BCAF97>").Append(Strings.Get("$comfortaudit_not_resting")).Append("</color>");
				return stringBuilder.ToString();
			}
			AppendHeadline(stringBuilder, snap);
			stringBuilder.Append('\n');
			AppendPreview(stringBuilder, snap);
			AppendShelter(stringBuilder, snap);
			stringBuilder.Append('\n');
			AppendGate(stringBuilder, snap);
			stringBuilder.Append('\n');
			AppendPieces(stringBuilder, snap);
			stringBuilder.Append('\n');
			AppendRecommendations(stringBuilder, snap);
			if (snap.Mismatch)
			{
				stringBuilder.Append('\n').Append(Bad(Strings.Get("$comfortaudit_mismatch", snap.VanillaComfortLevel, snap.ComfortLevel)));
			}
			return stringBuilder.ToString();
		}

		private void AppendHeadline(StringBuilder sb, ComfortSnapshot snap)
		{
			sb.Append("<b>").Append(Strings.Get("$comfortaudit_comfort")).Append(" ")
				.Append(snap.ComfortLevel)
				.Append("</b>");
			sb.Append("   ").Append(Strings.Get("$comfortaudit_rested")).Append(' ')
				.Append(RestedMath.Format(snap.RestedSeconds));
			if (!snap.TtlFromLiveEffect && PluginConfig.Verbose.Value)
			{
				sb.Append(' ').Append("<color=#BCAF97>").Append('(')
					.Append(Strings.Get("$comfortaudit_from_prefab"))
					.Append(')')
					.Append("</color>");
			}
			sb.Append('\n').Append("<color=#BCAF97>");
			sb.Append(snap.InShelter ? Strings.Get("$comfortaudit_baseline") : Strings.Get("$comfortaudit_baseline_noshelter"));
			sb.Append("</color>").Append('\n');
			AppendCeiling(sb, snap);
		}

		private void AppendCeiling(StringBuilder sb, ComfortSnapshot snap)
		{
			if (PluginConfig.ShowCeiling.Value && snap.CeilingValid && snap.CeilingUnlocked > 0)
			{
				sb.Append("<color=#BCAF97>").Append("  ");
				string text = RestedMath.Format(snap.BaseTTL + (float)(snap.CeilingUnlocked - 1) * snap.TTLPerLevel);
				if (snap.ComfortLevel >= snap.CeilingUnlocked)
				{
					sb.Append(Strings.Get("$comfortaudit_ceiling", snap.CeilingUnlocked, text)).Append(" — ").Append(Strings.Get("$comfortaudit_ceiling_atmax"));
				}
				else
				{
					sb.Append(Strings.Get("$comfortaudit_ceiling", snap.CeilingUnlocked, text));
				}
				if (snap.CeilingAll > snap.CeilingUnlocked)
				{
					string text2 = RestedMath.Format(snap.BaseTTL + (float)(snap.CeilingAll - 1) * snap.TTLPerLevel);
					sb.Append(", ").Append(Strings.Get("$comfortaudit_ceiling_all", snap.CeilingAll, text2));
				}
				sb.Append("</color>").Append('\n');
			}
		}

		private void AppendPreview(StringBuilder sb, ComfortSnapshot snap)
		{
			if (_preview == null || !_preview.Active)
			{
				return;
			}
			sb.Append(Orange(Strings.Get("$comfortaudit_preview"))).Append('\n').Append("  ");
			if (!_preview.InRange)
			{
				sb.Append("<color=#BCAF97>").Append(Strings.Get("$comfortaudit_preview_outofrange", _preview.DisplayName, _preview.Distance, 10)).Append("</color>")
					.Append('\n')
					.Append('\n');
				return;
			}
			if (_preview.Delta > 0)
			{
				sb.Append(Good(Strings.Get("$comfortaudit_preview_gain", _preview.DisplayName, _preview.Delta, _preview.NewTotal)));
			}
			else
			{
				sb.Append("<color=#BCAF97>").Append(Strings.Get("$comfortaudit_preview_nogain", _preview.DisplayName));
				if (!snap.InShelter)
				{
					sb.Append(" — ").Append(Strings.Get("$comfortaudit_preview_noshelter"));
				}
				else if (!string.IsNullOrEmpty(_preview.BlockedBy))
				{
					sb.Append(" — ").Append(Strings.Get("$comfortaudit_preview_blocked", _preview.BlockedBy));
				}
				sb.Append("</color>");
			}
			sb.Append('\n').Append('\n');
		}

		private void AppendShelter(StringBuilder sb, ComfortSnapshot snap)
		{
			int num = Mathf.RoundToInt(snap.CoverPercentage * 100f);
			if (snap.InShelter)
			{
				sb.Append(Good(Strings.Get("$comfortaudit_shelter_ok", num))).Append('\n');
				return;
			}
			string text = Strings.Get(snap.UnderRoof ? "$comfortaudit_yes" : "$comfortaudit_no");
			sb.Append(Bad(Strings.Get("$comfortaudit_shelter_no", num, text))).Append('\n');
			sb.Append(Bad(Strings.Get("$comfortaudit_shelter_warn", snap.PotentialIfSheltered))).Append('\n');
		}

		private void AppendGate(StringBuilder sb, ComfortSnapshot snap)
		{
			if (snap.Gate != null)
			{
				sb.Append(Orange(Strings.Get("$comfortaudit_gate_header"))).Append('\n');
				for (int i = 0; i < snap.Gate.Conditions.Count; i++)
				{
					GateCondition gateCondition = snap.Gate.Conditions[i];
					sb.Append(gateCondition.Met ? Good("  + ") : Bad("  - "));
					sb.Append(gateCondition.Met ? "<color=#BCAF97>" : string.Empty);
					sb.Append(Strings.Get(gateCondition.Token));
					sb.Append(gateCondition.Met ? "</color>" : string.Empty);
					sb.Append('\n');
				}
			}
		}

		private void AppendPieces(StringBuilder sb, ComfortSnapshot snap)
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			if (snap.PieceCount == 0)
			{
				sb.Append("<color=#BCAF97>").Append(Strings.Get("$comfortaudit_no_pieces", 10)).Append("</color>")
					.Append('\n');
				AppendMissing(sb, snap);
				return;
			}
			bool inShelter = snap.InShelter;
			sb.Append(Orange(Strings.Get(inShelter ? "$comfortaudit_contributing" : "$comfortaudit_contributing_noshelter"))).Append('\n');
			bool flag = false;
			for (int i = 0; i < snap.Pieces.Count; i++)
			{
				PieceEntry pieceEntry = snap.Pieces[i];
				if (pieceEntry.Status == PieceStatus.Counted)
				{
					flag = true;
					sb.Append("  ").Append(ComfortGroups.Name(pieceEntry.Group)).Append(": ")
						.Append(pieceEntry.DisplayName);
					if (pieceEntry.Inactive)
					{
						sb.Append("  ").Append((pieceEntry.LightGain > 0) ? Bad(Strings.Get("$comfortaudit_unlit_hint", pieceEntry.LightGain)) : Bad(Strings.Get("$comfortaudit_unlit")));
					}
					else if (inShelter)
					{
						sb.Append("  ").Append(Orange("+" + pieceEntry.Comfort));
					}
					else
					{
						sb.Append("  ").Append("<color=#BCAF97>").Append("+")
							.Append(pieceEntry.Comfort)
							.Append("</color>");
					}
					AppendSuffix(sb, pieceEntry);
					sb.Append('\n');
				}
			}
			if (!flag)
			{
				sb.Append("<color=#BCAF97>").Append("  —").Append("</color>")
					.Append('\n');
			}
			bool flag2 = false;
			int num = 0;
			int num2 = 0;
			int num3 = Mathf.Max(1, PluginConfig.MaxIgnored.Value);
			for (int j = 0; j < snap.Pieces.Count; j++)
			{
				PieceEntry pieceEntry2 = snap.Pieces[j];
				if (pieceEntry2.Status == PieceStatus.Counted)
				{
					continue;
				}
				if (!flag2)
				{
					sb.Append('\n').Append(Orange(Strings.Get("$comfortaudit_ignored"))).Append('\n');
					flag2 = true;
				}
				if (num >= num3)
				{
					num2++;
					continue;
				}
				num++;
				sb.Append("<color=#BCAF97>").Append("  ").Append(ComfortGroups.Name(pieceEntry2.Group))
					.Append(": ")
					.Append(pieceEntry2.DisplayName)
					.Append("  ");
				sb.Append((pieceEntry2.Status == PieceStatus.ShadowedByGroup) ? Strings.Get("$comfortaudit_beaten_by", pieceEntry2.ShadowedBy) : Strings.Get("$comfortaudit_dupe_of", pieceEntry2.ShadowedBy));
				sb.Append(" — ");
				if (pieceEntry2.Inactive && pieceEntry2.LightGain > 0)
				{
					sb.Append("</color>").Append(Good(Strings.Get("$comfortaudit_unlit_hint", pieceEntry2.LightGain))).Append("<color=#BCAF97>");
				}
				else if (pieceEntry2.RemovalLoss > 0)
				{
					sb.Append(Strings.Get("$comfortaudit_keep_removal_costs", pieceEntry2.RemovalLoss));
				}
				else
				{
					sb.Append(Strings.Get("$comfortaudit_safe_to_remove"));
				}
				AppendSuffix(sb, pieceEntry2);
				sb.Append("</color>").Append('\n');
			}
			if (num2 > 0)
			{
				sb.Append("<color=#BCAF97>").Append("  ").Append(Strings.Get("$comfortaudit_ignored_more", num2))
					.Append("</color>")
					.Append('\n');
			}
			AppendMissing(sb, snap);
		}

		private void AppendRecommendations(StringBuilder sb, ComfortSnapshot snap)
		{
			sb.Append(Orange(Strings.Get("$comfortaudit_recommend"))).Append('\n');
			if (!snap.RecommendationsReady)
			{
				sb.Append("<color=#BCAF97>").Append("  ").Append(Strings.Get("$comfortaudit_rec_loading"))
					.Append("</color>")
					.Append('\n');
				return;
			}
			if (snap.Recommendations.Count == 0)
			{
				AppendNothingToDo(sb, snap);
				return;
			}
			for (int i = 0; i < snap.Recommendations.Count; i++)
			{
				AppendRecommendation(sb, snap.Recommendations[i]);
			}
		}

		private void AppendNothingToDo(StringBuilder sb, ComfortSnapshot snap)
		{
			RecommendationStats recStats = snap.RecStats;
			if (recStats == null || recStats.Catalogue == 0)
			{
				sb.Append("<color=#BCAF97>").Append("  ").Append(Strings.Get("$comfortaudit_rec_none"))
					.Append("</color>")
					.Append('\n');
				return;
			}
			if (recStats.StationOutOfRange > 0 || recStats.MaterialsShort > 0)
			{
				sb.Append("<color=#BCAF97>").Append("  ").Append(Strings.Get("$comfortaudit_rec_none_filtered"))
					.Append("</color>")
					.Append('\n');
			}
			else if (recStats.NoGain > 0)
			{
				sb.Append("  ").Append(Good(Strings.Get("$comfortaudit_rec_none_best"))).Append('\n');
			}
			else
			{
				sb.Append("<color=#BCAF97>").Append("  ").Append(Strings.Get("$comfortaudit_rec_none"))
					.Append("</color>")
					.Append('\n');
			}
			if (recStats.RecipeUnknown > 0)
			{
				AppendNote(sb, Strings.Get("$comfortaudit_rec_locked", recStats.RecipeUnknown));
			}
			if (recStats.StationOutOfRange > 0)
			{
				AppendNote(sb, Strings.Get("$comfortaudit_rec_needstation", recStats.StationOutOfRange));
			}
			if (recStats.MaterialsShort > 0)
			{
				AppendNote(sb, Strings.Get("$comfortaudit_rec_needmaterials", recStats.MaterialsShort));
			}
		}

		private void AppendNote(StringBuilder sb, string text)
		{
			sb.Append("<color=#BCAF97>").Append("  ").Append(text)
				.Append("</color>")
				.Append('\n');
		}

		private void AppendRecommendation(StringBuilder sb, Recommendation r)
		{
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			sb.Append("  ").Append(Orange("+" + r.Gain)).Append("  ")
				.Append(r.DisplayName);
			if (r.Kind == RecommendationKind.LightIt)
			{
				sb.Append("  ").Append(Good(Strings.Get("$comfortaudit_rec_light")));
			}
			if (!string.IsNullOrEmpty(r.Replaces))
			{
				sb.Append("  ").Append("<color=#BCAF97>").Append(Strings.Get("$comfortaudit_rec_replaces", r.Replaces))
					.Append("</color>");
			}
			else if (r.Kind == RecommendationKind.Stacking)
			{
				sb.Append("  ").Append("<color=#BCAF97>").Append(Strings.Get("$comfortaudit_rec_stacking"))
					.Append("</color>");
			}
			else if (r.Kind == RecommendationKind.NewGroup && r.GroupKnown)
			{
				string text = ComfortGroups.Name(r.Group);
				if (!string.Equals(text, r.DisplayName, StringComparison.OrdinalIgnoreCase))
				{
					sb.Append("  ").Append("<color=#BCAF97>").Append(text)
						.Append("</color>");
				}
			}
			sb.Append('\n');
			if (r.Free || r.Kind == RecommendationKind.Shelter)
			{
				return;
			}
			if (!r.StationInRange)
			{
				sb.Append("      ").Append(Bad(Strings.Get("$comfortaudit_rec_nostation"))).Append('\n');
			}
			else
			{
				if (!PluginConfig.ShowMaterials.Value || r.Materials.Count == 0)
				{
					return;
				}
				sb.Append("      ");
				for (int i = 0; i < r.Materials.Count; i++)
				{
					MaterialLine materialLine = r.Materials[i];
					if (i > 0)
					{
						sb.Append("<color=#BCAF97>").Append(", ").Append("</color>");
					}
					string text2 = materialLine.Amount + " " + materialLine.DisplayName;
					if (materialLine.Enough)
					{
						sb.Append("<color=#BCAF97>").Append(text2).Append("</color>");
					}
					else if (materialLine.Have > 0)
					{
						sb.Append(Bad(text2 + " " + Strings.Get("$comfortaudit_have", materialLine.Have)));
					}
					else
					{
						sb.Append(Bad(text2));
					}
				}
				sb.Append('\n');
			}
		}

		private void AppendSuffix(StringBuilder sb, PieceEntry e)
		{
			if (PluginConfig.ShowDistances.Value)
			{
				sb.Append("<color=#BCAF97>").Append($"  {e.Distance:0.0}m").Append("</color>");
			}
			if (PluginConfig.ShowPrefabNames.Value)
			{
				sb.Append("<color=#BCAF97>").Append("  [").Append(e.PrefabName)
					.Append(']')
					.Append("</color>");
			}
		}

		private void AppendMissing(StringBuilder sb, ComfortSnapshot snap)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			if (snap.MissingGroups.Count == 0)
			{
				return;
			}
			sb.Append('\n').Append(Orange(Strings.Get("$comfortaudit_missing"))).Append('\n')
				.Append("<color=#BCAF97>")
				.Append("  ");
			for (int i = 0; i < snap.MissingGroups.Count; i++)
			{
				if (i > 0)
				{
					sb.Append(", ");
				}
				sb.Append(ComfortGroups.Name(snap.MissingGroups[i]));
			}
			sb.Append("</color>").Append('\n');
		}
	}
}
namespace ComfortAudit.Patches
{
	[HarmonyPatch(typeof(StatusEffect), "GetIconText")]
	public static class StatusEffect_GetIconText_Patch
	{
		private const float FreshScanSeconds = 2.5f;

		private static int _comfort = -1;

		private static int _ceiling = -1;

		private static string _suffix = string.Empty;

		private static void Postfix(StatusEffect __instance, ref string __result)
		{
			if (PluginConfig.ShowComfortOnIcon.Value && __instance is SE_Rested)
			{
				Refresh();
				if (_suffix.Length != 0)
				{
					__result = (string.IsNullOrEmpty(__result) ? _suffix : (__result + "  " + _suffix));
				}
			}
		}

		private static void Refresh()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				Set(0, 0);
				return;
			}
			ComfortSnapshot current = SnapshotService.Current;
			int comfortLevel;
			int ceiling;
			if (current.Valid && Time.time - SnapshotService.LastScanTime < 2.5f)
			{
				comfortLevel = current.ComfortLevel;
				ceiling = (current.CeilingValid ? current.CeilingUnlocked : 0);
			}
			else
			{
				comfortLevel = localPlayer.GetComfortLevel();
				Ceiling.Result result = Ceiling.Get(localPlayer);
				ceiling = (result.Valid ? result.Unlocked : 0);
			}
			Set(comfortLevel, ceiling);
		}

		private static void Set(int comfort, int ceiling)
		{
			if (comfort != _comfort || ceiling != _ceiling)
			{
				_comfort = comfort;
				_ceiling = ceiling;
				if (comfort <= 0)
				{
					_suffix = string.Empty;
				}
				else if (ceiling > 0)
				{
					_suffix = comfort + "/" + ceiling;
				}
				else
				{
					_suffix = comfort.ToString();
				}
			}
		}
	}
}
namespace ComfortAudit.Model
{
	public enum PieceStatus
	{
		Counted,
		ShadowedByGroup,
		ShadowedByName
	}
	public sealed class PieceEntry
	{
		public string PrefabName;

		public string DisplayName;

		public string NameToken;

		public ComfortGroup Group;

		public bool GroupKnown;

		public int Comfort;

		public int RawComfort;

		public bool Inactive;

		public PieceStatus Status;

		public string ShadowedBy;

		public int LightGain;

		public int RemovalLoss;

		public float Distance;

		public Sprite Icon;

		public bool Contributing
		{
			get
			{
				if (Status == PieceStatus.Counted)
				{
					return Comfort > 0;
				}
				return false;
			}
		}
	}
	public sealed class GateCondition
	{
		public string Token;

		public bool Met;

		public GateCondition(string token, bool met)
		{
			Token = token;
			Met = met;
		}
	}
	public sealed class RestGateResult
	{
		public List<GateCondition> Conditions = new List<GateCondition>();

		public bool CanRest;

		public bool NearFire;

		public bool Sheltered;

		public bool Sitting;

		public bool AllMet
		{
			get
			{
				for (int i = 0; i < Conditions.Count; i++)
				{
					if (!Conditions[i].Met)
					{
						return false;
					}
				}
				return true;
			}
		}
	}
	public enum RecommendationKind
	{
		LightIt,
		Shelter,
		NewGroup,
		Stacking,
		Upgrade
	}
	public sealed class MaterialLine
	{
		public string DisplayName;

		public int Amount;

		public int Have;

		public bool Enough => Have >= Amount;
	}
	public sealed class Recommendation
	{
		public RecommendationKind Kind;

		public string DisplayName;

		public string PrefabName;

		public ComfortGroup Group;

		public bool GroupKnown;

		public int Gain;

		public float CostScore;

		public List<MaterialLine> Materials = new List<MaterialLine>();

		public bool HaveAllMaterials;

		public bool StationInRange;

		public string Replaces;

		public bool Free => Kind == RecommendationKind.LightIt;
	}
	public sealed class RecommendationStats
	{
		public int Catalogue;

		public int NotInMenu;

		public int NoGain;

		public int RecipeUnknown;

		public int StationOutOfRange;

		public int MaterialsShort;

		public int Candidates;

		public override string ToString()
		{
			return $"catalogue={Catalogue} notInMenu={NotInMenu} noGain={NoGain} recipeUnknown={RecipeUnknown} stationOutOfRange={StationOutOfRange} materialsShort={MaterialsShort} -> candidates={Candidates}";
		}
	}
	public sealed class PlacementPreviewResult
	{
		public bool Active;

		public string DisplayName;

		public ComfortGroup Group;

		public bool GroupKnown;

		public int PieceComfort;

		public int Delta;

		public int NewTotal;

		public float Distance;

		public bool InRange;

		public string BlockedBy;
	}
	public sealed class ComfortSnapshot
	{
		public bool Valid;

		public float Time;

		public int ComfortLevel;

		public int VanillaComfortLevel;

		public bool Mismatch;

		public bool InShelter;

		public float CoverPercentage;

		public bool UnderRoof;

		public int PotentialIfSheltered;

		public float RestedSeconds;

		public float BaseTTL;

		public float TTLPerLevel;

		public bool TtlFromLiveEffect;

		public RestGateResult Gate;

		public List<PieceEntry> Pieces = new List<PieceEntry>();

		public List<ComfortGroup> MissingGroups = new List<ComfortGroup>();

		public List<Recommendation> Recommendations = new List<Recommendation>();

		public bool RecommendationsReady;

		public RecommendationStats RecStats;

		public int CeilingUnlocked;

		public int CeilingAll;

		public bool CeilingValid;

		public int PieceCount => Pieces.Count;
	}
}
namespace ComfortAudit.L10n
{
	public static class Strings
	{
		public static void Register()
		{
			CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
			Add(localization, "English", "ComfortAudit.L10n.en.json");
			Add(localization, "Norwegian", "ComfortAudit.L10n.nb.json");
		}

		private static void Add(CustomLocalization loc, string language, string resource)
		{
			string text = ReadEmbedded(resource);
			if (!string.IsNullOrEmpty(text))
			{
				loc.AddJsonFile(language, text);
			}
		}

		public static string Get(string token)
		{
			if (string.IsNullOrEmpty(token))
			{
				return string.Empty;
			}
			if (token[0] != '$')
			{
				token = "$" + token;
			}
			if (Localization.instance == null)
			{
				return token;
			}
			return Localization.instance.Localize(token);
		}

		public static string Get(string token, params object[] args)
		{
			string text = Get(token);
			if (args != null && args.Length != 0)
			{
				return string.Format(text, args);
			}
			return text;
		}

		private static string ReadEmbedded(string name)
		{
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
			if (stream == null)
			{
				return null;
			}
			using StreamReader streamReader = new StreamReader(stream);
			return streamReader.ReadToEnd();
		}
	}
}
namespace ComfortAudit.Core
{
	public static class Ceiling
	{
		public struct Result
		{
			public int Unlocked;

			public int All;

			public bool Valid;
		}

		private const float TtlSeconds = 10f;

		private static Result _cached;

		private static float _computedAt = -999f;

		public static void Invalidate()
		{
			_computedAt = -999f;
		}

		public static Result Get(Player player)
		{
			if (Time.time - _computedAt < 10f)
			{
				return _cached;
			}
			Result result = Compute(player);
			if (result.Valid)
			{
				_cached = result;
				_computedAt = Time.time;
			}
			return result;
		}

		private static Result Compute(Player player)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			Result result = default(Result);
			List<PieceCatalog.Entry> list = PieceCatalog.Entries();
			if (list == null || (Object)(object)player == (Object)null)
			{
				return result;
			}
			Dictionary<ComfortGroup, int> map = new Dictionary<ComfortGroup, int>();
			Dictionary<ComfortGroup, int> map2 = new Dictionary<ComfortGroup, int>();
			Dictionary<string, int> map3 = new Dictionary<string, int>();
			Dictionary<string, int> map4 = new Dictionary<string, int>();
			for (int i = 0; i < list.Count; i++)
			{
				PieceCatalog.Entry entry = list[i];
				if (!entry.InBuildMenu)
				{
					continue;
				}
				bool flag = PieceCatalog.Available(entry, player) && player.HaveRequirements(entry.Piece, (RequirementMode)1);
				if (ComfortGroups.Stacks(entry.Group))
				{
					Accumulate(map3, entry.NameToken, entry.Comfort);
					if (flag)
					{
						Accumulate(map4, entry.NameToken, entry.Comfort);
					}
				}
				else
				{
					Accumulate(map, entry.Group, entry.Comfort);
					if (flag)
					{
						Accumulate(map2, entry.Group, entry.Comfort);
					}
				}
			}
			result.All = 2 + Sum(map) + Sum(map3);
			result.Unlocked = 2 + Sum(map2) + Sum(map4);
			result.Valid = true;
			return result;
		}

		private static void Accumulate<TKey>(Dictionary<TKey, int> map, TKey key, int value)
		{
			if (key != null && (!map.TryGetValue(key, out var value2) || value > value2))
			{
				map[key] = value;
			}
		}

		private static int Sum<TKey>(Dictionary<TKey, int> map)
		{
			int num = 0;
			foreach (KeyValuePair<TKey, int> item in map)
			{
				num += item.Value;
			}
			return num;
		}
	}
	public static class ComfortGroups
	{
		public static readonly ComfortGroup[] Real;

		private static readonly Dictionary<ComfortGroup, string> Tokens;

		static ComfortGroups()
		{
			ComfortGroup[] array = new ComfortGroup[11];
			RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
			Real = (ComfortGroup[])(object)array;
			Tokens = new Dictionary<ComfortGroup, string>();
			Tokens[(ComfortGroup)0] = "$comfortaudit_group_none";
			for (int i = 0; i < Real.Length; i++)
			{
				Tokens[Real[i]] = "$comfortaudit_group_" + ((object)Unsafe.As<ComfortGroup, ComfortGroup>(ref Real[i])/*cast due to .constrained prefix*/).ToString().ToLowerInvariant();
			}
		}

		public static bool IsKnown(ComfortGroup g)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			return Enum.IsDefined(typeof(ComfortGroup), g);
		}

		public static string Name(ComfortGroup g)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if (!IsKnown(g))
			{
				return Strings.Get("$comfortaudit_group_other");
			}
			if (!Tokens.TryGetValue(g, out var value))
			{
				return Strings.Get("$comfortaudit_group_other");
			}
			return Strings.Get(value);
		}

		public static bool Stacks(ComfortGroup g)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			return (int)g == 0;
		}
	}
	public static class ComfortScanner
	{
		public const float ComfortRadius = 10f;

		private static readonly List<Piece> Buffer = new List<Piece>(128);

		private static readonly List<ComfortWalk.Item> Items = new List<ComfortWalk.Item>(128);

		private static readonly List<ComfortWalk.Outcome> Outcomes = new List<ComfortWalk.Outcome>(128);

		private static int LastComfort = -1;

		private static float LastChangeTime;

		private static readonly Dictionary<string, int> RemovalMemo = new Dictionary<string, int>();

		public static ComfortSnapshot Scan(Player player)
		{
			//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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			ComfortSnapshot comfortSnapshot = new ComfortSnapshot
			{
				Time = Time.time
			};
			if ((Object)(object)player == (Object)null || (Object)(object)((Character)player).m_nview == (Object)null)
			{
				return comfortSnapshot;
			}
			Vector3 position = ((Component)player).transform.position;
			comfortSnapshot.Gate = RestGate.Evaluate(player);
			comfortSnapshot.InShelter = player.InShelter();
			comfortSnapshot.CoverPercentage = player.m_coverPercentage;
			comfortSnapshot.UnderRoof = player.m_underRoof;
			Buffer.Clear();
			Piece.GetAllComfortPiecesInRadius(position, 10f, Buffer);
			Items.Clear();
			for (int i = 0; i < Buffer.Count; i++)
			{
				Piece val = Buffer[i];
				comfortSnapshot.Pieces.Add(Describe(val, position));
				Items.Add(new ComfortWalk.Item
				{
					Group = val.m_comfortGroup,
					Comfort = val.GetComfort(),
					NameToken = val.m_name,
					Source = i
				});
			}
			Outcomes.Clear();
			int comfortLevel = ComfortWalk.Run(Items, comfortSnapshot.InShelter, Outcomes);
			for (int j = 0; j < Outcomes.Count; j++)
			{
				ComfortWalk.Outcome outcome = Outcomes[j];
				if (outcome.Source >= 0 && outcome.Source < comfortSnapshot.Pieces.Count)
				{
					PieceEntry pieceEntry = comfortSnapshot.Pieces[outcome.Source];
					pieceEntry.Status = outcome.Status;
					if (outcome.ShadowedBySource >= 0 && outcome.ShadowedBySource < comfortSnapshot.Pieces.Count)
					{
						pieceEntry.ShadowedBy = comfortSnapshot.Pieces[outcome.ShadowedBySource].DisplayName;
					}
				}
			}
			AnalyseHypotheticals(comfortSnapshot);
			comfortSnapshot.ComfortLevel = comfortLevel;
			comfortSnapshot.VanillaComfortLevel = player.GetComfortLevel();
			if (comfortSnapshot.ComfortLevel != LastComfort)
			{
				LastComfort = comfortSnapshot.ComfortLevel;
				LastChangeTime = Time.time;
			}
			comfortSnapshot.Mismatch = comfortSnapshot.VanillaComfortLevel > 0 && comfortSnapshot.ComfortLevel != comfortSnapshot.VanillaComfortLevel && Time.time - LastChangeTime > 2.5f;
			comfortSnapshot.PotentialIfSheltered = (comfortSnapshot.InShelter ? comfortSnapshot.ComfortLevel : SE_Rested.CalculateComfortLevel(true, position));
			ComputeMissingGroups(comfortSnapshot);
			RestedMath.Fill(comfortSnapshot);
			comfortSnapshot.Valid = true;
			Recommender.Fill(comfortSnapshot, player);
			Ceiling.Result result = Ceiling.Get(player);
			comfortSnapshot.CeilingValid = result.Valid;
			comfortSnapshot.CeilingUnlocked = result.Unlocked;
			comfortSnapshot.CeilingAll = result.All;
			return comfortSnapshot;
		}

		public static void ResetHistory()
		{
			LastComfort = -1;
			LastChangeTime = Time.time;
		}

		private static void AnalyseHypotheticals(ComfortSnapshot snap)
		{
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Expected I4, but got Unknown
			int num = ComfortWalk.Total(Items);
			RemovalMemo.Clear();
			for (int i = 0; i < Items.Count; i++)
			{
				ComfortWalk.Item item = Items[i];
				if (item.Source < 0 || item.Source >= snap.Pieces.Count)
				{
					continue;
				}
				PieceEntry pieceEntry = snap.Pieces[item.Source];
				if (pieceEntry.Inactive)
				{
					pieceEntry.LightGain = Mathf.Max(0, ComfortWalk.TotalWithComfort(Items, i, pieceEntry.RawComfort) - num);
				}
				if (pieceEntry.Status != PieceStatus.Counted)
				{
					string key = (int)item.Group + "|" + item.Comfort + "|" + item.NameToken;
					if (!RemovalMemo.TryGetValue(key, out var value))
					{
						value = ComfortWalk.TotalWithout(Items, i);
						RemovalMemo[key] = value;
					}
					pieceEntry.RemovalLoss = Mathf.Max(0, num - value);
				}
			}
		}

		private static void ComputeMissingGroups(ComfortSnapshot snap)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			HashSet<ComfortGroup> hashSet = new HashSet<ComfortGroup>();
			for (int i = 0; i < snap.Pieces.Count; i++)
			{
				PieceEntry pieceEntry = snap.Pieces[i];
				if (pieceEntry.Contributing)
				{
					hashSet.Add(pieceEntry.Group);
				}
			}
			for (int j = 0; j < ComfortGroups.Real.Length; j++)
			{
				if (!hashSet.Contains(ComfortGroups.Real[j]))
				{
					snap.MissingGroups.Add(ComfortGroups.Real[j]);
				}
			}
		}

		private static PieceEntry Describe(Piece piece, Vector3 from)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			int comfort = piece.GetComfort();
			return new PieceEntry
			{
				PrefabName = (((Object)(object)((Component)piece).gameObject != (Object)null) ? ((Object)((Component)piece).gameObject).name : "?"),
				DisplayName = DisplayName(piece),
				NameToken = piece.m_name,
				Group = piece.m_comfortGroup,
				GroupKnown = ComfortGroups.IsKnown(piece.m_comfortGroup),
				Comfort = comfort,
				RawComfort = piece.m_comfort,
				Inactive = (comfort == 0 && piece.m_comfort > 0),
				Distance = Vector3.Distance(from, ((Component)piece).transform.position),
				Icon = piece.m_icon
			};
		}

		private static string DisplayName(Piece piece)
		{
			if ((Object)(object)piece == (Object)null)
			{
				return "?";
			}
			return Names.Display(piece, ((Object)(object)((Component)piece).gameObject != (Object)null) ? ((Object)((Component)piece).gameObject).name : null);
		}
	}
	public static class ComfortWalk
	{
		public struct Item
		{
			public ComfortGroup Group;

			public int Comfort;

			public string NameToken;

			public int Source;
		}

		public struct Outcome
		{
			public int Source;

			public PieceStatus Status;

			public int ShadowedBySource;
		}

		public static readonly Comparison<Item> Sort = delegate(Item x, Item y)
		{
			//IL_0001: 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_0016: Unknown result type (might be due to invalid IL or missing references)
			if (x.Group != y.Group)
			{
				ref ComfortGroup reference = ref x.Group;
				object target = y.Group;
				return ((Enum)Unsafe.As<ComfortGroup, ComfortGroup>(ref reference)/*cast due to .constrained prefix*/).CompareTo(target);
			}
			return (x.Comfort != y.Comfort) ? ((float)y.Comfort).CompareTo(x.Comfort) : string.CompareOrdinal(y.NameToken ?? string.Empty, x.NameToken ?? string.Empty);
		};

		private static readonly List<Item> Scratch = new List<Item>(256);

		public static int Run(List<Item> items, bool inShelter, List<Outcome> outcomes = null)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			outcomes?.Clear();
			int num = 1;
			if (!inShelter)
			{
				if (outcomes != null)
				{
					ReportOnly(items, outcomes);
				}
				return num;
			}
			num++;
			items.Sort(Sort);
			for (int i = 0; i < items.Count; i++)
			{
				Item item = items[i];
				if (i > 0)
				{
					Item item2 = items[i - 1];
					if ((int)item.Group != 0 && item.Group == item2.Group)
					{
						Add(outcomes, item.Source, PieceStatus.ShadowedByGroup, item2.Source);
						continue;
					}
					if (item.NameToken == item2.NameToken)
					{
						Add(outcomes, item.Source, PieceStatus.ShadowedByName, item2.Source);
						continue;
					}
				}
				Add(outcomes, item.Source, PieceStatus.Counted, -1);
				num += item.Comfort;
			}
			return num;
		}

		public static int Total(List<Item> items)
		{
			Scratch.Clear();
			Scratch.AddRange(items);
			return Run(Scratch, inShelter: true);
		}

		public static int TotalWith(List<Item> items, Item extra)
		{
			Scratch.Clear();
			Scratch.AddRange(items);
			Scratch.Add(extra);
			return Run(Scratch, inShelter: true);
		}

		public static int TotalWithout(List<Item> items, int index)
		{
			Scratch.Clear();
			for (int i = 0; i < items.Count; i++)
			{
				if (i != index)
				{
					Scratch.Add(items[i]);
				}
			}
			return Run(Scratch, inShelter: true);
		}

		public static int TotalWithComfort(List<Item> items, int index, int comfort)
		{
			Scratch.Clear();
			Scratch.AddRange(items);
			Item value = Scratch[index];
			value.Comfort = comfort;
			Scratch[index] = value;
			return Run(Scratch, inShelter: true);
		}

		private static void ReportOnly(List<Item> items, List<Outcome> outcomes)
		{
			//IL_0026: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			items.Sort(Sort);
			for (int i = 0; i < items.Count; i++)
			{
				Item item = items[i];
				if (i > 0)
				{
					Item item2 = items[i - 1];
					if ((int)item.Group != 0 && item.Group == item2.Group)
					{
						Add(outcomes, item.Source, PieceStatus.ShadowedByGroup, item2.Source);
						continue;
					}
					if (item.NameToken == item2.NameToken)
					{
						Add(outcomes, item.Source, PieceStatus.ShadowedByName, item2.Source);
						continue;
					}
				}
				Add(outcomes, item.Source, PieceStatus.Counted, -1);
			}
		}

		private static void Add(List<Outcome> outcomes, int source, PieceStatus status, int by)
		{
			outcomes?.Add(new Outcome
			{
				Source = source,
				Status = status,
				ShadowedBySource = by
			});
		}
	}
	public static class ConsoleCommands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__1_0;

			public static Comparison<PieceCatalog.Entry> <>9__4_0;

			internal void <Register>b__1_0(ConsoleEventArgs args)
			{
				switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "help")
				{
				case "pieces":
					DumpPieces(args.Context);
					return;
				case "costs":
					DumpCosts(args.Context);
					return;
				case "groups":
					DumpGroups(args.Context);
					return;
				case "now":
					DumpNow(args.Context);
					return;
				}
				args.Context.AddString("comfortaudit pieces  - every comfort piece prefab, group, comfort, cost");
				args.Context.AddString("comfortaudit costs   - material tokens in use, and which have no weight");
				args.Context.AddString("comfortaudit groups  - comfort groups and how many pieces each has");
				args.Context.AddString("comfortaudit now     - live breakdown here and now, also written to the log");
			}

			internal int <DumpPieces>b__4_0(PieceCatalog.Entry a, PieceCatalog.Entry b)
			{
				//IL_0001: 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_0015: Unknown result type (might be due to invalid IL or missing references)
				if (a.Group != b.Group)
				{
					ref ComfortGroup reference = ref a.Group;
					object target = b.Group;
					return ((Enum)Unsafe.As<ComfortGroup, ComfortGroup>(ref reference)/*cast due to .constrained prefix*/).CompareTo(target);
				}
				if (a.Comfort != b.Comfort)
				{
					return b.Comfort.CompareTo(a.Comfort);
				}
				return string.CompareOrdinal(a.PrefabName, b.PrefabName);
			}
		}

		private static bool _registered;

		public static void Register()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			_registered = true;
			object obj = <>c.<>9__1_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "help")
					{
					case "pieces":
						DumpPieces(args.Context);
						break;
					case "costs":
						DumpCosts(args.Context);
						break;
					case "groups":
						DumpGroups(args.Context);
						break;
					case "now":
						DumpNow(args.Context);
						break;
					default:
						args.Context.AddString("comfortaudit pieces  - every comfort piece prefab, group, comfort, cost");
						args.Context.AddString("comfortaudit costs   - material tokens in use, and which have no weight");
						args.Context.AddString("comfortaudit groups  - comfort groups and how many pieces each has");
						args.Context.AddString("comfortaudit now     - live breakdown here and now, also written to the log");
						break;
					}
				};
				<>c.<>9__1_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("comfortaudit", "ComfortAudit diagnostics: pieces | costs | groups", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private static void DumpNow(Terminal ctx)
		{
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				ctx.AddString("ComfortAudit: no local player.");
				return;
			}
			SnapshotService.Clear();
			ComfortSnapshot comfortSnapshot = SnapshotService.Get(0f);
			if (!comfortSnapshot.Valid)
			{
				ctx.AddString("ComfortAudit: no valid snapshot.");
				return;
			}
			ctx.AddString($"Comfort {comfortSnapshot.ComfortLevel} (game says {comfortSnapshot.VanillaComfortLevel}), shelter {comfortSnapshot.InShelter}, cover {comfortSnapshot.CoverPercentage:0.00}, {comfortSnapshot.PieceCount} piece(s) in range");
			for (int i = 0; i < comfortSnapshot.Pieces.Count; i++)
			{
				PieceEntry pieceEntry = comfortSnapshot.Pieces[i];
				ctx.AddString(string.Format("  {0,-12} {1,-20} {2,-16} {3}", ComfortGroups.Name(pieceEntry.Group), pieceEntry.DisplayName, pieceEntry.Status, (pieceEntry.Status == PieceStatus.Counted) ? ("+" + pieceEntry.Comfort) : ("beaten by " + (pieceEntry.ShadowedBy ?? "?"))));
			}
			for (int j = 0; j < comfortSnapshot.Recommendations.Count; j++)
			{
				Recommendation recommendation = comfortSnapshot.Recommendations[j];
				ctx.AddString($"  next: +{recommendation.Gain} {recommendation.DisplayName} [{recommendation.Kind}]");
			}
			Diagnostics.ReportBreakdown(comfortSnapshot, ComfortAuditPlugin.Log);
		}

		private static List<PieceCatalog.Entry> Catalog(Terminal ctx)
		{
			List<PieceCatalog.Entry> list = PieceCatalog.Entries();
			if (list == null)
			{
				ctx.AddString("ComfortAudit: no catalogue yet - load a world first.");
			}
			return list;
		}

		private static void DumpPieces(Terminal ctx)
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			List<PieceCatalog.Entry> list = Catalog(ctx);
			if (list == null)
			{
				return;
			}
			list.Sort(delegate(PieceCatalog.Entry a, PieceCatalog.Entry b)
			{
				//IL_0001: 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_0015: Unknown result type (might be due to invalid IL or missing references)
				if (a.Group != b.Group)
				{
					ref ComfortGroup reference = ref a.Group;
					object target = b.Group;
					return ((Enum)Unsafe.As<ComfortGroup, ComfortGroup>(ref reference)/*cast due to .constrained prefix*/).CompareTo(target);
				}
				return (a.Comfort != b.Comfort) ? b.Comfort.CompareTo(a.Comfort) : string.CompareOrdinal(a.PrefabName, b.PrefabName);
			});
			ctx.AddString("ComfortAudit: " + list.Count + " comfort pieces");
			for (int num = 0; num < list.Count; num++)
			{
				PieceCatalog.Entry entry = list[num];
				ctx.AddString($"{ComfortGroups.Name(entry.Group),-10} {entry.Comfort,2}  {entry.DisplayName,-28} cost {entry.CostScore,6:0.0}  [{entry.PrefabName}]");
			}
		}

		private static void DumpCosts(Terminal ctx)
		{
			List<PieceCatalog.Entry> list = Catalog(ctx);
			if (list == null)
			{
				return;
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			for (int i = 0; i < list.Count; i++)
			{
				Requirement[] resources = list[i].Piece.m_resources;
				if (resources == null)
				{
					continue;
				}
				for (int j = 0; j < resources.Length; j++)
				{
					string text = PieceCatalog.ItemToken(resources[j]);
					if (!string.IsNullOrEmpty(text))
					{
						dictionary.TryGetValue(text, out var value);
						dictionary[text] = value + 1;
					}
				}
			}
			ctx.AddString("ComfortAudit: " + dictionary.Count + " material tokens used by comfort pieces (" + CostTable.Count + " weights loaded)");
			List<string> list2 = new List<string>();
			foreach (KeyValuePair<string, int> item in dictionary)
			{
				float num = CostTable.Weight(item.Key);
				bool flag = CostTable.HasWeight(item.Key);
				ctx.AddString(string.Format("{0,-28} weight {1,5:0.0} {2}  used by {3} piece(s)", item.Key, num, flag ? "       " : "(default)", item.Value));
				if (!flag)
				{
					list2.Add(item.Key);
				}
			}
			if (list2.Count <= 0)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("No weight for: ");
			for (int k = 0; k < list2.Count; k++)
			{
				if (k > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(list2[k]);
			}
			ctx.AddString(stringBuilder.ToString());
			ctx.AddString("These score 1.0, so ranking falls back to raw item count for them.");
		}

		private static void DumpGroups(Terminal ctx)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_0064: 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_00b6: Unknown result type (might be due to invalid IL or missing references)
			List<PieceCatalog.Entry> list = Catalog(ctx);
			if (list == null)
			{
				return;
			}
			Dictionary<ComfortGroup, int> dictionary = new Dictionary<ComfortGroup, int>();
			Dictionary<ComfortGroup, int> dictionary2 = new Dictionary<ComfortGroup, int>();
			for (int i = 0; i < list.Count; i++)
			{
				PieceCatalog.Entry entry = list[i];
				dictionary.TryGetValue(entry.Group, out var value);
				dictionary[entry.Group] = value + 1;
				if (!dictionary2.TryGetValue(entry.Group, out var value2) || entry.Comfort > value2)
				{
					dictionary2[entry.Group] = entry.Comfort;
				}
			}
			foreach (KeyValuePair<ComfortGroup, int> item in dictionary)
			{
				ctx.AddString($"{ComfortGroups.Name(item.Key),-12} {item.Value,3} pieces, best comfort {dictionary2[item.Key]}");
			}
		}
	}
	public static class CostTable
	{
		public const float DefaultWeight = 1f;

		private const string OverrideFileName = "ComfortAudit.costs.json";

		private static Dictionary<string, float> _weights;

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

		public static IEnumerable<string> UnknownTokens => Unknown;

		public static int Count
		{
			get
			{
				if (_weights != null)
				{
					return _weights.Count;
				}
				return 0;
			}
		}

		public static void Load()
		{
			_weights = new Dictionary<string, float>(StringComparer.Ordinal);
			Unknown.Clear();
			Parse(ReadEmbedded("ComfortAudit.L10n.costs.json"));
			string path = Path.Combine(Paths.ConfigPath, "ComfortAudit.costs.json");
			if (File.Exists(path))
			{
				try
				{
					Parse(File.ReadAllText(path));
					ComfortAuditPlugin.Log.LogInfo((object)"cost weights overridden from ComfortAudit.costs.json");
				}
				catch (Exception ex)
				{
					ComfortAuditPlugin.Log.LogWarning((object)("could not read ComfortAudit.costs.json, using defaults: " + ex.Message));
				}
			}
		}

		public static float Weight(string itemToken)
		{
			if (_weights == null)
			{
				Load();
			}
			if (string.IsNullOrEmpty(itemToken))
			{
				return 1f;
			}
			if (_weights.TryGetValue(itemToken, out var value))
			{
				return value;
			}
			Unknown.Add(itemToken);
			return 1f;
		}

		public static bool HasWeight(string itemToken)
		{
			if (_weights == null)
			{
				Load();
			}
			if (!string.IsNullOrEmpty(itemToken))
			{
				return _weights.ContainsKey(itemToken);
			}
			return false;
		}

		private static void Parse(string json)
		{
			if (string.IsNullOrEmpty(json))
			{
				return;
			}
			int num = 0;
			while (num < json.Length)
			{
				int num2 = json.IndexOf('"', num);
				if (num2 < 0)
				{
					break;
				}
				int num3 = EndOfString(json, num2);
				if (num3 < 0)
				{
					break;
				}
				string key = json.Substring(num2 + 1, num3 - num2 - 1);
				int num4 = json.IndexOf(':', num3);
				if (num4 < 0)
				{
					break;
				}
				int i;
				for (i = num4 + 1; i < json.Length && char.IsWhiteSpace(json[i]); i++)
				{
				}
				if (i >= json.Length)
				{
					break;
				}
				if (json[i] == '"')
				{
					int num5 = EndOfString(json, i);
					if (num5 < 0)
					{
						break;
					}
					num = num5 + 1;
					continue;
				}
				int j;
				for (j = i; j < json.Length && json[j] != ',' && json[j] != '}'; j++)
				{
				}
				if (float.TryParse(json.Substring(i, j - i).Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					_weights[key] = result;
				}
				num = j + 1;
			}
		}

		private static int EndOfString(string json, int open)
		{
			for (int i = open + 1; i < json.Length; i++)
			{
				if (json[i] == '\\')
				{
					i++;
				}
				else if (json[i] == '"')
				{
					return i;
				}
			}
			return -1;
		}

		private static string ReadEmbedded(string name)
		{
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
			if (stream == null)
			{
				return null;
			}
			using StreamReader streamReader = new StreamReader(stream);
			return streamReader.ReadToEnd();
		}
	}
	public static class Diagnostics
	{
		private static bool _reported;

		private static bool _emptyReported;

		public static void Reset()
		{
			_reported = false;
			_emptyReported = false;
		}

		public static void ReportEmptyRecommendations(ComfortSnapshot snap)
		{
			if (snap != null && snap.Valid && snap.RecommendationsReady)
			{
				if (snap.Recommendations.Count > 0)
				{
					_emptyReported = false;
				}
				else if (!_emptyReported)
				{
					_emptyReported = true;
					ComfortAuditPlugin.Log.LogInfo((object)("no recommendations at comfort " + snap.ComfortLevel + " [filter=" + PluginConfig.Filter.Value.ToString() + "] " + ((snap.RecStats != null) ? snap.RecStats.ToString() : "(no stats)")));
				}
			}
		}

		public static void ReportOnce(ComfortSnapshot snap)
		{
			if (!_reported && snap != null && snap.Valid && snap.VanillaComfortLevel > 0)
			{
				_reported = true;
				ManualLogSource log = ComfortAuditPlugin.Log;
				log.LogInfo((object)"---- ComfortAudit diagnostics ----");
				log.LogInfo((object)string.Format("SE_Rested: baseTTL={0}s perLevel={1}s source={2}", snap.BaseTTL, snap.TTLPerLevel, snap.TtlFromLiveEffect ? "live effect" : "ObjectDB prefab"));
				log.LogInfo((object)$"comfort={snap.ComfortLevel} (game says {snap.VanillaComfortLevel}) shelter={snap.InShelter} cover={snap.CoverPercentage:0.00} pieces in range={snap.PieceCount} rested={snap.RestedSeconds}s");
				log.LogInfo((object)$"ceiling: {snap.CeilingUnlocked} unlocked, {snap.CeilingAll} with everything (valid={snap.CeilingValid})");
				ReportBreakdown(snap, log);
				ReportCatalogue(log);
				ReportGroups(log);
				ReportCostTokens(log);
				log.LogInfo((object)"---- end diagnostics ----");
			}
		}

		public static void ReportBreakdown(ComfortSnapshot snap, ManualLogSource log)
		{
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			if (snap == null || !snap.Valid)
			{
				log.LogInfo((object)"breakdown: no valid snapshot");
				return;
			}
			log.LogInfo((object)$"breakdown: comfort {snap.ComfortLevel}, shelter {snap.InShelter} (cover {snap.CoverPercentage:0.00}, roof {snap.UnderRoof})");
			for (int i = 0; i < snap.Pieces.Count; i++)
			{
				PieceEntry pieceEntry = snap.Pieces[i];
				log.LogInfo((object)string.Format("  {0,-14} {1,-22} {2,-16} {3}{4}", ComfortGroups.Name(pieceEntry.Group), pieceEntry.DisplayName, pieceEntry.Status, (pieceEntry.Status == PieceStatus.Counted) ? ("+" + pieceEntry.Comfort) : ("beaten by " + (pieceEntry.ShadowedBy ?? "?")), pieceEntry.Inactive ? ("  [unlit, designed " + pieceEntry.RawComfort + "]") : string.Empty));
			}
			if (snap.Recommendations != null)
			{
				for (int j = 0; j < snap.Recommendations.Count; j++)
				{
					Recommendation recommendation = snap.Recommendations[j];
					log.LogInfo((object)$"  rec #{j + 1}: +{recommendation.Gain} {recommendation.DisplayName} [{recommendation.Kind}] cost {recommendation.CostScore:0.0}");
				}
			}
		}

		private static void ReportCatalogue(ManualLogSource log)
		{
			List<PieceCatalog.Entry> list = PieceCatalog.Entries();
			if (list == null)
			{
				log.LogInfo((object)"catalogue: not built yet");
			}
			else
			{
				log.LogInfo((object)("catalogue: " + list.Count + " comfort pieces"));
			}
		}

		private static void ReportGroups(ManualLogSource log)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: 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_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			List<PieceCatalog.Entry> list = PieceCatalog.Entries();
			if (list == null)
			{
				return;
			}
			Dictionary<ComfortGroup, int> dictionary = new Dictionary<ComfortGroup, int>();
			Dictionary<ComfortGroup, int> dictionary2 = new Dictionary<ComfortGroup, int>();
			List<string> list2 = new List<string>();
			for (int i = 0; i < list.Count; i++)
			{
				PieceCatalog.Entry entry = list[i];
				dictionary.TryGetValue(entry.Group, out var value);
				dictionary[entry.Group] = value + 1;
				if (!dictionary2.TryGetValue(entry.Group, out var value2) || entry.Comfort > value2)
				{
					dictionary2[entry.Group] = entry.Comfort;
				}
				if ((int)entry.Group == 0)
				{
					list2.Add(entry.PrefabName + " [" + entry.DisplayName + "] (" + entry.Comfort + ")");
				}
			}
			foreach (KeyValuePair<ComfortGroup, int> item in dictionary)
			{
				log.LogInfo((object)$"  group {item.Key,-12} {item.Value,3} pieces, best comfort {dictionary2[item.Key]}");
			}
			if (list2.Count > 0)
			{
				log.LogInfo((object)("  ungrouped (these stack): " + string.Join(", ", list2.ToArray())));
			}
		}

		private static void ReportCostTokens(ManualLogSource log)
		{
			List<PieceCatalog.Entry> list = PieceCatalog.Entries();
			if (list == null)
			{
				return;
			}
			SortedDictionary<string, int> sortedDictionary = new SortedDictionary<string, int>();
			for (int i = 0; i < list.Count; i++)
			{
				Requirement[] resources = list[i].Piece.m_resources;
				if (resources == null)
				{
					continue;
				}
				for (int j = 0; j < resources.Length; j++)
				{
					string text = PieceCatalog.ItemToken(resources[j]);
					if (!string.IsNullOrEmpty(text))
					{
						sortedDictionary.TryGetValue(text, out var value);
						sortedDictionary[text] = value + 1;
					}
				}
			}
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			foreach (KeyValuePair<string, int> item in sortedDictionary)
			{
				if (CostTable.HasWeight(item.Key))
				{
					list2.Add(item.Key + "=" + CostTable.Weight(item.Key).ToString("0.#"));
				}
				else
				{
					list3.Add(item.Key);
				}
			}
			log.LogInfo((object)("cost tokens in use: " + sortedDictionary.Count + " (" + list2.Count + " weighted, " + list3.Count + " defaulted)"));
			if (list2.Count > 0)
			{
				log.LogInfo((object)("  weighted: " + string.Join(", ", list2.ToArray())));
			}
			if (list3.Count <= 0)
			{
				return;
			}
			log.LogInfo((object)("  NO WEIGHT (scoring 1.0): " + string.Join(", ", list3.ToArray())));
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("  paste-ready: ");
			for (int k = 0; k < list3.Count; k++)
			{
				if (k > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append('"').Append(list3[k]).Append("\": 1.0");
			}
			log.LogInfo((object)stringBuilder.ToString());
		}
	}
	public static class Names
	{
		public static string Display(Piece piece, string prefabName)
		{
			if ((Object)(object)piece == (Object)null)
			{
				return Prettify(prefabName);
			}
			if (string.IsNullOrEmpty(piece.m_name))
			{
				return Prettify(prefabName);
			}
			string text = ((Localization.instance != null) ? Localization.instance.Localize(piece.m_name) : piece.m_name);
			if (!IsUntranslated(text))
			{
				return text;
			}
			return Prettify(prefabName);
		}

		private static bool IsUntranslated(string localized)
		{
			if (!string.IsNullOrEmpty(localized))
			{
				if (localized.Length > 1 && localized[0] == '[')
				{
					return localized[localized.Length - 1] == ']';
				}
				return false;
			}
			return true;
		}

		public static string Prettify(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return "?";
			}
			string text = prefabName;
			if (text.StartsWith("piece_"))
			{
				text = text.Substring(6);
			}
			StringBuilder stringBuilder = new StringBuilder(text.Length + 8);
			bool flag = true;
			for (int i = 0; i < text.Length; i++)
			{
				char c = text[i];
				if (c == '_' || c == '-')
				{
					if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' ')
					{
						stringBuilder.Append(' ');
					}
					flag = true;
					continue;
				}
				if (i > 0 && char.IsUpper(c) && !char.IsUpper(text[i - 1]) && stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' ')
				{
					stringBuilder.Append(' ');
					flag = true;
				}
				stringBuilder.Append(flag ? char.ToUpperInvariant(c) : c);
				flag = false;
			}
			return stringBuilder.ToString().Trim();
		}
	}
	public static class PieceCatalog
	{
		public sealed class Entry
		{
			public Piece Piece;

			public string PrefabName;

			public string DisplayName;

			public string NameToken;

			public ComfortGroup Group;

			public bool GroupKnown;

			public int Comfort;

			public float CostScore;

			public string StationName;

			public bool InBuildMenu;

			public bool Enabled;
		}

		private static List<Entry> _entries;

		private static readonly List<KeyValuePair<GameObject, string>> TableEntries = new List<KeyValuePair<GameObject, string>>();

		public static bool Ready => _entries != null;

		public static int Count
		{
			get
			{
				if (_entries != null)
				{
					return _entries.Count;
				}
				return 0;
			}
		}

		public static bool Available(Entry e, Player player)
		{
			if (!e.InBuildMenu)
			{
				return false;
			}
			if (e.Enabled)
			{
				return true;
			}
			if ((Object)(object)player != (Object)null && (Object)(object)player.CurrentSeason != (Object)null && (Object)(object)e.Piece != (Object)null)
			{
				return player.CurrentSeason.Pieces.Contains(((Component)e.Piece).gameObject);
			}
			return false;
		}

		private static HashSet<string> BuildMenuNames()
		{
			if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items == null)
			{
				return null;
			}
			HashSet<string> hashSet = new HashSet<string>();
			List<string> list = new List<string>();
			List<GameObject> items = ObjectDB.instance.m_items;
			TableEntries.Clear();
			for (int i = 0; i < items.Count; i++)
			{
				GameObject val = items[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				ItemDrop component = val.GetComponent<ItemDrop>();
				if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null)
				{
					continue;
				}
				PieceTable buildPieces = component.m_itemData.m_shared.m_buildPieces;
				if ((Object)(object)buildPieces == (Object)null || buildPieces.m_pieces == null)
				{
					continue;
				}
				int num = 0;
				for (int j = 0; j < buildPieces.m_pieces.Count; j++)
				{
					GameObject val2 = buildPieces.m_pieces[j];
					if (!((Object)(object)val2 == (Object)null))
					{
						if (hashSet.Add(((Object)val2).name))
						{
							num++;
						}
						TableEntries.Add(new KeyValuePair<GameObject, string>(val2, ((Object)val).name));
					}
				}
				list.Add(((Object)val).name + ":" + buildPieces.m_pieces.Count + ((num < buildPieces.m_pieces.Count) ? ("(+" + num + ")") : ""));
			}
			if (hashSet.Count == 0)
			{
				return null;
			}
			ComfortAuditPlugin.Log.LogInfo((object)("build tables: " + string.Join(", ", list.ToArray())));
			return hashSet;
		}

		private static string NearestTableEntries(Entry e)
		{
			List<string> list = new List<string>();
			for (int i = 0; i < TableEntries.Count; i++)
			{
				GameObject key = TableEntries[i].Key;
				string text = null;
				if ((Object)(object)e.Piece != (Object)null && key == ((Component)e.Piece).gameObject)
				{
					text = "same object";
				}
				else if (string.Equals(((Object)key).name, e.PrefabName, StringComparison.OrdinalIgnoreCase))
				{
					text = "name differs by case";
				}
				else
				{
					Piece component = key.GetComponent<Piece>();
					if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(e.NameToken) && component.m_name == e.NameToken)
					{
						text = "same token";
					}
				}
				if (text != null)
				{
					list.Add(((Object)key).name + " in " + TableEntries[i].Value + " (" + text + ")");
				}
			}
			if (list.Count != 0)
			{
				return string.Join("; ", list.ToArray());
			}
			return "nothing similar in any table";
		}

		public static void Invalidate()
		{
			_entries = null;
			Ceiling.Invalidate();
		}

		public static List<Entry> Entries()
		{
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			if (_entries != null)
			{
				return _entries;
			}
			if ((Object)(object)ZNetScene.instance == (Object)null || ZNetScene.instance.m_prefabs == null)
			{
				return null;
			}
			List<Entry> list = new List<Entry>(128);
			List<GameObject> prefabs = ZNetScene.instance.m_prefabs;
			HashSet<string> hashSet = BuildMenuNames();
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			for (int i = 0; i < prefabs.Count; i++)
			{
				GameObject val = prefabs[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Piece component = val.GetComponent<Piece>();
				if (!((Object)(object)component == (Object)null) && component.m_comfort > 0)
				{
					bool flag = hashSet?.Contains(((Object)val).name) ?? true;
					if (!flag)
					{
						list2.Add(((Object)val).name);
					}
					if (!component.m_enabled)
					{
						list3.Add(((Object)val).name);
					}
					list.Add(new Entry
					{
						Piece = component,
						PrefabName = ((Object)val).name,
						DisplayName = Names.Display(component, ((Object)val).name),
						NameToken = component.m_name,
						Group = component.m_comfortGroup,
						GroupKnown = ComfortGroups.IsKnown(component.m_comfortGroup),
						Comfort = component.m_comfort,
						CostScore = ScoreCost(component),
						StationName = (((Object)(object)component.m_craftingStation != (Object)null) ? component.m_craftingStation.m_name : null),
						InBuildMenu = flag,
						Enabled = component.m_enabled
					});
				}
			}
			_entries = list;
			ComfortAuditPlugin.Log.LogInfo((object)("catalogued " + list.Count + " comfort pieces from " + prefabs.Count + " prefabs (" + list2.Count + " in no build menu, " + list3.Count + " disabled/seasonal" + ((hashSet == null) ? ", build menu unavailable" : "") + ")"));
			if (list2.Count > 0)
			{
				ComfortAuditPlugin.Log.LogInfo((object)("  not in any build menu: " + string.Join(", ", list2.ToArray())));
				for (int j = 0; j < list.Count; j++)
				{
					if (!list[j].InBuildMenu)
					{
						ComfortAuditPlugin.Log.LogInfo((object)("    " + list[j].PrefabName + " [" + list[j].NameToken + "]: " + NearestTableEntries(list[j])));
					}
				}
			}
			if (list3.Count > 0)
			{
				ComfortAuditPlugin.Log.LogInfo((object)("  disabled unless in season: " + string.Join(", ", list3.ToArray())));
			}
			return _entries;
		}

		private static float ScoreCost(Piece piece)
		{
			if (piece.m_resources == null)
			{
				return 0f;
			}
			float num = 0f;
			for (int i = 0; i < piece.m_resources.Length; i++)
			{
				Requirement val = piece.m_resources[i];
				if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0)
				{
					num += (float)val.m_amount * CostTable.Weight(ItemToken(val));
				}
			}
			return num;
		}

		public static string ItemToken(Requirement req)
		{
			if (req == null || (Object)(object)req.m_resItem == (Object)null || req.m_resItem.m_itemData == null || req.m_resItem.m_itemData.m_shared == null)
			{
				return null;
			}
			return req.m_resItem.m_itemData.m_shared.m_name;
		}

		public static int MaterialCount(Piece piece)
		{
			if (piece.m_resources == null)
			{
				return 0;
			}
			int num = 0;
			for (int i = 0; i < piece.m_resources.Length; i++)
			{
				Requirement val = piece.m_resources[i];
				if (val != null && (Object)(object)val.m_resItem != (Object)null && val.m_amount > 0)
				{
					num++;
				}
			}
			return num;
		}
	}
	public static class PlacementPreview
	{
		private static readonly List<ComfortWalk.Item> Items = new List<ComfortWalk.Item>(128);

		private static readonly List<Piece> Buffer = new List<Piece>(128);

		private static readonly List<ComfortWalk.Outcome> Outcomes = new List<ComfortWalk.Outcome>(128);

		private const int GhostSource = -2;

		private static readonly PlacementPreviewResult None = new PlacementPreviewResult();

		private static PlacementPreviewResult _memo;

		private static int _memoGhostId;

		private static Vector3 _memoGhostPos;

		private static Vector3 _memoPlayerPos;

		private static int _memoRevision = -1;

		private static bool _memoShelter;

		private const float MoveEpsilonSq = 0.0025000002f;

		public static PlacementPreviewResult Compute(Player player, ComfortSnapshot snap)
		{
			//IL_005d: 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)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: 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_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || snap == null || !snap.Valid)
			{
				return None;
			}
			GameObject placementGhost = player.m_placementGhost;
			if ((Object)(object)placementGhost == (Object)null || !placementGhost.activeInHierarchy)
			{
				return None;
			}
			Piece component = placementGhost.GetComponent<Piece>();
			if ((Object)(object)component == (Object)null || component.m_comfort <= 0)
			{
				return None;
			}
			Vector3 position = ((Component)player).transform.position;
			Vector3 position2 = placementGhost.transform.position;
			int instanceID = ((Object)placementGhost).GetInstanceID();
			if (_memo != null && instanceID == _memoGhostId && SnapshotService.Revision == _memoRevision && snap.InShelter == _memoShelter)
			{
				Vector3 val = position2 - _memoGhostPos;
				if (((Vector3)(ref val)).sqrMagnitude < 0.0025000002f)
				{
					val = position - _memoPlayerPos;
					if (((Vector3)(ref val)).sqrMagnitude < 0.0025000002f)
					{
						return _memo;
					}
				}
			}
			PlacementPreviewResult placementPreviewResult = (_memo = new PlacementPreviewResult());
			_memoGhostId = instanceID;
			_memoGhostPos = position2;
			_memoPlayerPos = position;
			_memoRevision = SnapshotService.Revision;
			_memoShelter = snap.InShelter;
			placementPreviewResult.Active = true;
			placementPreviewResult.DisplayName = Names.Display(component, PrefabName(((Object)placementGhost).name));
			placementPreviewResult.Group = component.m_comfortGroup;
			placementPreviewResult.GroupKnown = ComfortGroups.IsKnown(component.m_comfortGroup);
			placementPreviewResult.PieceComfort = component.m_comfort;
			placementPreviewResult.Distance = Vector3.Distance(position, position2);
			placementPreviewResult.InRange = placementPreviewResult.Distance < 10f;
			if (!placementPreviewResult.InRange)
			{
				return placementPreviewResult;
			}
			Items.Clear();
			Buffer.Clear();
			Piece.GetAllComfortPiecesInRadius(position, 10f, Buffer);
			for (int i = 0; i < Buffer.Count; i++)
			{
				Piece val2 = Buffer[i];
				Items.Add(new ComfortWalk.Item
				{
					Group = val2.m_comfortGroup,
					Comfort = val2.GetComfort(),
					NameToken = val2.m_name,
					Source = i
				});
			}
			int num = ComfortWalk.Run(Items, snap.InShelter);
			Items.Add(new ComfortWalk.Item
			{
				Group = component.m_comfortGroup,
				Comfort = component.m_comfort,
				NameToken = component.m_name,
				Source = -2
			});
			Outcomes.Clear();
			placementPreviewResult.NewTotal = ComfortWalk.Run(Items, snap.InShelter, Outcomes);
			placementPreviewResult.Delta = placementPreviewResult.NewTotal - num;
			if (placementPreviewResult.Delta <= 0)
			{
				placementPreviewResult.BlockedBy = FindBlocker(Buffer);
			}
			return placementPreviewResult;
		}

		private static string PrefabName(string ghostName)
		{
			if (ghostName != null && ghostName.EndsWith("(Clone)"))
			{
				return ghostName.Substring(0, ghostName.Length - "(Clone)".Length);
			}
			return ghostName;
		}

		private static string FindBlocker(List<Piece> real)
		{
			for (int i = 0; i < Outcomes.Count; i++)
			{
				ComfortWalk.Outcome outcome = Outcomes[i];
				if (outcome.Source == -2 && outcome.Status != PieceStatus.Counted)
				{
					int shadowedBySource = outcome.ShadowedBySource;
					if (shadowedBySource >= 0 && shadowedBySource < real.Count)
					{
						return Names.Display(real[shadowedBySource], ((Object)(object)((Component)real[shadowedBySource]).gameObject != (Object)null) ? ((Object)((Component)real[shadowedBySource]).gameObject).name : null);
					}
					return null;
				}
			}
			return null;
		}
	}
	public static class Recommender
	{
		private static readonly List<ComfortWalk.Item> BaseItems = new List<ComfortWalk.Item>(128);

		publ