Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Larder v0.2.0
plugins/Larder/Larder.dll
Decompiled 4 days agousing 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) { } } }