Decompiled source of Larder v0.2.0

plugins/Larder/Larder.dll

Decompiled 4 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 HarmonyLib;
using Larder.Core;
using Larder.Core.Model;
using Larder.UI;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
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("Larder")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("A food planner in the inventory screen: the best three foods you own and the one dish to cook next.")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+cfd8e02cf086a3d9959c4ec6b0d5fbaa5124d757")]
[assembly: AssemblyProduct("Larder")]
[assembly: AssemblyTitle("Larder")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.0.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;
		}
	}
}
internal sealed class ConfigurationManagerAttributes
{
	public int? Order;

	public bool? IsAdvanced;

	public bool? Browsable;

	public bool? ReadOnly;
}
namespace Larder
{
	[BepInPlugin("com.jumpingmushroom.larder", "Larder", "0.2.0")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim.x86_64")]
	public sealed class LarderPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.jumpingmushroom.larder";

		public const string PluginName = "Larder";

		public const string PluginVersion = "0.2.0";

		internal static ManualLogSource Log;

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

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			ConsoleCommands.Register();
			_harmony = new Harmony("com.jumpingmushroom.larder");
			_harmony.PatchAll(typeof(LarderPlugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Larder 0.2.0 loaded.");
		}

		private void Update()
		{
			Runtime.Tick();
		}

		private void OnDestroy()
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}

		internal static void WarnOnce(string key, Exception e)
		{
			if (Warned.Add(key))
			{
				Log.LogWarning((object)(key + ": " + e));
			}
		}
	}
	public enum PanelPlacement
	{
		Auto,
		Below,
		Manual
	}
	public static class PluginConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<float> Radius;

		public static ConfigEntry<bool> ShowUndiscovered;

		public static ConfigEntry<bool> IncludeCartsAndShips;

		public static ConfigEntry<bool> PanelOpen;

		public static ConfigEntry<PanelPlacement> Placement;

		public static ConfigEntry<KeyboardShortcut> ToggleKey;

		public static ConfigEntry<float> Scale;

		public static ConfigEntry<float> OffsetX;

		public static ConfigEntry<float> OffsetY;

		public static ConfigEntry<bool> Verbose;

		private static ConfigurationManagerAttributes Attr(int order, bool advanced = false, bool browsable = true)
		{
			return new ConfigurationManagerAttributes
			{
				Order = order,
				IsAdvanced = advanced,
				Browsable = browsable
			};
		}

		public static void Bind(ConfigFile cfg)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Expected O, but got Unknown
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Expected O, but got Unknown
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Expected O, but got Unknown
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Expected O, but got Unknown
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: 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_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Expected O, but got Unknown
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0277: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("General", "Enabled", true, new ConfigDescription("Master switch. Off hides the Larder button and panel.", (AcceptableValueBase)null, new object[1] { Attr(100) }));
			Radius = cfg.Bind<float>("General", "Radius", 20f, new ConfigDescription("Metres to search for chests, placed feasts and cooking stations.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 50f), new object[1] { Attr(95) }));
			ShowUndiscovered = cfg.Bind<bool>("General", "ShowUndiscovered", false, new ConfigDescription("Allow cook suggestions for recipes you haven't discovered yet, and name ingredients you haven't seen. Food you own is always shown.", (AcceptableValueBase)null, new object[1] { Attr(90) }));
			IncludeCartsAndShips = cfg.Bind<bool>("General", "IncludeCartsAndShips", true, new ConfigDescription("Count carts and ship holds within range as containers.", (AcceptableValueBase)null, new object[1] { Attr(85) }));
			PanelOpen = cfg.Bind<bool>("UI", "PanelOpen", true, new ConfigDescription("Whether the panel is open. The Larder button toggles this.", (AcceptableValueBase)null, new object[1] { Attr(80) }));
			Placement = cfg.Bind<PanelPlacement>("UI", "Placement", PanelPlacement.Auto, new ConfigDescription("Where the panel goes. Auto: right of the inventory and anything other mods put beside it. Below: under the chest window (or the inventory when no chest is open). Manual: at OffsetX/OffsetY from the top-left of the screen (negative OffsetY moves down). OffsetX/OffsetY nudge the Auto and Below positions too.", (AcceptableValueBase)null, new object[1] { Attr(79) }));
			ToggleKey = cfg.Bind<KeyboardShortcut>("UI", "ToggleKey", KeyboardShortcut.Empty, new ConfigDescription("Optional key that toggles the panel while the inventory is open.", (AcceptableValueBase)null, new object[1] { Attr(78) }));
			Scale = cfg.Bind<float>("UI", "Scale", 1f, new ConfigDescription("Size of the panel.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.6f, 1.6f), new object[1] { Attr(76) }));
			OffsetX = cfg.Bind<float>("UI", "OffsetX", 0f, new ConfigDescription("Horizontal nudge in pixels (positive is right).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1500f, 1500f), new object[1] { Attr(74) }));
			OffsetY = cfg.Bind<float>("UI", "OffsetY", 0f, new ConfigDescription("Vertical nudge in pixels (positive is up).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1000f, 1000f), new object[1] { Attr(72) }));
			Verbose = cfg.Bind<bool>("Logging", "Verbose", false, new ConfigDescription("Log plan timing to the BepInEx log.", (AcceptableValueBase)null, new object[1] { Attr(5, advanced: true) }));
		}
	}
}
namespace Larder.UI
{
	internal sealed class ComboRow
	{
		public RectTransform Root;

		public Image Icon;

		public TextMeshProUGUI Text;

		public Button Eat;

		public string FoodId;

		public static ComboRow Create(Transform parent, Button template, int index)
		{
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Expected O, but got Unknown
			ComboRow r = new ComboRow();
			r.Root = UiUtil.Rect("Row" + index, parent);
			HorizontalLayoutGroup obj = ((Component)r.Root).gameObject.AddComponent<HorizontalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f;
			((LayoutGroup)obj).childAlignment = (TextAnchor)3;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			RectTransform val = UiUtil.Rect("Icon", (Transform)(object)r.Root);
			r.Icon = ((Component)val).gameObject.AddComponent<Image>();
			r.Icon.preserveAspect = true;
			((Graphic)r.Icon).raycastTarget = false;
			UiUtil.Size(((Component)val).gameObject, 40f, 40f);
			r.Text = UiUtil.Text((Transform)(object)r.Root, "Text", 15f, (TextAlignmentOptions)513);
			((Component)r.Text).gameObject.AddComponent<LayoutElement>().flexibleWidth = 1f;
			r.Eat = UiUtil.Button((Transform)(object)r.Root, "Eat", template, "Eat", (UnityAction)delegate
			{
				Eater.Eat(r.FoodId);
			});
			UiUtil.Size(((Component)r.Eat).gameObject, 64f, 30f);
			((Component)r.Eat).gameObject.SetActive(false);
			return r;
		}
	}
	internal static class Eater
	{
		public static ItemData FindInBag(Player p, string foodId)
		{
			if ((Object)(object)p == (Object)null || string.IsNullOrEmpty(foodId) || FoodCatalog.FeastFood.ContainsKey(foodId))
			{
				return null;
			}
			foreach (ItemData allItem in ((Humanoid)p).GetInventory().GetAllItems())
			{
				if (allItem != null && allItem.m_shared != null && allItem.m_shared.m_name == foodId)
				{
					return allItem;
				}
			}
			return null;
		}

		public static bool CanEatNow(Player p, string foodId)
		{
			ItemData val = FindInBag(p, foodId);
			if (val != null)
			{
				return p.CanEat(val, false);
			}
			return false;
		}

		public static void Eat(string foodId)
		{
			Player localPlayer = Player.m_localPlayer;
			ItemData val = FindInBag(localPlayer, foodId);
			if (val != null)
			{
				((Humanoid)localPlayer).UseItem(((Humanoid)localPlayer).GetInventory(), val, true);
				Runtime.Refresh();
			}
		}

		public static void EatAll()
		{
			try
			{
				Player localPlayer = Player.m_localPlayer;
				PlanView last = Runtime.Last;
				if ((Object)(object)localPlayer != (Object)null && last != null && last.Combo.Count > 0)
				{
					EatLoop(localPlayer, last.Combo);
				}
			}
			catch (Exception e)
			{
				LarderPlugin.WarnOnce("Larder: eat all failed", e);
			}
			Runtime.Refresh();
		}

		private static void EatLoop(Player p, List<FoodStats> plan)
		{
			for (int i = 0; i < 3; i++)
			{
				List<ActiveFood> active = ActiveFoods.Read(p);
				ISet<string> inBag = BagFoods(p);
				List<FoodStats> list = EatOrder.Next(plan, active, inBag, Game.m_foodRate);
				if (list.Count != 0)
				{
					string id = list[0].Id;
					ItemData val = FindInBag(p, id);
					if (val != null && p.CanEat(val, false))
					{
						int stack = val.m_stack;
						((Humanoid)p).UseItem(((Humanoid)p).GetInventory(), val, true);
						if (((Humanoid)p).GetInventory().ContainsItem(val) && val.m_stack >= stack)
						{
							break;
						}
						continue;
					}
					break;
				}
				break;
			}
		}

		private static HashSet<string> BagFoods(Player p)
		{
			HashSet<string> hashSet = new HashSet<string>();
			foreach (ItemData allItem in ((Humanoid)p).GetInventory().GetAllItems())
			{
				if (allItem != null && allItem.m_shared != null)
				{
					string name = allItem.m_shared.m_name;
					if (FoodCatalog.Foods.ContainsKey(name) && !FoodCatalog.FeastFood.ContainsKey(name))
					{
						hashSet.Add(name);
					}
				}
			}
			return hashSet;
		}
	}
	internal static class Format
	{
		public const string Hp = "#ff8080";

		public const string St = "#ffe080";

		public const string Eitr = "#9090ff";

		public const string Time = "#ffa040";

		public const string Dim = "#b0b0b0";

		public const string Good = "#80e080";

		public const string Bad = "#ff7070";

		public static string C(string color, string text)
		{
			return "<color=" + color + ">" + text + "</color>";
		}

		public static string Stats(float hp, float st, float eitr)
		{
			string text = C("#ff8080", hp.ToString("0") + " hp") + "  " + C("#ffe080", st.ToString("0") + " st");
			if (!(eitr > 0f))
			{
				return text;
			}
			return text + "  " + C("#9090ff", eitr.ToString("0") + " eitr");
		}

		public static string Row(PlannedSlot slot, PlanView v)
		{
			FoodStats food = slot.Food;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("<b>").Append(food.Name).Append("</b>\n");
			stringBuilder.Append(Stats(food.Health, food.Stamina, food.Eitr)).Append("  ").Append(C("#ffa040", Labels.Duration(food.BurnTime)));
			string text = Buff(food.Id);
			if (text != null)
			{
				stringBuilder.Append("  ").Append(C("#b0b0b0", "+ " + text));
			}
			stringBuilder.Append("\n<size=85%>").Append(C("#b0b0b0", Where(v.Snapshot, food.Id) + " · " + State(slot))).Append("</size>");
			return stringBuilder.ToString();
		}

		public static string State(PlannedSlot slot)
		{
			return slot.State switch
			{
				SlotState.Active => "active, " + Labels.Duration(slot.Seconds) + " left", 
				SlotState.RefreshNow => C("#80e080", "can refresh now"), 
				SlotState.EatLater => "eat in " + Labels.Duration(slot.Seconds), 
				SlotState.RefreshFirst => "refresh " + FoodCatalog.ItemName(slot.BlockedBy) + " first", 
				_ => C("#80e080", "eat now"), 
			};
		}

		public static string Totals(PlanView v)
		{
			return "<b>Plan</b>  " + Stats(v.Planned.Health, v.Planned.Stamina, v.Planned.Eitr) + "  " + C("#b0b0b0", "regen " + v.Planned.Regen.ToString("0")) + "\n<size=85%>" + C("#b0b0b0", "now " + v.Now.Health.ToString("0") + " hp  " + v.Now.Stamina.ToString("0") + " st" + ((v.Now.Eitr > 0f) ? ("  " + v.Now.Eitr.ToString("0") + " eitr") : "")) + "</size>";
		}

		public static string Others(PlanView v)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (OtherFood other in v.Slots.Others)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append('\n');
				}
				stringBuilder.Append(C("#b0b0b0", FoodCatalog.ItemName(other.Id) + " isn't in the plan: " + ((other.SecondsUntilFree <= 0f) ? "replaceable now" : ("slot frees in " + Labels.Duration(other.SecondsUntilFree)))));
			}
			return stringBuilder.ToString();
		}

		private static string Where(Snapshot s, string id)
		{
			if (s == null || !s.FoodSources.TryGetValue(id, out var value) || value.Count == 0)
			{
				return "?";
			}
			foreach (Source item in value)
			{
				if (item.Kind != SourceKind.Eaten)
				{
					return item.Label;
				}
			}
			return value[0].Label;
		}

		private static string Buff(string foodId)
		{
			if (!FoodCatalog.Items.TryGetValue(foodId, out var value) || (Object)(object)value.m_shared.m_consumeStatusEffect == (Object)null)
			{
				return null;
			}
			return FoodCatalog.ItemName(value.m_shared.m_consumeStatusEffect.m_name);
		}

		public static string Cook(PlanView v)
		{
			if (v.Cook.Ready == null && v.Cook.Almost == null)
			{
				if (v.Combo.Count <= 0)
				{
					return "";
				}
				return C("#b0b0b0", "Nothing you can cook improves this combo.");
			}
			StringBuilder stringBuilder = new StringBuilder();
			if (v.Cook.Ready != null)
			{
				stringBuilder.Append("<b>Cook next</b>\n");
				Suggestion(stringBuilder, v.Cook.Ready, v);
			}
			if (v.Cook.Almost != null)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append('\n');
				}
				stringBuilder.Append("<b>If you had…</b>\n");
				Suggestion(stringBuilder, v.Cook.Almost, v);
			}
			return stringBuilder.ToString();
		}

		private static void Suggestion(StringBuilder sb, CookSuggestion c, PlanView v)
		{
			Totals totals = Larder.Core.Model.Totals.Of(c.Combo, v.Planned.Health - Sum(v.Combo, 0), v.Planned.Stamina - Sum(v.Combo, 1));
			sb.Append(c.Dish.Food.Name).Append("  →  ").Append(Stats(totals.Health, totals.Stamina, totals.Eitr))
				.Append(C("#b0b0b0", "  (" + Delta(totals.Health - v.Planned.Health, "hp") + Delta(totals.Stamina - v.Planned.Stamina, "st") + Delta(totals.Eitr - v.Planned.Eitr, "eitr") + ")"))
				.Append('\n');
			Producer via = c.Plan.Root.Via;
			sb.Append("<indent=1em>").Append(Station(via, c.Plan.Root.StationMissing)).Append("</indent>\n");
			Steps(sb, c.Plan.Root, 1);
			foreach (StationNeed missingStation in c.Plan.MissingStations)
			{
				if (missingStation.StationId != via.StationId)
				{
					StringBuilder stringBuilder = sb.Append("<indent=1em>");
					string text = FoodCatalog.StationName(missingStation.StationId);
					int level = missingStation.Level;
					stringBuilder.Append(C("#ff7070", "needs " + text + " level " + level)).Append("</indent>\n");
				}
			}
		}

		private static float Sum(List<FoodStats> foods, int stat)
		{
			float num = 0f;
			foreach (FoodStats food in foods)
			{
				num += ((stat == 0) ? food.Health : food.Stamina);
			}
			return num;
		}

		private static string Delta(float d, string unit)
		{
			if (d > -0.5f && d < 0.5f)
			{
				return "";
			}
			return ((d > 0f) ? "+" : "") + d.ToString("0") + " " + unit + " ";
		}

		private static string Station(Producer p, bool missing)
		{
			string text = FoodCatalog.StationName(p.StationId);
			if (p.StationId.Length > 0 && p.StationLevel > 1)
			{
				string text2 = text;
				int stationLevel = p.StationLevel;
				text = text2 + " level " + stationLevel;
			}
			if (!missing)
			{
				return C("#80e080", "at " + text);
			}
			return C("#ff7070", "at " + text + " (not in range)");
		}

		private static void Steps(StringBuilder sb, CookStep step, int depth)
		{
			foreach (CookStep input in step.Inputs)
			{
				string color = ((input.Missing > 0) ? "#ff7070" : ((input.Via != null) ? "#ffe080" : "#80e080"));
				sb.Append("<indent=").Append(depth + 1).Append("em>")
					.Append(C(color, FoodCatalog.ItemName(input.ItemId) + " " + (input.Need - input.Missing) + "/" + input.Need));
				if (input.Via != null)
				{
					sb.Append(C("#b0b0b0", "  make " + (input.Need - input.FromStock) + " " + Station(input.Via, input.StationMissing)));
				}
				sb.Append("</indent>\n");
				if (input.Via != null)
				{
					Steps(sb, input, depth + 1);
				}
			}
		}
	}
	internal static class LarderPanel
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__EatAll;

			public static UnityAction <1>__Toggle;
		}

		private const float Width = 380f;

		private const float MinBgAlpha = 0.94f;

		private const float SettleTime = 0.5f;

		private static readonly Goal[] Goals = new Goal[4]
		{
			Goal.Balanced,
			Goal.Health,
			Goal.Stamina,
			Goal.Eitr
		};

		private static readonly Color On = new Color(1f, 0.85f, 0.4f, 1f);

		private static readonly Color Off = new Color(0.7f, 0.7f, 0.7f, 1f);

		private static RectTransform _root;

		private static Button _toggle;

		private static readonly Button[] Tabs = (Button[])(object)new Button[4];

		private static readonly ComboRow[] Rows = new ComboRow[3];

		private static Button _eatAll;

		private static TextMeshProUGUI _eatAllLabel;

		private static TextMeshProUGUI _totals;

		private static TextMeshProUGUI _cook;

		private static TextMeshProUGUI _status;

		private static bool _hooked;

		private static InventoryGui _gui;

		private static float _settleUntil;

		public static bool Visible
		{
			get
			{
				if ((Object)(object)_root != (Object)null && ((Component)_root).gameObject.activeSelf)
				{
					return InventoryGui.IsVisible();
				}
				return false;
			}
		}

		internal static ComboRow[] ComboRows => Rows;

		internal static TextMeshProUGUI CookText => _cook;

		public static void OnShow(InventoryGui gui)
		{
			if ((Object)(object)_root == (Object)null)
			{
				Build(gui);
			}
			if (!((Object)(object)_root == (Object)null))
			{
				bool value = PluginConfig.Enabled.Value;
				((Component)_toggle).gameObject.SetActive(value);
				((Component)_root).gameObject.SetActive(value && PluginConfig.PanelOpen.Value);
				_settleUntil = Time.unscaledTime + 0.5f;
				ApplyLayout();
				if (Visible)
				{
					Runtime.Refresh();
				}
			}
		}

		public static void Toggle()
		{
			if (!((Object)(object)_root == (Object)null))
			{
				PluginConfig.PanelOpen.Value = !PluginConfig.PanelOpen.Value;
				((Component)_root).gameObject.SetActive(PluginConfig.PanelOpen.Value);
				if (Visible)
				{
					Runtime.Refresh();
				}
			}
		}

		public static void Settle()
		{
			if (Visible && Time.unscaledTime < _settleUntil)
			{
				ApplyLayout();
			}
		}

		public static void ApplyLayout()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_root == (Object)null)
			{
				return;
			}
			try
			{
				((Transform)_root).localScale = Vector3.one * PluginConfig.Scale.Value;
				if (Visible && (Object)(object)_gui != (Object)null)
				{
					Placement.Apply(_root, _gui, ((Object)(object)_toggle != (Object)null) ? ((Component)_toggle).transform : null);
				}
			}
			catch (Exception e)
			{
				LarderPlugin.WarnOnce("Larder: panel layout failed", e);
			}
		}

		public static void ShowError(string message)
		{
			try
			{
				if ((Object)(object)_status != (Object)null)
				{
					((TMP_Text)_status).text = Format.C("#ff7070", message);
				}
			}
			catch (Exception)
			{
			}
		}

		private static void Build(InventoryGui gui)
		{
			RectTransform player = gui.m_player;
			if ((Object)(object)player == (Object)null)
			{
				LarderPlugin.Log.LogWarning((object)"Larder: no player inventory block to attach to; use the larder console command.");
				return;
			}
			try
			{
				BuildInternal(gui, player);
				_gui = gui;
			}
			catch (Exception e)
			{
				if ((Object)(object)_root != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)_root).gameObject);
					_root = null;
				}
				if ((Object)(object)_toggle != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)_toggle).gameObject);
					_toggle = null;
				}
				LarderPlugin.WarnOnce("Larder: panel build failed", e);
			}
		}

		private static void BuildInternal(InventoryGui gui, RectTransform host)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: 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)
			//IL_0083: 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_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Expected O, but got Unknown
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: 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_00dd: 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_0220: Unknown result type (might be due to invalid IL or missing references)
			//IL_022a: Expected O, but got Unknown
			//IL_0316: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0321: Expected O, but got Unknown
			//IL_042e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0433: Unknown result type (might be due to invalid IL or missing references)
			//IL_0434: Unknown result type (might be due to invalid IL or missing references)
			//IL_0446: Unknown result type (might be due to invalid IL or missing references)
			//IL_044d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0454: Unknown result type (might be due to invalid IL or missing references)
			//IL_045f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0469: Unknown result type (might be due to invalid IL or missing references)
			//IL_0474: Unknown result type (might be due to invalid IL or missing references)
			//IL_0488: Unknown result type (might be due to invalid IL or missing references)
			//IL_040f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0414: Unknown result type (might be due to invalid IL or missing references)
			//IL_041a: Expected O, but got Unknown
			Transform inventoryRoot = gui.m_inventoryRoot;
			RectTransform val = (RectTransform)(object)((inventoryRoot is RectTransform) ? inventoryRoot : null);
			_root = UiUtil.Rect("LarderPanel", (Transform)(object)(((Object)(object)val != (Object)null) ? val : host));
			((Transform)_root).SetAsLastSibling();
			RectTransform root = _root;
			RectTransform root2 = _root;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0.5f, 0.5f);
			root2.anchorMax = val2;
			root.anchorMin = val2;
			_root.pivot = new Vector2(0f, 1f);
			_root.sizeDelta = new Vector2(380f, 0f);
			Image val3 = ((Component)_root).gameObject.AddComponent<Image>();
			Image component = ((Component)host).GetComponent<Image>();
			if ((Object)(object)component != (Object)null && (Object)(object)component.sprite != (Object)null)
			{
				val3.sprite = component.sprite;
				val3.type = component.type;
				Color color = ((Graphic)component).color;
				color.a = Mathf.Max(color.a, 0.94f);
				((Graphic)val3).color = color;
			}
			else
			{
				val3.sprite = UiUtil.White;
				((Graphic)val3).color = new Color(0.1f, 0.09f, 0.08f, 0.94f);
			}
			VerticalLayoutGroup obj = ((Component)_root).gameObject.AddComponent<VerticalLayoutGroup>();
			((LayoutGroup)obj).padding = new RectOffset(14, 14, 12, 12);
			((HorizontalOrVerticalLayoutGroup)obj).spacing = 6f;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			((Component)_root).gameObject.AddComponent<ContentSizeFitter>().verticalFit = (FitMode)2;
			RectTransform val4 = UiUtil.Rect("Goals", (Transform)(object)_root);
			HorizontalLayoutGroup obj2 = ((Component)val4).gameObject.AddComponent<HorizontalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)obj2).spacing = 4f;
			((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = true;
			for (int i = 0; i < Goals.Length; i++)
			{
				Goal g = Goals[i];
				Tabs[i] = UiUtil.Button((Transform)(object)val4, "Goal" + g, gui.m_craftButton, g.ToString(), (UnityAction)delegate
				{
					SetGoal(g);
				});
				UiUtil.Size(((Component)Tabs[i]).gameObject, 80f, 28f);
			}
			RectTransform obj3 = UiUtil.Rect("ComboHeader", (Transform)(object)_root);
			HorizontalLayoutGroup obj4 = ((Component)obj3).gameObject.AddComponent<HorizontalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)obj4).spacing = 8f;
			((LayoutGroup)obj4).childAlignment = (TextAnchor)3;
			((HorizontalOrVerticalLayoutGroup)obj4).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj4).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj4).childForceExpandWidth = false;
			((HorizontalOrVerticalLayoutGroup)obj4).childForceExpandHeight = false;
			LayoutElement obj5 = ((Component)obj3).gameObject.AddComponent<LayoutElement>();
			float minHeight = (obj5.preferredHeight = 28f);
			obj5.minHeight = minHeight;
			TextMeshProUGUI obj6 = UiUtil.Text((Transform)(object)obj3, "ComboHeaderText", 17f, (TextAlignmentOptions)513);
			((TMP_Text)obj6).text = "<b>Best combo</b>";
			((Component)obj6).gameObject.AddComponent<LayoutElement>().flexibleWidth = 1f;
			Button craftButton = gui.m_craftButton;
			object obj7 = <>O.<0>__EatAll;
			if (obj7 == null)
			{
				UnityAction val5 = Eater.EatAll;
				<>O.<0>__EatAll = val5;
				obj7 = (object)val5;
			}
			_eatAll = UiUtil.Button((Transform)(object)obj3, "EatAll", craftButton, "Eat N", (UnityAction)obj7);
			UiUtil.Size(((Component)_eatAll).gameObject, 72f, 28f);
			_eatAllLabel = ((Component)_eatAll).GetComponentInChildren<TextMeshProUGUI>(true);
			((Component)_eatAll).gameObject.SetActive(false);
			for (int num2 = 0; num2 < Rows.Length; num2++)
			{
				Rows[num2] = ComboRow.Create((Transform)(object)_root, gui.m_craftButton, num2);
			}
			_totals = UiUtil.Text((Transform)(object)_root, "Totals", 15f, (TextAlignmentOptions)513);
			_cook = UiUtil.Text((Transform)(object)_root, "Cook", 14f, (TextAlignmentOptions)257);
			_status = UiUtil.Text((Transform)(object)_root, "Status", 14f, (TextAlignmentOptions)513);
			Button craftButton2 = gui.m_craftButton;
			object obj8 = <>O.<1>__Toggle;
			if (obj8 == null)
			{
				UnityAction val6 = Toggle;
				<>O.<1>__Toggle = val6;
				obj8 = (object)val6;
			}
			_toggle = UiUtil.Button((Transform)(object)host, "LarderToggle", craftButton2, "Larder", (UnityAction)obj8);
			RectTransform val7 = (RectTransform)((Component)_toggle).transform;
			((Vector2)(ref val2))..ctor(1f, 1f);
			val7.anchorMax = val2;
			val7.anchorMin = val2;
			val7.pivot = new Vector2(1f, 0f);
			val7.sizeDelta = new Vector2(90f, 30f);
			val7.anchoredPosition = new Vector2(0f, 4f);
			if (!_hooked)
			{
				_hooked = true;
				PluginConfig.Scale.SettingChanged += delegate
				{
					ApplyLayout();
				};
				PluginConfig.OffsetX.SettingChanged += delegate
				{
					ApplyLayout();
				};
				PluginConfig.OffsetY.SettingChanged += delegate
				{
					ApplyLayout();
				};
				PluginConfig.Placement.SettingChanged += delegate
				{
					ApplyLayout();
				};
				PluginConfig.Radius.SettingChanged += delegate
				{
					Runtime.RequestRefresh();
				};
				PluginConfig.ShowUndiscovered.SettingChanged += delegate
				{
					Runtime.RequestRefresh();
				};
				PluginConfig.IncludeCartsAndShips.SettingChanged += delegate
				{
					Runtime.RequestRefresh();
				};
			}
		}

		private static void SetGoal(Goal g)
		{
			GoalStore.Set(Player.m_localPlayer, g);
			Runtime.Refresh();
		}

		public static void Render(PlanView v)
		{
			//IL_0048: 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)
			if ((Object)(object)_root == (Object)null || v == null)
			{
				return;
			}
			for (int i = 0; i < Tabs.Length; i++)
			{
				UiUtil.SetLabel(Tabs[i], Goals[i].ToString(), (Goals[i] == v.Goal) ? On : Off);
			}
			if (v.Error != null)
			{
				ComboRow[] rows = Rows;
				for (int j = 0; j < rows.Length; j++)
				{
					((Component)rows[j].Root).gameObject.SetActive(false);
				}
				((Component)_eatAll).gameObject.SetActive(false);
				((TMP_Text)_totals).text = "";
				((TMP_Text)_cook).text = "";
				((TMP_Text)_status).text = Format.C("#ff7070", "Larder couldn't read the game: " + v.Error);
				return;
			}
			int count = EatOrder.Next(v.Combo, v.Active, v.BagFoods, Game.m_foodRate).Count;
			((Component)_eatAll).gameObject.SetActive(count >= 2);
			if (count >= 2)
			{
				((TMP_Text)_eatAllLabel).text = "Eat " + count;
			}
			for (int k = 0; k < Rows.Length; k++)
			{
				ComboRow comboRow = Rows[k];
				bool flag = k < v.Slots.Planned.Count;
				((Component)comboRow.Root).gameObject.SetActive(flag);
				if (flag)
				{
					PlannedSlot plannedSlot = v.Slots.Planned[k];
					comboRow.FoodId = plannedSlot.Food.Id;
					comboRow.Icon.sprite = (FoodCatalog.Items.TryGetValue(plannedSlot.Food.Id, out var value) ? IconOrNull(value) : null);
					((TMP_Text)comboRow.Text).text = Format.Row(plannedSlot, v);
					bool flag2 = plannedSlot.State == SlotState.EatNow || plannedSlot.State == SlotState.RefreshNow;
					((Component)comboRow.Eat).gameObject.SetActive(flag2 && Eater.CanEatNow(Player.m_localPlayer, plannedSlot.Food.Id));
				}
			}
			((TMP_Text)_totals).text = ((v.Combo.Count > 0) ? Format.Totals(v) : "");
			((TMP_Text)_cook).text = Format.Cook(v);
			((TMP_Text)_status).text = ((v.Combo.Count == 0) ? ("No food in your bag or in chests within " + PluginConfig.Radius.Value.ToString("0") + " m.") : Format.Others(v));
		}

		private static Sprite IconOrNull(ItemData item)
		{
			SharedData val = item?.m_shared;
			if (val == null || val.m_icons == null || val.m_icons.Length == 0)
			{
				return null;
			}
			int num = item.m_variant;
			if (num < 0 || num >= val.m_icons.Length)
			{
				num = 0;
			}
			return val.m_icons[num];
		}
	}
	internal static class Placement
	{
		private const float Gap = 24f;

		private const float Spacing = 12f;

		private const float MinSize = 8f;

		private static readonly Vector3[] Corners = (Vector3[])(object)new Vector3[4];

		private static readonly List<Graphic> Graphics = new List<Graphic>();

		private static readonly List<ScreenRect> Candidates = new List<ScreenRect>();

		public static void Apply(RectTransform panel, InventoryGui gui, Transform exclude)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0240: 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_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			Transform parent = ((Transform)panel).parent;
			RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			Canvas componentInParent = ((Component)panel).GetComponentInParent<Canvas>();
			if ((Object)(object)val == (Object)null || (Object)(object)componentInParent == (Object)null || (Object)(object)gui == (Object)null || (Object)(object)gui.m_player == (Object)null)
			{
				return;
			}
			componentInParent = componentInParent.rootCanvas;
			Camera val2 = (((int)componentInParent.renderMode == 0) ? null : componentInParent.worldCamera);
			float num = ((componentInParent.scaleFactor > 0f) ? componentInParent.scaleFactor : 1f);
			Rect pixelRect = componentInParent.pixelRect;
			ScreenRect screenRect = ScreenRectOf(gui.m_player, val2);
			if (!(screenRect.Right - screenRect.Left < 1f) && !(screenRect.Top - screenRect.Bottom < 1f))
			{
				float num2;
				float num3;
				switch (PluginConfig.Placement.Value)
				{
				case PanelPlacement.Manual:
					num2 = ((Rect)(ref pixelRect)).xMin;
					num3 = ((Rect)(ref pixelRect)).yMax;
					break;
				case PanelPlacement.Below:
				{
					ScreenRect screenRect2 = ScreenRectOf(((Object)(object)gui.m_container != (Object)null && ((Component)gui.m_container).gameObject.activeInHierarchy) ? gui.m_container : gui.m_player, val2);
					num2 = screenRect2.Left;
					num3 = screenRect2.Bottom - 12f * num;
					break;
				}
				default:
					CollectCandidates((Transform)(((Object)(object)gui.m_inventoryRoot != (Object)null) ? ((object)gui.m_inventoryRoot) : ((object)gui.m_player)), panel, exclude, val2, ((Rect)(ref pixelRect)).width);
					num2 = PlacementMath.ClusterRight(screenRect.Left, screenRect.Right, screenRect.Top, screenRect.Bottom, Candidates, 24f * num) + 12f * num;
					num3 = screenRect.Top;
					break;
				}
				num2 += PluginConfig.OffsetX.Value * num;
				num3 += PluginConfig.OffsetY.Value * num;
				LayoutRebuilder.ForceRebuildLayoutImmediate(panel);
				ScreenRect screenRect3 = ScreenRectOf(panel, val2);
				float num4 = screenRect3.Right - screenRect3.Left;
				float num5 = screenRect3.Top - screenRect3.Bottom;
				num2 = Mathf.Max(((Rect)(ref pixelRect)).xMin, Mathf.Min(num2, ((Rect)(ref pixelRect)).xMax - num4));
				num3 = Mathf.Min(((Rect)(ref pixelRect)).yMax, Mathf.Max(num3, ((Rect)(ref pixelRect)).yMin + num5));
				Vector2 val3 = default(Vector2);
				if (RectTransformUtility.ScreenPointToLocalPointInRectangle(val, new Vector2(num2, num3), val2, ref val3))
				{
					((Transform)panel).localPosition = new Vector3(val3.x, val3.y, ((Transform)panel).localPosition.z);
				}
			}
		}

		private static void CollectCandidates(Transform root, RectTransform panel, Transform exclude, Camera cam, float screenWidth)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			Candidates.Clear();
			Graphics.Clear();
			((Component)root).GetComponentsInChildren<Graphic>(false, Graphics);
			foreach (Graphic graphic in Graphics)
			{
				if ((Object)(object)graphic == (Object)null || !((Behaviour)graphic).enabled || graphic.color.a <= 0.01f || (Object)(object)((Component)graphic).transform == (Object)(object)root)
				{
					continue;
				}
				TMP_Text val = (TMP_Text)(object)((graphic is TMP_Text) ? graphic : null);
				if ((Object)(object)val != (Object)null && string.IsNullOrEmpty(val.text))
				{
					continue;
				}
				RectTransform rectTransform = graphic.rectTransform;
				if (!((Transform)rectTransform).IsChildOf((Transform)(object)panel) && (!((Object)(object)exclude != (Object)null) || !((Transform)rectTransform).IsChildOf(exclude)))
				{
					ScreenRect item = ScreenRectOf(rectTransform, cam);
					float num = item.Right - item.Left;
					if (!(num < 8f) && !(item.Top - item.Bottom < 8f) && !(num > screenWidth * 0.5f))
					{
						Candidates.Add(item);
					}
				}
			}
			Graphics.Clear();
		}

		private static ScreenRect ScreenRectOf(RectTransform rt, Camera cam)
		{
			//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_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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)
			rt.GetWorldCorners(Corners);
			float num = float.MaxValue;
			float num2 = float.MinValue;
			float num3 = float.MinValue;
			float num4 = float.MaxValue;
			for (int i = 0; i < 4; i++)
			{
				Vector2 val = RectTransformUtility.WorldToScreenPoint(cam, Corners[i]);
				num = Mathf.Min(num, val.x);
				num2 = Mathf.Max(num2, val.x);
				num3 = Mathf.Max(num3, val.y);
				num4 = Mathf.Min(num4, val.y);
			}
			return new ScreenRect(num, num2, num3, num4);
		}
	}
	internal static class UiUtil
	{
		private static Sprite _white;

		public static Sprite White
		{
			get
			{
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				//IL_001d: Expected O, but got Unknown
				//IL_003f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				//IL_007d: Unknown result type (might be due to invalid IL or missing references)
				//IL_008c: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)_white != (Object)null)
				{
					return _white;
				}
				Texture2D val = new Texture2D(4, 4, (TextureFormat)4, false);
				Color32[] array = (Color32[])(object)new Color32[16];
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
				}
				val.SetPixels32(array);
				val.Apply();
				((Object)val).hideFlags = (HideFlags)61;
				_white = Sprite.Create(val, new Rect(0f, 0f, 4f, 4f), new Vector2(0.5f, 0.5f), 4f);
				((Object)_white).hideFlags = (HideFlags)61;
				return _white;
			}
		}

		public static TMP_FontAsset Font
		{
			get
			{
				InventoryGui instance = InventoryGui.instance;
				TMP_Text val = (((Object)(object)instance != (Object)null && (Object)(object)instance.m_craftButton != (Object)null) ? ((Component)instance.m_craftButton).GetComponentInChildren<TMP_Text>(true) : null);
				if ((Object)(object)val != (Object)null)
				{
					return val.font;
				}
				if (!((Object)(object)Hud.instance != (Object)null) || !((Object)(object)Hud.instance.m_healthText != (Object)null))
				{
					return null;
				}
				return Hud.instance.m_healthText.font;
			}
		}

		public static RectTransform Rect(string name, Transform parent)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: 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)
			//IL_003d: Expected O, but got Unknown
			RectTransform val = (RectTransform)new GameObject(name, new Type[1] { typeof(RectTransform) })
			{
				layer = ((Component)parent).gameObject.layer
			}.transform;
			((Transform)val).SetParent(parent, false);
			return val;
		}

		public static void Stretch(RectTransform rt)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			rt.anchorMin = Vector2.zero;
			rt.anchorMax = Vector2.one;
			rt.offsetMin = Vector2.zero;
			rt.offsetMax = Vector2.zero;
		}

		public static void Size(GameObject go, float w, float h)
		{
			LayoutElement val = go.GetComponent<LayoutElement>();
			if ((Object)(object)val == (Object)null)
			{
				val = go.AddComponent<LayoutElement>();
			}
			LayoutElement obj = val;
			float minWidth = (val.preferredWidth = w);
			obj.minWidth = minWidth;
			LayoutElement obj2 = val;
			minWidth = (val.preferredHeight = h);
			obj2.minHeight = minWidth;
		}

		public static TextMeshProUGUI Text(Transform parent, string name, float size, TextAlignmentOptions align)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = Rect(name, parent);
			((Component)obj).gameObject.SetActive(false);
			TextMeshProUGUI val = ((Component)obj).gameObject.AddComponent<TextMeshProUGUI>();
			TMP_FontAsset font = Font;
			if ((Object)(object)font != (Object)null)
			{
				((TMP_Text)val).font = font;
			}
			((Component)obj).gameObject.SetActive(true);
			((TMP_Text)val).fontSize = size;
			((TMP_Text)val).alignment = align;
			((TMP_Text)val).textWrappingMode = (TextWrappingModes)1;
			((TMP_Text)val).richText = true;
			((Graphic)val).raycastTarget = false;
			((Graphic)val).color = new Color(0.9f, 0.9f, 0.9f, 1f);
			return val;
		}

		public static Button Button(Transform parent, string name, Button template, string label, UnityAction onClick)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Expected O, but got Unknown
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = Rect(name, parent);
			Image val = ((Component)obj).gameObject.AddComponent<Image>();
			Image val2 = (((Object)(object)template != (Object)null) ? ((Component)template).GetComponent<Image>() : null);
			if ((Object)(object)val2 != (Object)null)
			{
				val.sprite = val2.sprite;
				val.type = val2.type;
				((Graphic)val).color = ((Graphic)val2).color;
			}
			else
			{
				val.sprite = White;
				((Graphic)val).color = new Color(0.3f, 0.25f, 0.2f, 0.9f);
			}
			Button val3 = ((Component)obj).gameObject.AddComponent<Button>();
			((Selectable)val3).targetGraphic = (Graphic)(object)val;
			if ((Object)(object)template != (Object)null)
			{
				((Selectable)val3).transition = ((Selectable)template).transition;
				((Selectable)val3).colors = ((Selectable)template).colors;
				((Selectable)val3).spriteState = ((Selectable)template).spriteState;
			}
			((UnityEvent)val3.onClick).AddListener(onClick);
			TextMeshProUGUI obj2 = Text((Transform)(object)obj, "Label", 15f, (TextAlignmentOptions)514);
			((TMP_Text)obj2).textWrappingMode = (TextWrappingModes)0;
			Stretch((RectTransform)((TMP_Text)obj2).transform);
			((TMP_Text)obj2).text = label;
			return val3;
		}

		public static void SetLabel(Button b, string text, Color color)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			TMP_Text componentInChildren = ((Component)b).GetComponentInChildren<TMP_Text>(true);
			if (!((Object)(object)componentInChildren == (Object)null))
			{
				componentInChildren.text = text;
				((Graphic)componentInChildren).color = color;
			}
		}
	}
}
namespace Larder.Patches
{
	[HarmonyPatch(typeof(InventoryGui), "Show")]
	internal static class InventoryShowPatch
	{
		private static void Postfix(InventoryGui __instance)
		{
			try
			{
				LarderPanel.OnShow(__instance);
			}
			catch (Exception e)
			{
				LarderPlugin.WarnOnce("Larder: panel failed to open", e);
			}
		}
	}
}
namespace Larder.Core
{
	internal static class ActiveFoods
	{
		public static List<ActiveFood> Read(Player p)
		{
			List<ActiveFood> list = new List<ActiveFood>();
			foreach (Food food in p.GetFoods())
			{
				if (food != null && food.m_item != null && food.m_item.m_shared != null)
				{
					list.Add(new ActiveFood(food.m_item.m_shared.m_name, food.m_time, food.m_item.m_shared.m_foodBurnTime));
				}
			}
			return list;
		}
	}
	internal static class ConsoleCommands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__0_0;

			public static Comparison<FoodStats> <>9__2_0;

			internal void <Register>b__0_0(ConsoleEventArgs args)
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					Say(args.Context, "Larder: not in a game.");
					return;
				}
				string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "");
				if (text == "foods")
				{
					Foods(args.Context);
					return;
				}
				if (text == "goal")
				{
					if (args.Length > 2 && Enum.TryParse<Goal>(args[2], ignoreCase: true, out var result) && Enum.IsDefined(typeof(Goal), result))
					{
						GoalStore.Set(localPlayer, result);
					}
					Say(args.Context, "Larder: goal " + GoalStore.Get(localPlayer));
					Runtime.Refresh();
					return;
				}
				foreach (string item in Report.Lines(Planner.Compute(localPlayer, GoalStore.Get(localPlayer))))
				{
					Say(args.Context, item);
				}
			}

			internal int <Foods>b__2_0(FoodStats a, FoodStats b)
			{
				return (b.Health + b.Stamina + b.Eitr).CompareTo(a.Health + a.Stamina + a.Eitr);
			}
		}

		public static void Register()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			object obj = <>c.<>9__0_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					Player localPlayer = Player.m_localPlayer;
					if ((Object)(object)localPlayer == (Object)null)
					{
						Say(args.Context, "Larder: not in a game.");
					}
					else
					{
						string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "");
						if (text == "foods")
						{
							Foods(args.Context);
						}
						else
						{
							if (!(text == "goal"))
							{
								foreach (string item in Report.Lines(Planner.Compute(localPlayer, GoalStore.Get(localPlayer))))
								{
									Say(args.Context, item);
								}
								return;
							}
							if (args.Length > 2 && Enum.TryParse<Goal>(args[2], ignoreCase: true, out var result) && Enum.IsDefined(typeof(Goal), result))
							{
								GoalStore.Set(localPlayer, result);
							}
							Say(args.Context, "Larder: goal " + GoalStore.Get(localPlayer));
							Runtime.Refresh();
						}
					}
				};
				<>c.<>9__0_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("larder", "Larder: food plan (foods | goal <balanced|health|stamina|eitr>)", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		internal static void Say(Terminal ctx, string line)
		{
			if ((Object)(object)ctx != (Object)null)
			{
				ctx.AddString(line);
			}
			LarderPlugin.Log.LogInfo((object)line);
		}

		private static void Foods(Terminal ctx)
		{
			try
			{
				FoodCatalog.EnsureBuilt();
				List<FoodStats> list = new List<FoodStats>(FoodCatalog.Foods.Values);
				list.Sort((FoodStats a, FoodStats b) => (b.Health + b.Stamina + b.Eitr).CompareTo(a.Health + a.Stamina + a.Eitr));
				foreach (FoodStats item in list)
				{
					string[] obj = new string[16]
					{
						"Larder:   ", item.Id, " ", item.Name, " ", null, null, null, null, null,
						null, null, null, null, null, null
					};
					float health = item.Health;
					obj[5] = health.ToString();
					obj[6] = "/";
					health = item.Stamina;
					obj[7] = health.ToString();
					obj[8] = "/";
					health = item.Eitr;
					obj[9] = health.ToString();
					obj[10] = " ";
					obj[11] = Labels.Duration(item.BurnTime);
					obj[12] = " regen ";
					health = item.Regen;
					obj[13] = health.ToString();
					obj[14] = " producers ";
					obj[15] = FoodCatalog.Producers.For(item.Id).Count.ToString();
					Say(ctx, string.Concat(obj));
				}
				foreach (KeyValuePair<string, string> item2 in FoodCatalog.FeastFood)
				{
					Say(ctx, "Larder:   feast item " + item2.Key + " -> food " + item2.Value);
				}
				Say(ctx, "Larder: " + list.Count + " foods, " + FoodCatalog.FeastFood.Count + " feasts, " + FoodCatalog.Dishes.Count + " dishes.");
			}
			catch (Exception ex)
			{
				Say(ctx, "Larder: couldn't list foods: " + ex.Message);
				LarderPlugin.WarnOnce("Larder: foods command failed", ex);
			}
		}
	}
	internal static class Discovery
	{
		public static bool DishKnown(Player p, Dish d)
		{
			if (PluginConfig.ShowUndiscovered.Value)
			{
				return true;
			}
			foreach (Producer item in FoodCatalog.Producers.For(d.MakeId))
			{
				if ((item.Kind == ProducerKind.Recipe) ? p.IsRecipeKnown(d.MakeId) : p.IsMaterialKnown(d.MakeId))
				{
					return true;
				}
			}
			return false;
		}

		public static bool ItemKnown(Player p, string id)
		{
			if (!PluginConfig.ShowUndiscovered.Value)
			{
				return p.IsMaterialKnown(id);
			}
			return true;
		}
	}
	internal static class FoodCatalog
	{
		public static readonly Dictionary<string, FoodStats> Foods = new Dictionary<string, FoodStats>();

		public static readonly Dictionary<string, ItemData> Items = new Dictionary<string, ItemData>();

		public static readonly Dictionary<string, string> FeastFood = new Dictionary<string, string>();

		public static readonly List<Dish> Dishes = new List<Dish>();

		public static ProducerIndex Producers = new ProducerIndex();

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

		private static int _items = -1;

		private static int _recipes = -1;

		private static int _prefabs = -1;

		private static ObjectDB _db;

		public static void EnsureBuilt()
		{
			ObjectDB instance = ObjectDB.instance;
			ZNetScene instance2 = ZNetScene.instance;
			if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && instance.m_items.Count != 0 && (!((Object)(object)instance == (Object)(object)_db) || instance.m_items.Count != _items || instance.m_recipes.Count != _recipes || instance2.m_prefabs.Count != _prefabs))
			{
				Build(instance, instance2);
				_db = instance;
				_items = instance.m_items.Count;
				_recipes = instance.m_recipes.Count;
				_prefabs = instance2.m_prefabs.Count;
			}
		}

		public static string ItemName(string id)
		{
			if (Localization.instance == null)
			{
				return id;
			}
			return Localization.instance.Localize(id);
		}

		public static string StationName(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				return "by hand";
			}
			if (!StationNames.TryGetValue(id, out var value))
			{
				return ItemName(id);
			}
			return value;
		}

		private static void Build(ObjectDB db, ZNetScene scene)
		{
			Foods.Clear();
			Items.Clear();
			FeastFood.Clear();
			Dishes.Clear();
			StationNames.Clear();
			ProducerIndex producerIndex = new ProducerIndex();
			foreach (GameObject item in db.m_items)
			{
				ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val == (Object)null || val.m_itemData == null || val.m_itemData.m_shared == null)
				{
					continue;
				}
				string name = val.m_itemData.m_shared.m_name;
				if (string.IsNullOrEmpty(name) || Items.ContainsKey(name))
				{
					continue;
				}
				Items[name] = val.m_itemData;
				Feast component = item.GetComponent<Feast>();
				ItemDrop val2 = (((Object)(object)component != (Object)null && (Object)(object)component.m_foodItem != (Object)null) ? component.m_foodItem : val);
				if (val2.m_itemData != null && val2.m_itemData.m_shared != null)
				{
					if ((Object)(object)component != (Object)null)
					{
						FeastFood[name] = val2.m_itemData.m_shared.m_name;
					}
					AddFood(val2.m_itemData);
				}
			}
			foreach (Recipe recipe in db.m_recipes)
			{
				if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || (Object)(object)recipe.m_item == (Object)null || recipe.m_resources == null)
				{
					continue;
				}
				try
				{
					if (recipe.m_item.m_itemData == null || recipe.m_item.m_itemData.m_shared == null)
					{
						continue;
					}
					List<Ingredient> list = new List<Ingredient>();
					Requirement[] resources = recipe.m_resources;
					foreach (Requirement val3 in resources)
					{
						if (val3 != null && !((Object)(object)val3.m_resItem == (Object)null) && val3.m_amount > 0 && val3.m_resItem.m_itemData != null && val3.m_resItem.m_itemData.m_shared != null)
						{
							list.Add(new Ingredient(val3.m_resItem.m_itemData.m_shared.m_name, val3.m_amount));
						}
					}
					if (list.Count != 0)
					{
						string text = (((Object)(object)recipe.m_craftingStation != (Object)null) ? recipe.m_craftingStation.m_name : "");
						Name(text, text);
						producerIndex.Add(new Producer(ProducerKind.Recipe, recipe.m_item.m_itemData.m_shared.m_name, recipe.m_amount, text, recipe.m_minStationLevel, list, recipe.m_requireOnlyOneIngredient));
					}
				}
				catch (Exception e)
				{
					LarderPlugin.WarnOnce("Larder: bad recipe " + ((Object)recipe).name, e);
				}
			}
			foreach (GameObject prefab in scene.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				Piece component2 = prefab.GetComponent<Piece>();
				CookingStation component3 = prefab.GetComponent<CookingStation>();
				if ((Object)(object)component3 != (Object)null)
				{
					string stationId = StationId(component2, component3.m_name);
					foreach (ItemConversion item2 in component3.m_conversion)
					{
						AddConversion(producerIndex, stationId, item2.m_from, item2.m_to);
					}
				}
				Smelter component4 = prefab.GetComponent<Smelter>();
				if (!((Object)(object)component4 != (Object)null))
				{
					continue;
				}
				string stationId2 = StationId(component2, component4.m_name);
				foreach (ItemConversion item3 in component4.m_conversion)
				{
					AddConversion(producerIndex, stationId2, item3.m_from, item3.m_to);
				}
			}
			Producers = producerIndex;
			foreach (KeyValuePair<string, ItemData> item4 in Items)
			{
				if (!FeastFood.TryGetValue(item4.Key, out var value))
				{
					value = item4.Key;
				}
				if (Foods.TryGetValue(value, out var value2) && producerIndex.For(item4.Key).Count > 0)
				{
					Dishes.Add(new Dish(value2, item4.Key));
				}
			}
			LarderPlugin.Log.LogInfo((object)("Larder: catalogue " + Foods.Count + " foods, " + FeastFood.Count + " feasts, " + Dishes.Count + " dishes, " + producerIndex.Count + " producers."));
		}

		private static void AddFood(ItemData item)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			SharedData shared = item.m_shared;
			if ((int)shared.m_itemType == 2 && !(shared.m_food <= 0f) && !Foods.ContainsKey(shared.m_name))
			{
				Foods[shared.m_name] = new FoodStats(shared.m_name, ItemName(shared.m_name), shared.m_food, shared.m_foodStamina, shared.m_foodEitr, shared.m_foodBurnTime, shared.m_foodRegen);
				if (!Items.ContainsKey(shared.m_name))
				{
					Items[shared.m_name] = item;
				}
			}
		}

		private static void AddConversion(ProducerIndex producers, string stationId, ItemDrop from, ItemDrop to)
		{
			if ((Object)(object)from == (Object)null || (Object)(object)to == (Object)null)
			{
				return;
			}
			try
			{
				if (from.m_itemData != null && from.m_itemData.m_shared != null && to.m_itemData != null && to.m_itemData.m_shared != null)
				{
					producers.Add(new Producer(ProducerKind.Conversion, to.m_itemData.m_shared.m_name, 1, stationId, 1, new Ingredient[1]
					{
						new Ingredient(from.m_itemData.m_shared.m_name, 1)
					}));
				}
			}
			catch (Exception e)
			{
				LarderPlugin.WarnOnce("Larder: bad conversion " + stationId + " " + ((Object)to).name, e);
			}
		}

		private static string StationId(Piece piece, string fallback)
		{
			string obj = (((Object)(object)piece != (Object)null && !string.IsNullOrEmpty(piece.m_name)) ? piece.m_name : fallback);
			Name(obj, obj);
			return obj;
		}

		private static void Name(string id, string token)
		{
			if (!string.IsNullOrEmpty(id) && !StationNames.ContainsKey(id))
			{
				StationNames[id] = ItemName(token);
			}
		}
	}
	internal static class GoalStore
	{
		private const string Key = "larder.goal";

		public static Goal Get(Player p)
		{
			if ((Object)(object)p != (Object)null && p.m_customData.TryGetValue("larder.goal", out var value) && Enum.TryParse<Goal>(value, out var result) && Enum.IsDefined(typeof(Goal), result))
			{
				return result;
			}
			return Goal.Balanced;
		}

		public static void Set(Player p, Goal g)
		{
			if ((Object)(object)p != (Object)null)
			{
				p.m_customData["larder.goal"] = g.ToString();
			}
		}
	}
	internal static class NearbyPieces
	{
		public static List<Piece> Collect(Vector3 at, float radius)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			List<Piece> list = new List<Piece>();
			float num = radius * radius;
			Transform ghost = Ghost();
			foreach (Piece s_allPiece in Piece.s_allPieces)
			{
				if (!((Object)(object)s_allPiece == (Object)null) && IsReal(s_allPiece.m_nview, (Component)(object)s_allPiece, ghost))
				{
					Vector3 val = ((Component)s_allPiece).transform.position - at;
					if (((Vector3)(ref val)).sqrMagnitude <= num)
					{
						list.Add(s_allPiece);
					}
				}
			}
			return list;
		}

		public static Transform Ghost()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer != (Object)null) || !((Object)(object)localPlayer.m_placementGhost != (Object)null))
			{
				return null;
			}
			return localPlayer.m_placementGhost.transform;
		}

		public static bool IsReal(ZNetView nview, Component c, Transform ghost)
		{
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return false;
			}
			if (!((Object)(object)ghost == (Object)null))
			{
				return !c.transform.IsChildOf(ghost);
			}
			return true;
		}
	}
	internal sealed class PlanView
	{
		public Goal Goal;

		public Snapshot Snapshot;

		public StationLevels Stations;

		public List<FoodStats> Combo = new List<FoodStats>();

		public SlotAdvice Slots = new SlotAdvice();

		public List<ActiveFood> Active = new List<ActiveFood>();

		public HashSet<string> BagFoods = new HashSet<string>();

		public Totals Now;

		public Totals Planned;

		public CookAdvice Cook = new CookAdvice();

		public string Error;

		public double Millis;
	}
	internal static class Planner
	{
		public static PlanView Compute(Player player, Goal goal)
		{
			//IL_003b: 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)
			PlanView planView = new PlanView
			{
				Goal = goal
			};
			Stopwatch stopwatch = Stopwatch.StartNew();
			try
			{
				FoodCatalog.EnsureBuilt();
				float value = PluginConfig.Radius.Value;
				List<Piece> nearby = NearbyPieces.Collect(((Component)player).transform.position, value);
				planView.Snapshot = StockScanner.Take(player, nearby);
				planView.Stations = StationScanner.Scan(((Component)player).transform.position, value, nearby);
				planView.BagFoods = BagFoodIds(planView.Snapshot);
				List<ActiveFood> active = (planView.Active = ActiveFoods.Read(player));
				StockScanner.AddEaten(planView.Snapshot, active);
				planView.Combo = ComboSolver.Best(planView.Snapshot.Pool, goal);
				planView.Slots = SlotAdvisor.Advise(planView.Combo, active, Game.m_foodRate);
				planView.Planned = Totals.Of(planView.Combo, player.m_baseHP, player.m_baseStamina);
				planView.Now = new Totals
				{
					Health = ((Character)player).GetMaxHealth(),
					Stamina = ((Character)player).GetMaxStamina(),
					Eitr = ((Character)player).GetMaxEitr()
				};
				List<Dish> list = new List<Dish>();
				foreach (Dish dish in FoodCatalog.Dishes)
				{
					if (Discovery.DishKnown(player, dish))
					{
						list.Add(dish);
					}
				}
				planView.Cook = CookAdvisor.Advise(planView.Snapshot.Pool, goal, list, FoodCatalog.Producers, planView.Snapshot.Stock, planView.Stations, (string id) => Discovery.ItemKnown(player, id));
			}
			catch (Exception ex)
			{
				planView.Error = ex.GetType().Name + ": " + ex.Message;
				LarderPlugin.WarnOnce("Larder: plan failed", ex);
			}
			planView.Millis = stopwatch.Elapsed.TotalMilliseconds;
			if (PluginConfig.Verbose.Value)
			{
				LarderPlugin.Log.LogInfo((object)("Larder: planned in " + planView.Millis.ToString("0.00") + " ms"));
			}
			return planView;
		}

		private static HashSet<string> BagFoodIds(Snapshot snapshot)
		{
			HashSet<string> hashSet = new HashSet<string>();
			foreach (KeyValuePair<string, List<Source>> foodSource in snapshot.FoodSources)
			{
				foreach (Source item in foodSource.Value)
				{
					if (item.Kind == SourceKind.Bag)
					{
						hashSet.Add(foodSource.Key);
						break;
					}
				}
			}
			return hashSet;
		}
	}
	internal static class Report
	{
		public static List<string> Lines(PlanView v)
		{
			List<string> list = new List<string>();
			if (v.Error != null)
			{
				list.Add("Larder: couldn't plan: " + v.Error);
				return list;
			}
			Snapshot snapshot = v.Snapshot;
			list.Add("Larder: goal " + v.Goal.ToString() + ", radius " + PluginConfig.Radius.Value + " m, " + snapshot.Containers + " containers, " + snapshot.Feasts + " feasts, " + snapshot.Stock.Count + " item kinds, " + snapshot.Pool.Count + " foods, " + v.Millis.ToString("0.00") + " ms");
			foreach (PlannedSlot item in v.Slots.Planned)
			{
				FoodStats food = item.Food;
				string[] obj = new string[15]
				{
					"Larder:   ", food.Name, " ", null, null, null, null, null, null, null,
					null, null, null, null, null
				};
				float health = food.Health;
				obj[3] = health.ToString();
				obj[4] = "/";
				health = food.Stamina;
				obj[5] = health.ToString();
				obj[6] = "/";
				health = food.Eitr;
				obj[7] = health.ToString();
				obj[8] = " ";
				obj[9] = Labels.Duration(food.BurnTime);
				obj[10] = " — ";
				obj[11] = FirstSource(snapshot, food.Id);
				obj[12] = ", ";
				obj[13] = item.State.ToString();
				obj[14] = ((item.Seconds > 0f) ? (" " + Labels.Duration(item.Seconds)) : "");
				list.Add(string.Concat(obj));
			}
			foreach (OtherFood other in v.Slots.Others)
			{
				list.Add("Larder:   outside plan: " + FoodCatalog.ItemName(other.Id) + " frees in " + Labels.Duration(other.SecondsUntilFree));
			}
			list.Add("Larder: plan " + v.Planned.Health + "/" + v.Planned.Stamina + "/" + v.Planned.Eitr + " regen " + v.Planned.Regen + "; now " + v.Now.Health.ToString("0") + "/" + v.Now.Stamina.ToString("0") + "/" + v.Now.Eitr.ToString("0"));
			Cook(list, "ready", v.Cook.Ready);
			Cook(list, "almost", v.Cook.Almost);
			StringBuilder stringBuilder = new StringBuilder("Larder: stations:");
			foreach (KeyValuePair<string, int> item2 in v.Stations.All)
			{
				stringBuilder.Append(' ').Append(item2.Key).Append('=')
					.Append(item2.Value);
			}
			list.Add(stringBuilder.ToString());
			return list;
		}

		private static string FirstSource(Snapshot s, string id)
		{
			if (!s.FoodSources.TryGetValue(id, out var value) || value.Count <= 0)
			{
				return "?";
			}
			return value[0].Label;
		}

		private static void Cook(List<string> lines, string label, CookSuggestion c)
		{
			if (c == null)
			{
				lines.Add("Larder: cook " + label + ": none");
				return;
			}
			lines.Add("Larder: cook " + label + ": " + c.Dish.Food.Name + " via " + FoodCatalog.StationName(c.Plan.Root.Via.StationId));
			Tree(lines, c.Plan.Root, 2);
			foreach (KeyValuePair<string, int> missingItem in c.Plan.MissingItems)
			{
				lines.Add("Larder:     missing " + FoodCatalog.ItemName(missingItem.Key) + " x" + missingItem.Value);
			}
			foreach (StationNeed missingStation in c.Plan.MissingStations)
			{
				string text = FoodCatalog.StationName(missingStation.StationId);
				int level = missingStation.Level;
				lines.Add("Larder:     needs " + text + " level " + level);
			}
		}

		private static void Tree(List<string> lines, CookStep step, int indent)
		{
			foreach (CookStep input in step.Inputs)
			{
				lines.Add("Larder: " + new string(' ', indent * 2) + FoodCatalog.ItemName(input.ItemId) + " " + input.FromStock + "/" + input.Need + ((input.Via != null) ? (" (+" + (input.Need - input.FromStock) + " via " + FoodCatalog.StationName(input.Via.StationId) + ")") : "") + ((input.Missing > 0) ? (" MISSING " + input.Missing) : ""));
				if (input.Via != null)
				{
					Tree(lines, input, indent + 1);
				}
			}
		}
	}
	internal static class Runtime
	{
		private static float _next;

		public static PlanView Last { get; private set; }

		public static void Tick()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (PluginConfig.Enabled.Value && InventoryGui.IsVisible())
				{
					KeyboardShortcut value = PluginConfig.ToggleKey.Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						LarderPanel.Toggle();
					}
					LarderPanel.Settle();
					if (LarderPanel.Visible && !(Time.unscaledTime < _next))
					{
						Refresh();
					}
				}
			}
			catch (Exception e)
			{
				LarderPlugin.WarnOnce("Larder: tick failed", e);
			}
		}

		public static void RequestRefresh()
		{
			_next = 0f;
		}

		public static void Refresh()
		{
			_next = Time.unscaledTime + 1f;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !LarderPanel.Visible)
			{
				return;
			}
			try
			{
				Last = Planner.Compute(localPlayer, GoalStore.Get(localPlayer));
				LarderPanel.Render(Last);
				LarderPanel.ApplyLayout();
			}
			catch (Exception e)
			{
				LarderPlugin.WarnOnce("Larder: panel render failed", e);
				LarderPanel.ShowError("Larder: internal error, see log.");
			}
		}
	}
	internal static class StationScanner
	{
		public static StationLevels Scan(Vector3 at, float radius, List<Piece> nearby)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			StationLevels stationLevels = new StationLevels();
			float num = radius * radius;
			Transform ghost = NearbyPieces.Ghost();
			foreach (CraftingStation allStation in CraftingStation.m_allStations)
			{
				if ((Object)(object)allStation != (Object)null && NearbyPieces.IsReal(allStation.m_nview, (Component)(object)allStation, ghost))
				{
					Vector3 val = ((Component)allStation).transform.position - at;
					if (((Vector3)(ref val)).sqrMagnitude <= num)
					{
						stationLevels.Set(allStation.m_name, allStation.GetLevel(true));
					}
				}
			}
			foreach (Piece item in nearby)
			{
				if ((Object)(object)((Component)item).GetComponent<CookingStation>() != (Object)null || (Object)(object)((Component)item).GetComponent<Smelter>() != (Object)null)
				{
					stationLevels.Set(item.m_name, 1);
				}
			}
			return stationLevels;
		}
	}
	internal enum SourceKind
	{
		Eaten,
		Bag,
		Container,
		Feast,
		FeastItem
	}
	internal sealed class Source
	{
		public SourceKind Kind;

		public string Label;

		public float Distance;

		public int Count;
	}
	internal sealed class Snapshot
	{
		public readonly Dictionary<string, int> Stock = new Dictionary<string, int>();

		public readonly Dictionary<string, List<Source>> FoodSources = new Dictionary<string, List<Source>>();

		public readonly List<FoodStats> Pool = new List<FoodStats>();

		public int Containers;

		public int Feasts;
	}
	internal static class StockScanner
	{
		public static Snapshot Take(Player player, List<Piece> nearby)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			Snapshot snapshot = new Snapshot();
			AddInventory(snapshot, ((Humanoid)player).GetInventory(), SourceKind.Bag, "bag", 0f);
			Vector3 position = ((Component)player).transform.position;
			long playerID = player.GetPlayerID();
			foreach (Piece item in nearby)
			{
				try
				{
					AddPiece(snapshot, item, position, playerID);
				}
				catch (Exception e)
				{
					LarderPlugin.WarnOnce("Larder: skipped piece " + ((Object)item).name, e);
				}
			}
			foreach (KeyValuePair<string, List<Source>> foodSource in snapshot.FoodSources)
			{
				foodSource.Value.Sort((Source x, Source y) => x.Distance.CompareTo(y.Distance));
				snapshot.Pool.Add(FoodCatalog.Foods[foodSource.Key]);
			}
			return snapshot;
		}

		public static void AddEaten(Snapshot snap, List<ActiveFood> active)
		{
			foreach (ActiveFood item in active)
			{
				if (FoodCatalog.Foods.TryGetValue(item.Id, out var value))
				{
					if (!snap.FoodSources.ContainsKey(item.Id))
					{
						snap.Pool.Add(value);
					}
					AddFood(snap, item.Id, new Source
					{
						Kind = SourceKind.Eaten,
						Label = "eaten",
						Distance = -1f,
						Count = 0
					});
				}
			}
		}

		private static void AddPiece(Snapshot snap, Piece piece, Vector3 at, long me)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			float num = Vector3.Distance(((Component)piece).transform.position, at);
			Feast component = ((Component)piece).GetComponent<Feast>();
			if ((Object)(object)component != (Object)null)
			{
				int stack = component.GetStack();
				if (stack > 0 && (Object)(object)component.m_foodItem != (Object)null)
				{
					snap.Feasts++;
					AddFood(snap, component.m_foodItem.m_itemData.m_shared.m_name, new Source
					{
						Kind = SourceKind.Feast,
						Label = FoodCatalog.ItemName(piece.m_name) + " " + Meters(num) + " (" + stack + " left)",
						Distance = num,
						Count = stack
					});
				}
				return;
			}
			Container componentInChildren = ((Component)piece).GetComponentInChildren<Container>();
			if (!((Object)(object)componentInChildren == (Object)null) && !((Object)(object)((Component)componentInChildren).GetComponent<TombStone>() != (Object)null))
			{
				Inventory inventory = componentInChildren.GetInventory();
				if (inventory != null && CanOpen(componentInChildren, me) && ((!((Object)(object)componentInChildren.m_wagon != (Object)null) && !((Object)(object)((Component)piece).GetComponent<Ship>() != (Object)null)) || PluginConfig.IncludeCartsAndShips.Value))
				{
					snap.Containers++;
					AddInventory(snap, inventory, SourceKind.Container, FoodCatalog.ItemName(componentInChildren.m_name) + " " + Meters(num), num);
				}
			}
		}

		private static bool CanOpen(Container c, long me)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			if (c.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)c).transform.position, 0f, false, false))
			{
				return false;
			}
			return c.CheckAccess(me);
		}

		private static void AddInventory(Snapshot snap, Inventory inv, SourceKind kind, string label, float d)
		{
			foreach (ItemData allItem in inv.GetAllItems())
			{
				if (allItem != null && allItem.m_shared != null)
				{
					string name = allItem.m_shared.m_name;
					snap.Stock.TryGetValue(name, out var value);
					snap.Stock[name] = value + allItem.m_stack;
					if (FoodCatalog.FeastFood.TryGetValue(name, out var value2))
					{
						AddFood(snap, value2, new Source
						{
							Kind = SourceKind.FeastItem,
							Label = label + ", place then eat",
							Distance = d,
							Count = allItem.m_stack
						});
					}
					else if (FoodCatalog.Foods.ContainsKey(name))
					{
						AddFood(snap, name, new Source
						{
							Kind = kind,
							Label = label,
							Distance = d,
							Count = allItem.m_stack
						});
					}
				}
			}
		}

		private static void AddFood(Snapshot snap, string foodId, Source s)
		{
			if (FoodCatalog.Foods.ContainsKey(foodId))
			{
				if (!snap.FoodSources.TryGetValue(foodId, out var value))
				{
					value = (snap.FoodSources[foodId] = new List<Source>());
				}
				value.Add(s);
			}
		}

		private static string Meters(float d)
		{
			return Mathf.RoundToInt(d) + "m";
		}
	}
}
namespace Larder.Core.Model
{
	public static class ComboSolver
	{
		public const int Slots = 3;

		public static List<FoodStats> Best(IEnumerable<FoodStats> pool, Goal goal)
		{
			HashSet<string> hashSet = new HashSet<string>();
			List<FoodStats> list = new List<FoodStats>();
			foreach (FoodStats item in pool)
			{
				if (item != null && hashSet.Add(item.Id))
				{
					list.Add(item);
				}
			}
			list.Sort(delegate(FoodStats x, FoodStats y)
			{
				int num = Scoring.Of(goal, y).CompareTo(Scoring.Of(goal, x));
				return (num == 0) ? string.CompareOrdinal(x.Id, y.Id) : num;
			});
			return list.GetRange(0, Math.Min(3, list.Count));
		}
	}
	public sealed class Dish
	{
		public readonly FoodStats Food;

		public readonly string MakeId;

		public Dish(FoodStats food, string makeId = null)
		{
			Food = food;
			MakeId = makeId ?? food.Id;
		}
	}
	public sealed class CookSuggestion
	{
		public readonly Dish Dish;

		public readonly CookPlan Plan;

		public readonly List<FoodStats> Combo;

		public readonly Score Score;

		public CookSuggestion(Dish dish, CookPlan plan, List<FoodStats> combo, Score score)
		{
			Dish = dish;
			Plan = plan;
			Combo = combo;
			Score = score;
		}
	}
	public sealed class CookAdvice
	{
		public CookSuggestion Ready;

		public CookSuggestion Almost;
	}
	public static class CookAdvisor
	{
		public static CookAdvice Advise(IList<FoodStats> pool, Goal goal, IEnumerable<Dish> dishes, ProducerIndex producers, IDictionary<string, int> stock, IStationLevels stations, Func<string, bool> itemKnown)
		{
			CookAdvice cookAdvice = new CookAdvice();
			Score score = Scoring.Of(goal, ComboSolver.Best(pool, goal));
			HashSet<string> hashSet = new HashSet<string>();
			foreach (FoodStats item in pool)
			{
				hashSet.Add(item.Id);
			}
			List<CookSuggestion> list = new List<CookSuggestion>();
			foreach (Dish dish in dishes)
			{
				if (dish == null || hashSet.Contains(dish.Food.Id))
				{
					continue;
				}
				List<FoodStats> list2 = ComboSolver.Best(new List<FoodStats>(pool) { dish.Food }, goal);
				if (!list2.Contains(dish.Food))
				{
					continue;
				}
				Score score2 = Scoring.Of(goal, list2);
				if (score2 > score)
				{
					CookPlan cookPlan = CookPlanner.Plan(dish.MakeId, producers, stock, stations);
					if (cookPlan != null)
					{
						list.Add(new CookSuggestion(dish, cookPlan, list2, score2));
					}
				}
			}
			foreach (CookSuggestion item2 in list)
			{
				if (item2.Plan.ReadyNow && (cookAdvice.Ready == null || Beats(item2, cookAdvice.Ready)))
				{
					cookAdvice.Ready = item2;
				}
			}
			foreach (CookSuggestion item3 in list)
			{
				if (item3.Plan.ReadyNow || (cookAdvice.Ready != null && !(item3.Score > cookAdvice.Ready.Score)))
				{
					continue;
				}
				bool flag = true;
				foreach (string key in item3.Plan.MissingItems.Keys)
				{
					if (!itemKnown(key))
					{
						flag = false;
						break;
					}
				}
				if (flag && (cookAdvice.Almost == null || BeatsAlmost(item3, cookAdvice.Almost)))
				{
					cookAdvice.Almost = item3;
				}
			}
			return cookAdvice;
		}

		private static bool Beats(CookSuggestion a, CookSuggestion b)
		{
			int num = a.Score.CompareTo(b.Score);
			if (num == 0)
			{
				return string.CompareOrdinal(a.Dish.Food.Id, b.Dish.Food.Id) < 0;
			}
			return num > 0;
		}

		private static bool BeatsAlmost(CookSuggestion a, CookSuggestion b)
		{
			int num = a.Score.CompareTo(b.Score);
			if (num != 0)
			{
				return num > 0;
			}
			if (a.Plan.MissingUnits != b.Plan.MissingUnits)
			{
				return a.Plan.MissingUnits < b.Plan.MissingUnits;
			}
			return string.CompareOrdinal(a.Dish.Food.Id, b.Dish.Food.Id) < 0;
		}
	}
	public sealed class StationNeed
	{
		public readonly string StationId;

		public readonly int Level;

		public StationNeed(string stationId, int level)
		{
			StationId = stationId;
			Level = level;
		}
	}
	public sealed class CookStep
	{
		public string ItemId;

		public int Need;

		public int FromStock;

		public int Missing;

		public Producer Via;

		public int Crafts;

		public bool StationMissing;

		public readonly List<CookStep> Inputs = new List<CookStep>();
	}
	public sealed class CookPlan
	{
		public CookStep Root;

		public readonly Dictionary<string, int> MissingItems = new Dictionary<string, int>();

		public readonly List<StationNeed> MissingStations = new List<StationNeed>();

		public bool ReadyNow
		{
			get
			{
				if (MissingItems.Count == 0)
				{
					return MissingStations.Count == 0;
				}
				return false;
			}
		}

		public int MissingUnits
		{
			get
			{
				int num = 0;
				foreach (int value in MissingItems.Values)
				{
					num += value;
				}
				return num;
			}
		}

		internal void AddMissing(string id, int n)
		{
			MissingItems.TryGetValue(id, out var value);
			MissingItems[id] = value + n;
		}

		internal void AddStation(string id, int level)
		{
			for (int i = 0; i < MissingStations.Count; i++)
			{
				if (!(MissingStations[i].StationId != id))
				{
					if (MissingStations[i].Level < level)
					{
						MissingStations[i] = new StationNeed(id, level);
					}
					return;
				}
			}
			MissingStations.Add(new StationNeed(id, level));
		}

		internal void Merge(CookPlan other)
		{
			foreach (KeyValuePair<string, int> missingItem in other.MissingItems)
			{
				AddMissing(missingItem.Key, missingItem.Value);
			}
			foreach (StationNeed missingStation in other.MissingStations)
			{
				AddStation(missingStation.StationId, missingStation.Level);
			}
		}
	}
	public static class CookPlanner
	{
		private sealed class Ctx
		{
			public ProducerIndex Producers;

			public IStationLevels Stations;

			public int MaxDepth;

			public readonly HashSet<string> Path = new HashSet<string>();
		}

		public const int MaxDepth = 3;

		public static CookPlan Plan(string itemId, ProducerIndex producers, IDictionary<string, int> stock, IStationLevels stations, int maxDepth = 3)
		{
			CookPlan cookPlan = null;
			foreach (Producer item in producers.For(itemId))
			{
				Ctx ctx = new Ctx
				{
					Producers = producers,
					Stations = stations,
					MaxDepth = maxDepth
				};
				ctx.Path.Add(itemId);
				CookPlan cookPlan2 = new CookPlan();
				cookPlan2.Root = Make(itemId, 1, item, ctx, new Dictionary<string, int>(stock), 1, cookPlan2);
				if (cookPlan == null || Better(cookPlan2, cookPlan))
				{
					cookPlan = cookPlan2;
				}
			}
			return cookPlan;
		}

		private static bool Better(CookPlan a, CookPlan b)
		{
			if (a.ReadyNow != b.ReadyNow)
			{
				return a.ReadyNow;
			}
			if (a.MissingStations.Count != b.MissingStations.Count)
			{
				return a.MissingStations.Count < b.MissingStations.Count;
			}
			return a.MissingUnits < b.MissingUnits;
		}

		private static int Count(Dictionary<string, int> stock, string id)
		{
			if (!stock.TryGetValue(id, out var value))
			{
				return 0;
			}
			return value;
		}

		private static CookStep Make(string itemId, int need, Producer p, Ctx ctx, Dictionary<string, int> stock, int depth, CookPlan plan)
		{
			CookStep cookStep = new CookStep
			{
				ItemId = itemId,
				Need = need,
				Via = p,
				Crafts = (need + p.Yield - 1) / p.Yield
			};
			if (p.StationId.Length > 0 && ctx.Stations.Level(p.StationId) < p.StationLevel)
			{
				cookStep.StationMissing = true;
				plan.AddStation(p.StationId, p.StationLevel);
			}
			if (p.AnyOneInput && p.Inputs.Count > 1)
			{
				CookStep cookStep2 = null;
				CookPlan cookPlan = null;
				Dictionary<string, int> dictionary = null;
				foreach (Ingredient input in p.Inputs)
				{
					Dictionary<string, int> dictionary2 = new Dictionary<string, int>(stock);
					CookPlan cookPlan2 = new CookPlan();
					CookStep cookStep3 = Supply(input.ItemId, input.Amount * cookStep.Crafts, ctx, dictionary2, depth, cookPlan2);
					if (cookPlan == null || Better(cookPlan2, cookPlan))
					{
						cookStep2 = cookStep3;
						cookPlan = cookPlan2;
						dictionary = dictionary2;
					}
				}
				if (cookStep2 != null)
				{
					stock.Clear();
					foreach (KeyValuePair<string, int> item in dictionary)
					{
						stock[item.Key] = item.Value;
					}
					plan.Merge(cookPlan);
					cookStep.Inputs.Add(cookStep2);
				}
			}
			else
			{
				foreach (Ingredient input2 in p.Inputs)
				{
					cookStep.Inputs.Add(Supply(input2.ItemId, input2.Amount * cookStep.Crafts, ctx, stock, depth, plan));
				}
			}
			int num = cookStep.Crafts * p.Yield - need;
			if (num > 0)
			{
				stock[itemId] = Count(stock, itemId) + num;
			}
			return cookStep;
		}

		private static CookStep Supply(string itemId, int need, Ctx ctx, Dictionary<string, int> stock, int depth, CookPlan plan)
		{
			int num = Count(stock, itemId);
			int num2 = Math.Min(num, need);
			stock[itemId] = num - num2;
			int num3 = need - num2;
			if (num3 > 0 && depth < ctx.MaxDepth && !ctx.Path.Contains(itemId))
			{
				CookStep cookStep = null;
				CookPlan cookPlan = null;
				Dictionary<string, int> dictionary = null;
				ctx.Path.Add(itemId);
				foreach (Producer item in ctx.Producers.For(itemId))
				{
					Dictionary<string, int> dictionary2 = new Dictionary<string, int>(stock);
					CookPlan cookPlan2 = new CookPlan();
					CookStep cookStep2 = Make(itemId, num3, item, ctx, dictionary2, depth + 1, cookPlan2);
					if (cookPlan == null || Better(cookPlan2, cookPlan))
					{
						cookStep = cookStep2;
						cookPlan = cookPlan2;
						dictionary = dictionary2;
					}
				}
				ctx.Path.Remove(itemId);
				if (cookStep != null)
				{
					stock.Clear();
					foreach (KeyValuePair<string, int> item2 in dictionary)
					{
						stock[item2.Key] = item2.Value;
					}
					plan.Merge(cookPlan);
					cookStep.Need = need;
					cookStep.FromStock = num2;
					return cookStep;
				}
			}
			if (num3 > 0)
			{
				plan.AddMissing(itemId, num3);
			}
			return new CookStep
			{
				ItemId = itemId,
				Need = need,
				FromStock = num2,
				Missing = num3
			};
		}
	}
	public static class EatOrder
	{
		public const int MaxEats = 3;

		public static List<FoodStats> Next(IList<FoodStats> plan, IList<ActiveFood> active, ISet<string> inBag, float foodRate)
		{
			List<FoodStats> list = new List<FoodStats>();
			List<ActiveFood> active2 = new List<ActiveFood>(active);
			HashSet<string> hashSet = new HashSet<string>();
			for (int i = 0; i < 3; i++)
			{
				PlannedSlot plannedSlot = PickCandidate(SlotAdvisor.Advise(Reachable(plan, active2, inBag), active2, foodRate), inBag, hashSet);
				if (plannedSlot == null)
				{
					break;
				}
				list.Add(plannedSlot.Food);
				hashSet.Add(plannedSlot.Food.Id);
				active2 = Simulate(active2, plannedSlot.Food);
			}
			return list;
		}

		private static List<FoodStats> Reachable(IList<FoodStats> plan, List<ActiveFood> active, ISet<string> inBag)
		{
			HashSet<string> hashSet = new HashSet<string>();
			foreach (ActiveFood item in active)
			{
				hashSet.Add(item.Id);
			}
			List<FoodStats> list = new List<FoodStats>();
			foreach (FoodStats item2 in plan)
			{
				if (hashSet.Contains(item2.Id) || inBag.Contains(item2.Id))
				{
					list.Add(item2);
				}
			}
			return list;
		}

		private static PlannedSlot PickCandidate(SlotAdvice advice, ISet<string> inBag, HashSet<string> eaten)
		{
			PlannedSlot plannedSlot = null;
			PlannedSlot plannedSlot2 = null;
			foreach (PlannedSlot item in advice.Planned)
			{
				if (!eaten.Contains(item.Food.Id) && inBag.Contains(item.Food.Id))
				{
					if (item.State == SlotState.RefreshNow && plannedSlot == null)
					{
						plannedSlot = item;
					}
					else if (item.State == SlotState.EatNow && plannedSlot2 == null)
					{
						plannedSlot2 = item;
					}
				}
			}
			return plannedSlot ?? plannedSlot2;
		}

		private static List<ActiveFood> Simulate(List<ActiveFood> active, FoodStats eaten)
		{
			List<ActiveFood> list = new List<ActiveFood>(active);
			int num = list.FindIndex((ActiveFood a) => a.Id == eaten.Id);
			if (num >= 0)
			{
				list[num] = new ActiveFood(eaten.Id, eaten.BurnTime, eaten.BurnTime);
				return list;
			}
			if (list.Count < 3)
			{
				list.Add(new ActiveFood(eaten.Id, eaten.BurnTime, eaten.BurnTime));
				return list;
			}
			int num2 = -1;
			float num3 = 0f;
			for (int num4 = 0; num4 < list.Count; num4++)
			{
				ActiveFood activeFood = list[num4];
				if (activeFood.CanEatAgain && (num2 < 0 || activeFood.TimeLeft < num3))
				{
					num2 = num4;
					num3 = activeFood.TimeLeft;
				}
			}
			if (num2 >= 0)
			{
				list[num2] = new ActiveFood(eaten.Id, eaten.BurnTime, eaten.BurnTime);
			}
			return list;
		}
	}
	public sealed class FoodStats
	{
		public readonly string Id;

		public readonly string Name;

		public readonly float Health;

		public readonly float Stamina;

		public readonly float Eitr;

		public readonly float BurnTime;

		public readonly float Regen;

		public FoodStats(string id, string name, float health, float stamina, float eitr, float burnTime, float regen)
		{
			Id = id;
			Name = name;
			Health = health;
			Stamina = stamina;
			Eitr = eitr;
			BurnTime = burnTime;
			Regen = regen;
		}

		public override string ToString()
		{
			return Name;
		}
	}
	public enum Goal
	{
		Balanced,
		Health,
		Stamina,
		Eitr
	}
	public static class Labels
	{
		public static string Duration(float seconds)
		{
			int num = (int)Math.Ceiling(Math.Max(0f, seconds));
			if (num < 60)
			{
				return num + "s";
			}
			int num2 = num / 60;
			if (num2 < 60)
			{
				return num2 + "m";
			}
			return num2 / 60 + "h " + (num2 % 60).ToString("00") + "m";
		}
	}
	public struct ScreenRect
	{
		public readonly float Left;

		public readonly float Right;

		public readonly float Top;

		public readonly float Bottom;

		public ScreenRect(float left, float right, float top, float bottom)
		{
			Left = left;
			Right = right;
			Top = top;
			Bottom = bottom;
		}
	}
	public static class PlacementMath
	{
		public static float ClusterRight(float startLeft, float startRight, float top, float bottom, IList<ScreenRect> candidates, float gap)
		{
			float num = startRight;
			if (candidates == null)
			{
				return num;
			}
			bool flag = true;
			while (flag)
			{
				flag = false;
				for (int i = 0; i < candidates.Count; i++)
				{
					ScreenRect screenRect = candidates[i];
					if (Math.Min(top, screenRect.Top) > Math.Max(bottom, screenRect.Bottom) && !(screenRect.Left < startLeft) && !(screenRect.Left > num + gap) && !(screenRect.Right <= num))
					{
						num = screenRect.Right;
						flag = true;
					}
				}
			}
			return num;
		}
	}
	public enum ProducerKind
	{
		Recipe,
		Conversion
	}
	public sealed class Ingredient
	{
		public readonly string ItemId;

		public readonly int Amount;

		public Ingredient(string itemId, int amount)
		{
			ItemId = itemId;
			Amount = amount;
		}
	}
	public sealed class Producer
	{
		public readonly ProducerKind Kind;

		public readonly string OutputId;

		public readonly int Yield;

		public readonly string StationId;

		public readonly int StationLevel;

		public readonly IList<Ingredient> Inputs;

		public readonly bool AnyOneInput;

		public Producer(ProducerKind kind, string outputId, int yield, string stationId, int stationLevel, IList<Ingredient> inputs, bool anyOneInput = false)
		{
			Kind = kind;
			OutputId = outputId;
			Yield = Math.Max(1, yield);
			StationId = stationId ?? "";
			StationLevel = Math.Max(1, stationLevel);
			Inputs = inputs ?? new Ingredient[0];
			AnyOneInput = anyOneInput;
		}
	}
	public sealed class ProducerIndex
	{
		private static readonly IList<Producer> None = new Producer[0];

		private readonly Dictionary<string, List<Producer>> _byOutput = new Dictionary<string, List<Producer>>();

		public int Count { get; private set; }

		public void Add(Producer p)
		{
			if (!_byOutput.TryGetValue(p.OutputId, out var value))
			{
				value = (_byOutput[p.OutputId] = new List<Producer>());
			}
			value.Add(p);
			Count++;
		}

		public IList<Producer> For(string itemId)
		{
			if (itemId == null || !_byOutput.TryGetValue(itemId, out var value))
			{
				return None;
			}
			return value;
		}
	}
	public interface IStationLevels
	{
		int Level(string stationId);
	}
	public sealed class StationLevels : IStationLevels
	{
		private readonly Dictionary<string, int> _levels = new Dictionary<string, int>();

		public IEnumerable<KeyValuePair<string, int>> All => _levels;

		public void Set(string id, int level)
		{
			if (!string.IsNullOrEmpty(id) && (!_levels.TryGetValue(id, out var value) || level > value))
			{
				_levels[id] = level;
			}
		}

		public int Level(string stationId)
		{
			if (stationId == null || !_levels.TryGetValue(stationId, out var value))
			{
				return 0;
			}
			return value;
		}
	}
	public struct Score : IComparable<Score>
	{
		public readonly float A;

		public readonly float B;

		public readonly float C;

		public readonly float D;

		public Score(float a, float b, float c, float d)
		{
			A = a;
			B = b;
			C = c;
			D = d;
		}

		public int CompareTo(Score o)
		{
			int num = Cmp(A, o.A);
			if (num != 0)
			{
				return num;
			}
			num = Cmp(B, o.B);
			if (num != 0)
			{
				return num;
			}
			num = Cmp(C, o.C);
			if (num == 0)
			{
				return Cmp(D, o.D);
			}
			return num;
		}

		private static int Cmp(float x, float y)
		{
			return Math.Round((double)x * 1000.0).CompareTo(Math.Round((double)y * 1000.0));
		}

		public static bool operator >(Score a, Score b)
		{
			return a.CompareTo(b) > 0;
		}

		public static bool operator <(Score a, Score b)
		{
			return a.CompareTo(b) < 0;
		}
	}
	public static class Scoring
	{
		public static Score Of(Goal goal, FoodStats f)
		{
			return Of(goal, f.Health, f.Stamina, f.Eitr, f.Regen, f.BurnTime);
		}

		public static Score Of(Goal goal, IEnumerable<FoodStats> foods)
		{
			float num = 0f;
			float num2 = 0f;
			float num3 = 0f;
			float num4 = 0f;
			float num5 = 0f;
			foreach (FoodStats food in foods)
			{
				num += food.Health;
				num2 += food.Stamina;
				num3 += food.Eitr;
				num4 += food.Regen;
				num5 += food.BurnTime;
			}
			return Of(goal, num, num2, num3, num4, num5);
		}

		private static Score Of(Goal goal, float h, float s, float e, float r, float b)
		{
			return goal switch
			{
				Goal.Health => new Score(h, s + e, r, b), 
				Goal.Stamina => new Score(s, h + e, r, b), 
				Goal.Eitr => new Score(e, h + s, r, b), 
				_ => new Score(h + s, e, r, b), 
			};
		}
	}
	public sealed class ActiveFood
	{
		public readonly string Id;

		public readonly float TimeLeft;

		public readonly float BurnTime;

		public bool CanEatAgain => TimeLeft < BurnTime / 2f;

		public ActiveFood(string id, float timeLeft, float burnTime)
		{
			Id = id;
			TimeLeft = timeLeft;
			BurnTime = burnTime;
		}

		public float SecondsUntilRefreshable(float rate)
		{
			if (!CanEatAgain)
			{
				return (TimeLeft - BurnTime / 2f) / rate;
			}
			return 0f;
		}
	}
	public enum SlotState
	{
		EatNow,
		EatLater,
		Active,
		RefreshNow,
		RefreshFirst
	}
	public sealed class PlannedSlot
	{
		public readonly FoodStats Food;

		public readonly SlotState State;

		public readonly float Seconds;

		public readonly string BlockedBy;

		public PlannedSlot(FoodStats food, SlotState state, float seconds, string blockedBy = null)
		{
			Food = food;
			State = state;
			Seconds = seconds;
			BlockedBy = blockedBy;
		}
	}
	public sealed class OtherFood
	{
		public readonly string Id;

		public readonly float SecondsUntilFree;

		public OtherFood(string id, float secondsUntilFree)
		{
			Id = id;
			SecondsUntilFree = secondsUntilFree;
		}
	}
	public sealed class SlotAdvice
	{
		public readonly List<PlannedSlot> Planned = new List<PlannedSlot>();

		public readonly List<OtherFood> Others = new List<OtherFood>();
	}
	public static class SlotAdvisor
	{
		public const int MaxFoods = 3;

		public static SlotAdvice Advise(IList<FoodStats> plan, IList<ActiveFood> active, float foodRate)
		{
			float rate = ((foodRate > 0f) ? foodRate : 1f);
			SlotAdvice slotAdvice = new SlotAdvice();
			HashSet<string> hashSet = new HashSet<string>();
			foreach (FoodStats item in plan)
			{
				hashSet.Add(item.Id);
			}
			List<ActiveFood> list = new List<ActiveFood>();
			List<ActiveFood> list2 = new List<ActiveFood>();
			foreach (ActiveFood item2 in active)
			{
				if (hashSet.Contains(item2.Id))
				{
					list2.Add(item2);
				}
				else
				{
					list.Add(item2);
				}
			}
			list.Sort(delegate(ActiveFood x, ActiveFood y)
			{
				int num4 = x.SecondsUntilRefreshable(rate).CompareTo(y.SecondsUntilRefreshable(rate));
				if (num4 == 0)
				{
					float timeLeft = x.TimeLeft;
					return timeLeft.CompareTo(y.TimeLeft);
				}
				return num4;
			});
			foreach (ActiveFood item3 in list)
			{
				slotAdvice.Others.Add(new OtherFood(item3.Id, item3.SecondsUntilRefreshable(rate)));
			}
			ActiveFood activeFood = null;
			foreach (ActiveFood item4 in list2)
			{
				if (item4.CanEatAgain && (activeFood == null || item4.TimeLeft < activeFood.TimeLeft))
				{
					activeFood = item4;
				}
			}
			int num = Math.Max(0, 3 - active.Count);
			int num2 = 0;
			foreach (FoodStats item5 in plan)
			{
				ActiveFood activeFood2 = null;
				foreach (ActiveFood item6 in active)
				{
					if (item6.Id == item5.Id)
					{
						activeFood2 = item6;
					}
				}
				if (activeFood2 != null)
				{
					slotAdvice.Planned.Add(new PlannedSlot(item5, activeFood2.CanEatAgain ? SlotState.RefreshNow : SlotState.Active, activeFood2.TimeLeft / rate));
					continue;
				}
				if (num > 0)
				{
					num--;
					slotAdvice.Planned.Add(new PlannedSlot(item5, SlotState.EatNow, 0f));
					continue;
				}
				ActiveFood activeFood3 = ((num2 < list.Count) ? list[num2] : null);
				bool flag = activeFood3?.CanEatAgain ?? false;
				if (activeFood != null && (!flag || activeFood.TimeLeft <= activeFood3.TimeLeft))
				{
					slotAdvice.Planned.Add(new PlannedSlot(item5, SlotState.RefreshFirst, 0f, activeFood.Id));
					continue;
				}
				float num3 = activeFood3?.SecondsUntilRefreshable(rate) ?? 0f;
				num2++;
				slotAdvice.Planned.Add(new PlannedSlot(item5, (!(num3 <= 0f)) ? SlotState.EatLater : SlotState.EatNow, num3));
			}
			return slotAdvice;
		}
	}
	public struct Totals
	{
		public float Health;

		public float Stamina;

		public float Eitr;

		public float Regen;

		public static Totals Of(IEnumerable<FoodStats> foods, float baseHealth, float baseStamina)
		{
			Totals result = new Totals
			{
				Health = baseHealth,
				Stamina = baseStamina
			};
			foreach (FoodStats food in foods)
			{
				result.Health += food.Health;
				result.Stamina += food.Stamina;
				result.Eitr += food.Eitr;
				result.Regen += food.Regen;
			}
			return result;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}