Decompiled source of CraftPlanner v0.1.0

bin/Debug/CraftPlanner.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace CraftPlanner;

internal static class CameraPatches
{
	[HarmonyPatch(typeof(GameCamera), "LateUpdate")]
	private static class GameCameraLateUpdatePatch
	{
		private static bool Prefix()
		{
			return !PlannerUI.Visible;
		}
	}

	[HarmonyPatch(typeof(InventoryGui), "IsVisible")]
	private static class InventoryGuiVisiblePatch
	{
		private static void Postfix(ref bool __result)
		{
			if (PlannerUI.Visible)
			{
				__result = true;
			}
		}
	}
}
public sealed class RecipeEntry
{
	public string Key;

	public string Display;

	public string SearchIndex = string.Empty;

	public string Station;

	public int StationLevel;

	public int OutputAmount = 1;

	public bool IsPiece;

	public Recipe Recipe;

	public Piece Piece;

	public Requirement[] Resources => IsPiece ? Piece.m_resources : Recipe.m_resources;
}
public sealed class GameData
{
	private static GameData _cached;

	private static ObjectDB _cachedDb;

	public readonly Dictionary<string, RecipeEntry> ByKey = new Dictionary<string, RecipeEntry>();

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

	public readonly List<RecipeEntry> All = new List<RecipeEntry>();

	private Dictionary<string, string> _processing;

	private static FieldRef<ObjectDB, List<Piece>> _buildPiecesRef;

	private static bool _buildPiecesResolved;

	public Dictionary<string, string> Processing
	{
		get
		{
			if (_processing == null)
			{
				BuildProcessing();
			}
			return _processing;
		}
	}

	public static GameData Get()
	{
		ObjectDB instance = ObjectDB.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return null;
		}
		if (_cached != null && (Object)(object)_cachedDb == (Object)(object)instance)
		{
			return _cached;
		}
		_cachedDb = instance;
		_cached = Build(instance);
		return _cached;
	}

	public string DisplayOf(string key)
	{
		if (string.IsNullOrEmpty(key))
		{
			return "?";
		}
		if (DisplayNames.TryGetValue(key, out var value))
		{
			return value;
		}
		return Loc(key);
	}

	public static string Loc(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return s;
		}
		try
		{
			Localization instance = Localization.instance;
			if (instance != null)
			{
				return instance.Localize(s);
			}
		}
		catch
		{
		}
		return s;
	}

	private static GameData Build(ObjectDB odb)
	{
		GameData gameData = new GameData();
		if (odb.m_items != null)
		{
			foreach (GameObject item2 in odb.m_items)
			{
				if ((Object)(object)item2 == (Object)null)
				{
					continue;
				}
				ItemDrop component = item2.GetComponent<ItemDrop>();
				if (!((Object)(object)component == (Object)null) && component.m_itemData != null && component.m_itemData.m_shared != null)
				{
					string name = component.m_itemData.m_shared.m_name;
					if (!string.IsNullOrEmpty(name) && !gameData.DisplayNames.ContainsKey(name))
					{
						gameData.DisplayNames[name] = Loc(name);
					}
				}
			}
		}
		if (odb.m_recipes != null)
		{
			foreach (Recipe recipe in odb.m_recipes)
			{
				if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || recipe.m_resources == null || recipe.m_resources.Length == 0)
				{
					continue;
				}
				ItemDrop item = recipe.m_item;
				if ((Object)(object)item == (Object)null || item.m_itemData == null || item.m_itemData.m_shared == null)
				{
					continue;
				}
				string name2 = item.m_itemData.m_shared.m_name;
				if (!string.IsNullOrEmpty(name2))
				{
					RecipeEntry recipeEntry = new RecipeEntry
					{
						Key = name2,
						Display = gameData.DisplayOf(name2),
						Station = (((Object)(object)recipe.m_craftingStation != (Object)null) ? Loc(recipe.m_craftingStation.m_name) : null),
						StationLevel = recipe.m_minStationLevel,
						OutputAmount = Mathf.Max(1, recipe.m_amount),
						IsPiece = false,
						Recipe = recipe
					};
					recipeEntry.SearchIndex = BuildSearchIndex(gameData, name2, recipeEntry.Station);
					gameData.All.Add(recipeEntry);
					if (!gameData.ByKey.ContainsKey(name2))
					{
						gameData.ByKey[name2] = recipeEntry;
					}
				}
			}
		}
		List<Piece> buildPieces = GetBuildPieces(odb);
		if (buildPieces != null)
		{
			foreach (Piece item3 in buildPieces)
			{
				if (!((Object)(object)item3 == (Object)null) && item3.m_resources != null && item3.m_resources.Length != 0 && !string.IsNullOrEmpty(item3.m_name))
				{
					string name3 = item3.m_name;
					if (!gameData.ByKey.ContainsKey(name3))
					{
						RecipeEntry recipeEntry2 = new RecipeEntry
						{
							Key = name3,
							Display = gameData.DisplayOf(name3),
							Station = null,
							StationLevel = 0,
							OutputAmount = 1,
							IsPiece = true,
							Piece = item3
						};
						recipeEntry2.SearchIndex = BuildSearchIndex(gameData, name3, null);
						gameData.All.Add(recipeEntry2);
						gameData.ByKey[name3] = recipeEntry2;
					}
				}
			}
		}
		gameData.All.Sort((RecipeEntry a, RecipeEntry b) => string.Compare(a.Display, b.Display, StringComparison.CurrentCultureIgnoreCase));
		if (CraftPlannerPlugin.Log != null)
		{
			CraftPlannerPlugin.Log.LogInfo((object)("Indexed " + gameData.All.Count + " recipes/pieces"));
		}
		return gameData;
	}

	private static string BuildSearchIndex(GameData data, string key, string station)
	{
		StringBuilder stringBuilder = new StringBuilder();
		string value = data.DisplayOf(key);
		if (!string.IsNullOrEmpty(value))
		{
			stringBuilder.Append(value);
		}
		stringBuilder.Append(' ').Append(key.TrimStart('$').Replace('_', ' '));
		if (!string.IsNullOrEmpty(station))
		{
			stringBuilder.Append(' ').Append(station);
		}
		return stringBuilder.ToString().ToLowerInvariant();
	}

	private static List<Piece> GetBuildPieces(ObjectDB odb)
	{
		if (!_buildPiecesResolved)
		{
			_buildPiecesResolved = true;
			try
			{
				_buildPiecesRef = AccessTools.FieldRefAccess<ObjectDB, List<Piece>>("m_buildPieces");
			}
			catch (Exception)
			{
				_buildPiecesRef = null;
			}
		}
		if (_buildPiecesRef != null)
		{
			try
			{
				return _buildPiecesRef.Invoke(odb);
			}
			catch (Exception)
			{
			}
		}
		try
		{
			List<Piece> list = new List<Piece>();
			Piece[] array = Resources.FindObjectsOfTypeAll<Piece>();
			foreach (Piece val in array)
			{
				if (!((Object)(object)val == (Object)null) && val.m_resources != null && val.m_resources.Length != 0 && !string.IsNullOrEmpty(val.m_name))
				{
					list.Add(val);
				}
			}
			return list;
		}
		catch (Exception)
		{
			return null;
		}
	}

	private void BuildProcessing()
	{
		_processing = new Dictionary<string, string>();
		ZNetScene instance = ZNetScene.instance;
		if ((Object)(object)instance == (Object)null || instance.m_prefabs == null)
		{
			return;
		}
		HashSet<string> hashSet = new HashSet<string>();
		foreach (GameObject prefab in instance.m_prefabs)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				continue;
			}
			Smelter component = prefab.GetComponent<Smelter>();
			if ((Object)(object)component == (Object)null || component.m_conversion == null)
			{
				continue;
			}
			foreach (ItemConversion item in component.m_conversion)
			{
				if (item == null || (Object)(object)item.m_from == (Object)null || (Object)(object)item.m_to == (Object)null || item.m_from.m_itemData == null || item.m_from.m_itemData.m_shared == null || item.m_to.m_itemData == null || item.m_to.m_itemData.m_shared == null)
				{
					continue;
				}
				string name = item.m_from.m_itemData.m_shared.m_name;
				string name2 = item.m_to.m_itemData.m_shared.m_name;
				if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(name2) || name == name2)
				{
					continue;
				}
				if (_processing.TryGetValue(name2, out var value))
				{
					if (value != name)
					{
						hashSet.Add(name2);
					}
				}
				else
				{
					_processing[name2] = name;
				}
			}
		}
		foreach (string item2 in hashSet)
		{
			_processing.Remove(item2);
		}
	}
}
public sealed class MatNode
{
	public string Key;

	public string Display;

	public int Amount;

	public string Note;

	public readonly List<MatNode> Children = new List<MatNode>();

	public bool IsRaw => Children.Count == 0;
}
public static class Materials
{
	private const int MaxDepth = 16;

	public static MatNode Build(string key, int amount, int quality)
	{
		GameData gameData = GameData.Get();
		if (gameData == null)
		{
			return null;
		}
		HashSet<string> path = new HashSet<string>();
		return Expand(key, amount, quality, gameData, path, 0);
	}

	public static Dictionary<string, int> Totals(MatNode root)
	{
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		if (root == null)
		{
			return dictionary;
		}
		Collect(root, dictionary);
		return dictionary;
	}

	private static void Collect(MatNode node, Dictionary<string, int> totals)
	{
		if (node.IsRaw)
		{
			totals.TryGetValue(node.Key, out var value);
			totals[node.Key] = value + node.Amount;
			return;
		}
		foreach (MatNode child in node.Children)
		{
			Collect(child, totals);
		}
	}

	private static MatNode Expand(string key, int amount, int quality, GameData data, HashSet<string> path, int depth)
	{
		MatNode matNode = new MatNode
		{
			Key = key,
			Display = data.DisplayOf(key),
			Amount = amount
		};
		if (amount <= 0)
		{
			return matNode;
		}
		if (depth >= 16 || path.Contains(key))
		{
			return matNode;
		}
		if (data.ByKey.TryGetValue(key, out var value))
		{
			Requirement[] resources = value.Resources;
			if (resources == null || resources.Length == 0)
			{
				return matNode;
			}
			int num = Math.Max(1, value.OutputAmount);
			int num2 = (amount + num - 1) / num;
			matNode.Note = (value.IsPiece ? "build" : ((value.Station != null) ? (value.Station + ((value.StationLevel > 1) ? (" lvl " + value.StationLevel) : "")) : "craft"));
			path.Add(key);
			Requirement[] array = resources;
			foreach (Requirement val in array)
			{
				if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_resItem.m_itemData != null && val.m_resItem.m_itemData.m_shared != null)
				{
					int amount2 = val.GetAmount(quality);
					if (amount2 > 0)
					{
						string name = val.m_resItem.m_itemData.m_shared.m_name;
						MatNode item = Expand(name, amount2 * num2, quality, data, path, depth + 1);
						matNode.Children.Add(item);
					}
				}
			}
			path.Remove(key);
			return matNode;
		}
		if (CraftPlannerPlugin.DecomposeProcessing != null && CraftPlannerPlugin.DecomposeProcessing.Value && data.Processing.TryGetValue(key, out var value2))
		{
			path.Add(key);
			MatNode matNode2 = Expand(value2, amount, quality, data, path, depth + 1);
			path.Remove(key);
			matNode2.Note = "smelted";
			matNode.Children.Add(matNode2);
		}
		return matNode;
	}
}
internal sealed class PlannerUI
{
	private const int WindowId = 202047470;

	private static readonly Color ColorOk = new Color(0.62f, 0.95f, 0.62f);

	private static readonly Color ColorMissing = new Color(1f, 0.78f, 0.42f);

	private static readonly Color ColorRaw = new Color(0.8f, 0.88f, 1f);

	private static readonly Color ColorNote = new Color(0.62f, 0.62f, 0.62f);

	private Rect _rect = new Rect(90f, 70f, 880f, 600f);

	private string _search = string.Empty;

	private List<RecipeEntry> _filtered = new List<RecipeEntry>();

	private RecipeEntry _selected;

	private int _quality = 1;

	private int _maxQuality = 1;

	private int _target = 1;

	private Vector2 _listScroll;

	private Vector2 _detailScroll;

	private GameData _data;

	private MatNode _tree;

	private Dictionary<string, int> _totals = new Dictionary<string, int>();

	private Dictionary<string, int> _have = new Dictionary<string, int>();

	private bool _prevCursorVisible;

	private CursorLockMode _prevCursorLock;

	private bool _errorLogged;

	private GUIStyle _title;

	private GUIStyle _section;

	private GUIStyle _node;

	private GUIStyle _dim;

	public bool IsOpen { get; private set; }

	public static bool Visible { get; private set; }

	public void Toggle()
	{
		if (IsOpen)
		{
			Close();
		}
		else
		{
			Open();
		}
	}

	public void Open()
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		if (!IsOpen)
		{
			Strings.Refresh();
			_prevCursorVisible = Cursor.visible;
			_prevCursorLock = Cursor.lockState;
			Cursor.visible = true;
			Cursor.lockState = (CursorLockMode)0;
			IsOpen = true;
			Visible = true;
			Stopwatch stopwatch = Stopwatch.StartNew();
			RefreshData();
			Rescan();
			if (CraftPlannerPlugin.Log != null)
			{
				CraftPlannerPlugin.Log.LogInfo((object)("Planner opened in " + stopwatch.ElapsedMilliseconds + " ms"));
			}
		}
	}

	public void Close()
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		if (IsOpen)
		{
			IsOpen = false;
			Visible = false;
			Cursor.visible = _prevCursorVisible;
			Cursor.lockState = _prevCursorLock;
		}
	}

	public void Tick()
	{
		Cursor.visible = true;
		Cursor.lockState = (CursorLockMode)0;
	}

	private void RefreshData()
	{
		try
		{
			_data = GameData.Get();
		}
		catch (Exception ex)
		{
			CraftPlannerPlugin.Log.LogError((object)("Failed to index game data: " + ex));
			_data = null;
		}
		ApplyFilter();
		if (_selected != null)
		{
			_selected = ((_data != null && _data.ByKey.TryGetValue(_selected.Key, out var value)) ? value : null);
		}
		Rebuild();
	}

	private void ApplyFilter()
	{
		_filtered.Clear();
		if (_data == null)
		{
			return;
		}
		string text = (_search ?? string.Empty).Trim();
		if (text.Length == 0)
		{
			_filtered.AddRange(_data.All);
			return;
		}
		foreach (RecipeEntry item in _data.All)
		{
			if (!string.IsNullOrEmpty(item.SearchIndex) && item.SearchIndex.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				_filtered.Add(item);
			}
		}
	}

	private void Select(RecipeEntry entry)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		_selected = entry;
		_quality = 1;
		_maxQuality = MaxQuality(entry);
		_detailScroll = Vector2.zero;
		Rebuild();
	}

	private static int MaxQuality(RecipeEntry entry)
	{
		if (entry == null || entry.IsPiece)
		{
			return 1;
		}
		if ((Object)(object)entry.Recipe == (Object)null || (Object)(object)entry.Recipe.m_item == (Object)null)
		{
			return 1;
		}
		SharedData val = ((entry.Recipe.m_item.m_itemData != null) ? entry.Recipe.m_item.m_itemData.m_shared : null);
		return (val == null) ? 1 : Mathf.Max(1, val.m_maxQuality);
	}

	private void Rebuild()
	{
		if (_selected == null || _data == null)
		{
			_tree = null;
			_totals = new Dictionary<string, int>();
		}
		else
		{
			_tree = Materials.Build(_selected.Key, Mathf.Max(1, _target), _quality);
			_totals = Materials.Totals(_tree);
		}
	}

	private void Rescan()
	{
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			_have = new Dictionary<string, int>();
		}
		else
		{
			_have = StorageScanner.Scan(Mathf.Max(1f, CraftPlannerPlugin.ScanRadius.Value));
		}
	}

	public void Draw()
	{
		//IL_0023: 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_0052: Expected O, but got Unknown
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		if (!IsOpen)
		{
			return;
		}
		try
		{
			InitStyles();
			_rect = GUILayout.Window(202047470, _rect, new WindowFunction(DrawWindow), Strings.T("Title") + "  v0.1.0", Array.Empty<GUILayoutOption>());
		}
		catch (Exception ex)
		{
			if (!_errorLogged)
			{
				_errorLogged = true;
				CraftPlannerPlugin.Log.LogError((object)("Planner UI error: " + ex));
			}
			Close();
		}
	}

	public void Prewarm()
	{
		try
		{
			InitStyles();
		}
		catch
		{
		}
	}

	private void DrawWindow(int id)
	{
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		if (_data == null && (Object)(object)Player.m_localPlayer != (Object)null)
		{
			RefreshData();
		}
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		DrawList();
		GUILayout.Space(8f);
		DrawDetails();
		GUILayout.EndHorizontal();
		GUI.DragWindow(new Rect(0f, 0f, 10000f, 22f));
	}

	private void DrawList()
	{
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_0173: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(300f) });
		GUILayout.Label(Strings.T("Search"), _dim, Array.Empty<GUILayoutOption>());
		string text = GUILayout.TextField(_search ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) });
		if (text != _search)
		{
			_search = text;
			ApplyFilter();
		}
		GUILayout.Space(6f);
		_listScroll = GUILayout.BeginScrollView(_listScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
		if (_data == null)
		{
			GUILayout.Label(Strings.T("LoadWorld"), _node, Array.Empty<GUILayoutOption>());
		}
		else
		{
			foreach (RecipeEntry item in _filtered)
			{
				bool flag = _selected != null && _selected.Key == item.Key;
				Color backgroundColor = GUI.backgroundColor;
				if (flag)
				{
					GUI.backgroundColor = new Color(0.42f, 0.62f, 0.42f);
				}
				if (GUILayout.Button(item.Display, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }))
				{
					Select(item);
				}
				GUI.backgroundColor = backgroundColor;
			}
			if (_filtered.Count == 0)
			{
				GUILayout.Label(Strings.T("NothingFound"), _dim, Array.Empty<GUILayoutOption>());
			}
		}
		GUILayout.EndScrollView();
		GUILayout.Label(Strings.T("Close") + "  •  " + _filtered.Count + " " + Strings.T("Entries"), _dim, Array.Empty<GUILayoutOption>());
		GUILayout.EndVertical();
	}

	private void DrawDetails()
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_021d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		//IL_0243: Unknown result type (might be due to invalid IL or missing references)
		//IL_0296: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
		_detailScroll = GUILayout.BeginScrollView(_detailScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
		if (_selected == null)
		{
			GUILayout.Label(Strings.T("Pick"), _node, Array.Empty<GUILayoutOption>());
		}
		else
		{
			GUILayout.Label(_selected.Display, _title, Array.Empty<GUILayoutOption>());
			if (!string.IsNullOrEmpty(_selected.Station))
			{
				GUILayout.Label(Strings.T("Station") + " " + _selected.Station + ((_selected.StationLevel > 1) ? ("  lvl " + _selected.StationLevel) : string.Empty), _dim, Array.Empty<GUILayoutOption>());
			}
			if (!_selected.IsPiece && _selected.OutputAmount > 1)
			{
				GUILayout.Label(Strings.Format("Output", _selected.OutputAmount), _dim, Array.Empty<GUILayoutOption>());
			}
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(Strings.T("Target"), _dim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) });
			_target = AmountButton(1, _target);
			_target = AmountButton(5, _target);
			_target = AmountButton(10, _target);
			_target = AmountButton(25, _target);
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			if (_maxQuality > 1)
			{
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label(Strings.T("Quality"), _dim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) });
				for (int i = 1; i <= _maxQuality; i++)
				{
					Color backgroundColor = GUI.backgroundColor;
					if (i == _quality)
					{
						GUI.backgroundColor = new Color(0.42f, 0.62f, 0.42f);
					}
					if (GUILayout.Button(i.ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }) && i != _quality)
					{
						_quality = i;
						Rebuild();
					}
					GUI.backgroundColor = backgroundColor;
				}
				GUILayout.FlexibleSpace();
				GUILayout.EndHorizontal();
			}
			GUILayout.Space(6f);
			GUILayout.Label(Strings.T("Ingredients"), _section, Array.Empty<GUILayoutOption>());
			if (_tree != null)
			{
				DrawNode(_tree, 0);
			}
			GUILayout.Space(10f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(Strings.T("Raw"), _section, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			if (GUILayout.Button(Strings.T("Rescan"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) }))
			{
				Rescan();
			}
			GUILayout.EndHorizontal();
			DrawTotals();
		}
		GUILayout.EndScrollView();
		GUILayout.EndVertical();
	}

	private int AmountButton(int value, int current)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: 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)
		Color backgroundColor = GUI.backgroundColor;
		if (current == value)
		{
			GUI.backgroundColor = new Color(0.42f, 0.62f, 0.42f);
		}
		int num = (GUILayout.Button("×" + value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(46f) }) ? value : current);
		GUI.backgroundColor = backgroundColor;
		if (num != current)
		{
			Rebuild();
		}
		return num;
	}

	private void DrawNode(MatNode node, int depth)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0123: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Space((float)depth * 18f);
		if (node.IsRaw)
		{
			GUI.color = ColorRaw;
		}
		else
		{
			GUI.color = Color.white;
		}
		GUILayout.Label(node.Display + "  ×" + node.Amount, _node, Array.Empty<GUILayoutOption>());
		if (!string.IsNullOrEmpty(node.Note))
		{
			GUI.color = ColorNote;
			GUILayout.Label("[" + NoteName(node.Note) + "]", _dim, Array.Empty<GUILayoutOption>());
		}
		GUI.color = Color.white;
		if (node.IsRaw)
		{
			_have.TryGetValue(node.Key, out var value);
			GUI.color = ((value >= node.Amount) ? ColorOk : ColorMissing);
			GUILayout.Label(Strings.T("Have") + " " + value, _dim, Array.Empty<GUILayoutOption>());
			GUI.color = Color.white;
		}
		GUILayout.FlexibleSpace();
		GUILayout.EndHorizontal();
		foreach (MatNode child in node.Children)
		{
			DrawNode(child, depth + 1);
		}
	}

	private static string NoteName(string note)
	{
		return note switch
		{
			"build" => Strings.T("Build"), 
			"craft" => Strings.T("Craft"), 
			"smelted" => Strings.T("Smelted"), 
			_ => note, 
		};
	}

	private void DrawTotals()
	{
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
		if (_totals == null || _totals.Count == 0)
		{
			GUILayout.Label(Strings.T("NoMaterials"), _dim, Array.Empty<GUILayoutOption>());
			return;
		}
		List<string> list = new List<string>(_totals.Keys);
		list.Sort((string a, string b) => string.Compare(_data.DisplayOf(a), _data.DisplayOf(b), StringComparison.CurrentCultureIgnoreCase));
		foreach (string item in list)
		{
			int num = _totals[item];
			_have.TryGetValue(item, out var value);
			GUI.color = ((value >= num) ? ColorOk : ColorMissing);
			GUILayout.Label(_data.DisplayOf(item) + "   " + value + " / " + num, _node, Array.Empty<GUILayoutOption>());
			GUI.color = Color.white;
		}
	}

	private void InitStyles()
	{
		//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_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		//IL_0044: 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_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Expected O, but got Unknown
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Expected O, but got Unknown
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Expected O, but got Unknown
		if (_title == null)
		{
			_title = new GUIStyle(GUI.skin.label)
			{
				fontSize = 19,
				fontStyle = (FontStyle)1
			};
			_section = new GUIStyle(GUI.skin.label)
			{
				fontSize = 13,
				fontStyle = (FontStyle)1
			};
			_node = new GUIStyle(GUI.skin.label)
			{
				fontSize = 13
			};
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				fontSize = 11
			};
			val.normal.textColor = new Color(0.72f, 0.72f, 0.72f);
			_dim = val;
		}
	}
}
[BepInPlugin("opencode.craftplanner", "Craft Planner", "0.1.0")]
public class CraftPlannerPlugin : BaseUnityPlugin
{
	public const string Guid = "opencode.craftplanner";

	public const string Version = "0.1.0";

	internal static CraftPlannerPlugin Instance;

	internal static ManualLogSource Log;

	internal static ConfigEntry<KeyCode> ToggleKey;

	internal static ConfigEntry<float> ScanRadius;

	internal static ConfigEntry<bool> DecomposeProcessing;

	internal static ConfigEntry<string> UiLanguage;

	private PlannerUI _ui;

	private Harmony _harmony;

	private void Awake()
	{
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Expected O, but got Unknown
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Expected O, but got Unknown
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "ToggleKey", (KeyCode)287, "Key that opens/closes the craft planner window.");
		ScanRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ScanRadius", 40f, "Radius in meters used when counting materials in nearby containers.");
		DecomposeProcessing = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DecomposeProcessing", true, "Show smelted bars and milled goods as their source material (ore, barley).");
		UiLanguage = ((BaseUnityPlugin)this).Config.Bind<string>("General", "UiLanguage", "auto", new ConfigDescription("UI language: auto (follow the game), en or ru.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "auto", "en", "ru" }), Array.Empty<object>()));
		Strings.Refresh();
		_ui = new PlannerUI();
		_harmony = new Harmony("opencode.craftplanner");
		_harmony.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Craft Planner v0.1.0 loaded. Toggle with " + ((object)ToggleKey.Value/*cast due to .constrained prefix*/).ToString()));
	}

	private void Update()
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		if (_ui != null)
		{
			if (Input.GetKeyDown(ToggleKey.Value) && !Menu.IsVisible() && !TextInput.IsVisible())
			{
				_ui.Toggle();
			}
			if (_ui.IsOpen)
			{
				_ui.Tick();
			}
		}
	}

	private void OnGUI()
	{
		if (_ui != null)
		{
			if (!_ui.IsOpen)
			{
				_ui.Prewarm();
			}
			_ui.Draw();
		}
	}

	private void OnDestroy()
	{
		_ui?.Close();
		_ui = null;
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		_harmony = null;
	}
}
public static class StorageScanner
{
	public static Dictionary<string, int> Scan(float radius)
	{
		//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_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		Dictionary<string, int> result = new Dictionary<string, int>();
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return result;
		}
		Accumulate(((Humanoid)localPlayer).GetInventory(), result);
		Vector3 position = ((Component)localPlayer).transform.position;
		float num = radius * radius;
		Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
		Container[] array2 = array;
		foreach (Container val in array2)
		{
			if (!((Object)(object)val == (Object)null))
			{
				Vector3 val2 = ((Component)val).transform.position - position;
				if (!(((Vector3)(ref val2)).sqrMagnitude > num))
				{
					Accumulate(val.GetInventory(), result);
				}
			}
		}
		return result;
	}

	private static void Accumulate(Inventory inventory, Dictionary<string, int> result)
	{
		if (inventory == null)
		{
			return;
		}
		foreach (ItemData allItem in inventory.GetAllItems())
		{
			if (allItem != null && allItem.m_shared != null)
			{
				string name = allItem.m_shared.m_name;
				if (!string.IsNullOrEmpty(name))
				{
					result.TryGetValue(name, out var value);
					result[name] = value + allItem.m_stack;
				}
			}
		}
	}
}
internal static class Strings
{
	private static readonly Dictionary<string, string> En = new Dictionary<string, string>
	{
		{ "Title", "Craft Planner" },
		{ "Search", "Search (ru/en)" },
		{ "NothingFound", "Nothing found." },
		{ "LoadWorld", "Load a world to use the planner." },
		{ "Pick", "Pick an item or a building piece on the left." },
		{ "Target", "Target:" },
		{ "Quality", "Quality:" },
		{ "Ingredients", "Ingredients" },
		{ "Raw", "Needed raw materials" },
		{ "Rescan", "Rescan containers" },
		{ "Close", "F6 to close" },
		{ "Entries", "entries" },
		{ "Station", "Station:" },
		{ "Output", "Output: x{0} per craft" },
		{ "Have", "have" },
		{ "NoMaterials", "No materials needed." },
		{ "Build", "build" },
		{ "Craft", "craft" },
		{ "Smelted", "smelted" }
	};

	private static readonly Dictionary<string, string> Ru = new Dictionary<string, string>
	{
		{ "Title", "Планировщик крафта" },
		{ "Search", "Поиск (рус/англ)" },
		{ "NothingFound", "Ничего не найдено." },
		{ "LoadWorld", "Загрузи мир, чтобы пользоваться планировщиком." },
		{ "Pick", "Выбери предмет или постройку слева." },
		{ "Target", "Нужно:" },
		{ "Quality", "Качество:" },
		{ "Ingredients", "Состав" },
		{ "Raw", "Нужно сырья" },
		{ "Rescan", "Обновить сундуки" },
		{ "Close", "F6 — закрыть" },
		{ "Entries", "позиций" },
		{ "Station", "Станция:" },
		{ "Output", "Выход: x{0} за крафт" },
		{ "Have", "есть" },
		{ "NoMaterials", "Материалы не нужны." },
		{ "Build", "постройка" },
		{ "Craft", "крафт" },
		{ "Smelted", "плавка" }
	};

	public static bool IsRussian { get; private set; }

	public static void Refresh()
	{
		string a = ((CraftPlannerPlugin.UiLanguage != null) ? CraftPlannerPlugin.UiLanguage.Value : "auto");
		if (string.Equals(a, "ru", StringComparison.OrdinalIgnoreCase))
		{
			IsRussian = true;
			return;
		}
		if (string.Equals(a, "en", StringComparison.OrdinalIgnoreCase))
		{
			IsRussian = false;
			return;
		}
		bool isRussian = false;
		try
		{
			Localization instance = Localization.instance;
			string text = ((instance != null) ? instance.GetSelectedLanguage() : null);
			isRussian = !string.IsNullOrEmpty(text) && text.IndexOf("Russian", StringComparison.OrdinalIgnoreCase) >= 0;
		}
		catch
		{
		}
		IsRussian = isRussian;
	}

	public static string T(string key)
	{
		Dictionary<string, string> dictionary = (IsRussian ? Ru : En);
		string value;
		return dictionary.TryGetValue(key, out value) ? value : key;
	}

	public static string Format(string key, object arg)
	{
		return string.Format(T(key), arg);
	}
}

dist/CraftPlanner.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace CraftPlanner;

internal static class CameraPatches
{
	[HarmonyPatch(typeof(GameCamera), "LateUpdate")]
	private static class GameCameraLateUpdatePatch
	{
		private static bool Prefix()
		{
			return !PlannerUI.Visible;
		}
	}

	[HarmonyPatch(typeof(InventoryGui), "IsVisible")]
	private static class InventoryGuiVisiblePatch
	{
		private static void Postfix(ref bool __result)
		{
			if (PlannerUI.Visible)
			{
				__result = true;
			}
		}
	}
}
public sealed class RecipeEntry
{
	public string Key;

	public string Display;

	public string SearchIndex = string.Empty;

	public string Station;

	public int StationLevel;

	public int OutputAmount = 1;

	public bool IsPiece;

	public Recipe Recipe;

	public Piece Piece;

	public Requirement[] Resources => IsPiece ? Piece.m_resources : Recipe.m_resources;
}
public sealed class GameData
{
	private static GameData _cached;

	private static ObjectDB _cachedDb;

	public readonly Dictionary<string, RecipeEntry> ByKey = new Dictionary<string, RecipeEntry>();

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

	public readonly List<RecipeEntry> All = new List<RecipeEntry>();

	private Dictionary<string, string> _processing;

	private static FieldRef<ObjectDB, List<Piece>> _buildPiecesRef;

	private static bool _buildPiecesResolved;

	public Dictionary<string, string> Processing
	{
		get
		{
			if (_processing == null)
			{
				BuildProcessing();
			}
			return _processing;
		}
	}

	public static GameData Get()
	{
		ObjectDB instance = ObjectDB.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return null;
		}
		if (_cached != null && (Object)(object)_cachedDb == (Object)(object)instance)
		{
			return _cached;
		}
		_cachedDb = instance;
		_cached = Build(instance);
		return _cached;
	}

	public string DisplayOf(string key)
	{
		if (string.IsNullOrEmpty(key))
		{
			return "?";
		}
		if (DisplayNames.TryGetValue(key, out var value))
		{
			return value;
		}
		return Loc(key);
	}

	public static string Loc(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return s;
		}
		try
		{
			Localization instance = Localization.instance;
			if (instance != null)
			{
				return instance.Localize(s);
			}
		}
		catch
		{
		}
		return s;
	}

	private static GameData Build(ObjectDB odb)
	{
		GameData gameData = new GameData();
		if (odb.m_items != null)
		{
			foreach (GameObject item2 in odb.m_items)
			{
				if ((Object)(object)item2 == (Object)null)
				{
					continue;
				}
				ItemDrop component = item2.GetComponent<ItemDrop>();
				if (!((Object)(object)component == (Object)null) && component.m_itemData != null && component.m_itemData.m_shared != null)
				{
					string name = component.m_itemData.m_shared.m_name;
					if (!string.IsNullOrEmpty(name) && !gameData.DisplayNames.ContainsKey(name))
					{
						gameData.DisplayNames[name] = Loc(name);
					}
				}
			}
		}
		if (odb.m_recipes != null)
		{
			foreach (Recipe recipe in odb.m_recipes)
			{
				if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || recipe.m_resources == null || recipe.m_resources.Length == 0)
				{
					continue;
				}
				ItemDrop item = recipe.m_item;
				if ((Object)(object)item == (Object)null || item.m_itemData == null || item.m_itemData.m_shared == null)
				{
					continue;
				}
				string name2 = item.m_itemData.m_shared.m_name;
				if (!string.IsNullOrEmpty(name2))
				{
					RecipeEntry recipeEntry = new RecipeEntry
					{
						Key = name2,
						Display = gameData.DisplayOf(name2),
						Station = (((Object)(object)recipe.m_craftingStation != (Object)null) ? Loc(recipe.m_craftingStation.m_name) : null),
						StationLevel = recipe.m_minStationLevel,
						OutputAmount = Mathf.Max(1, recipe.m_amount),
						IsPiece = false,
						Recipe = recipe
					};
					recipeEntry.SearchIndex = BuildSearchIndex(gameData, name2, recipeEntry.Station);
					gameData.All.Add(recipeEntry);
					if (!gameData.ByKey.ContainsKey(name2))
					{
						gameData.ByKey[name2] = recipeEntry;
					}
				}
			}
		}
		List<Piece> buildPieces = GetBuildPieces(odb);
		if (buildPieces != null)
		{
			foreach (Piece item3 in buildPieces)
			{
				if (!((Object)(object)item3 == (Object)null) && item3.m_resources != null && item3.m_resources.Length != 0 && !string.IsNullOrEmpty(item3.m_name))
				{
					string name3 = item3.m_name;
					if (!gameData.ByKey.ContainsKey(name3))
					{
						RecipeEntry recipeEntry2 = new RecipeEntry
						{
							Key = name3,
							Display = gameData.DisplayOf(name3),
							Station = null,
							StationLevel = 0,
							OutputAmount = 1,
							IsPiece = true,
							Piece = item3
						};
						recipeEntry2.SearchIndex = BuildSearchIndex(gameData, name3, null);
						gameData.All.Add(recipeEntry2);
						gameData.ByKey[name3] = recipeEntry2;
					}
				}
			}
		}
		gameData.All.Sort((RecipeEntry a, RecipeEntry b) => string.Compare(a.Display, b.Display, StringComparison.CurrentCultureIgnoreCase));
		if (CraftPlannerPlugin.Log != null)
		{
			CraftPlannerPlugin.Log.LogInfo((object)("Indexed " + gameData.All.Count + " recipes/pieces"));
		}
		return gameData;
	}

	private static string BuildSearchIndex(GameData data, string key, string station)
	{
		StringBuilder stringBuilder = new StringBuilder();
		string value = data.DisplayOf(key);
		if (!string.IsNullOrEmpty(value))
		{
			stringBuilder.Append(value);
		}
		stringBuilder.Append(' ').Append(key.TrimStart('$').Replace('_', ' '));
		if (!string.IsNullOrEmpty(station))
		{
			stringBuilder.Append(' ').Append(station);
		}
		return stringBuilder.ToString().ToLowerInvariant();
	}

	private static List<Piece> GetBuildPieces(ObjectDB odb)
	{
		if (!_buildPiecesResolved)
		{
			_buildPiecesResolved = true;
			try
			{
				_buildPiecesRef = AccessTools.FieldRefAccess<ObjectDB, List<Piece>>("m_buildPieces");
			}
			catch (Exception)
			{
				_buildPiecesRef = null;
			}
		}
		if (_buildPiecesRef != null)
		{
			try
			{
				return _buildPiecesRef.Invoke(odb);
			}
			catch (Exception)
			{
			}
		}
		try
		{
			List<Piece> list = new List<Piece>();
			Piece[] array = Resources.FindObjectsOfTypeAll<Piece>();
			foreach (Piece val in array)
			{
				if (!((Object)(object)val == (Object)null) && val.m_resources != null && val.m_resources.Length != 0 && !string.IsNullOrEmpty(val.m_name))
				{
					list.Add(val);
				}
			}
			return list;
		}
		catch (Exception)
		{
			return null;
		}
	}

	private void BuildProcessing()
	{
		_processing = new Dictionary<string, string>();
		ZNetScene instance = ZNetScene.instance;
		if ((Object)(object)instance == (Object)null || instance.m_prefabs == null)
		{
			return;
		}
		HashSet<string> hashSet = new HashSet<string>();
		foreach (GameObject prefab in instance.m_prefabs)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				continue;
			}
			Smelter component = prefab.GetComponent<Smelter>();
			if ((Object)(object)component == (Object)null || component.m_conversion == null)
			{
				continue;
			}
			foreach (ItemConversion item in component.m_conversion)
			{
				if (item == null || (Object)(object)item.m_from == (Object)null || (Object)(object)item.m_to == (Object)null || item.m_from.m_itemData == null || item.m_from.m_itemData.m_shared == null || item.m_to.m_itemData == null || item.m_to.m_itemData.m_shared == null)
				{
					continue;
				}
				string name = item.m_from.m_itemData.m_shared.m_name;
				string name2 = item.m_to.m_itemData.m_shared.m_name;
				if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(name2) || name == name2)
				{
					continue;
				}
				if (_processing.TryGetValue(name2, out var value))
				{
					if (value != name)
					{
						hashSet.Add(name2);
					}
				}
				else
				{
					_processing[name2] = name;
				}
			}
		}
		foreach (string item2 in hashSet)
		{
			_processing.Remove(item2);
		}
	}
}
public sealed class MatNode
{
	public string Key;

	public string Display;

	public int Amount;

	public string Note;

	public readonly List<MatNode> Children = new List<MatNode>();

	public bool IsRaw => Children.Count == 0;
}
public static class Materials
{
	private const int MaxDepth = 16;

	public static MatNode Build(string key, int amount, int quality)
	{
		GameData gameData = GameData.Get();
		if (gameData == null)
		{
			return null;
		}
		HashSet<string> path = new HashSet<string>();
		return Expand(key, amount, quality, gameData, path, 0);
	}

	public static Dictionary<string, int> Totals(MatNode root)
	{
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		if (root == null)
		{
			return dictionary;
		}
		Collect(root, dictionary);
		return dictionary;
	}

	private static void Collect(MatNode node, Dictionary<string, int> totals)
	{
		if (node.IsRaw)
		{
			totals.TryGetValue(node.Key, out var value);
			totals[node.Key] = value + node.Amount;
			return;
		}
		foreach (MatNode child in node.Children)
		{
			Collect(child, totals);
		}
	}

	private static MatNode Expand(string key, int amount, int quality, GameData data, HashSet<string> path, int depth)
	{
		MatNode matNode = new MatNode
		{
			Key = key,
			Display = data.DisplayOf(key),
			Amount = amount
		};
		if (amount <= 0)
		{
			return matNode;
		}
		if (depth >= 16 || path.Contains(key))
		{
			return matNode;
		}
		if (data.ByKey.TryGetValue(key, out var value))
		{
			Requirement[] resources = value.Resources;
			if (resources == null || resources.Length == 0)
			{
				return matNode;
			}
			int num = Math.Max(1, value.OutputAmount);
			int num2 = (amount + num - 1) / num;
			matNode.Note = (value.IsPiece ? "build" : ((value.Station != null) ? (value.Station + ((value.StationLevel > 1) ? (" lvl " + value.StationLevel) : "")) : "craft"));
			path.Add(key);
			Requirement[] array = resources;
			foreach (Requirement val in array)
			{
				if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_resItem.m_itemData != null && val.m_resItem.m_itemData.m_shared != null)
				{
					int amount2 = val.GetAmount(quality);
					if (amount2 > 0)
					{
						string name = val.m_resItem.m_itemData.m_shared.m_name;
						MatNode item = Expand(name, amount2 * num2, quality, data, path, depth + 1);
						matNode.Children.Add(item);
					}
				}
			}
			path.Remove(key);
			return matNode;
		}
		if (CraftPlannerPlugin.DecomposeProcessing != null && CraftPlannerPlugin.DecomposeProcessing.Value && data.Processing.TryGetValue(key, out var value2))
		{
			path.Add(key);
			MatNode matNode2 = Expand(value2, amount, quality, data, path, depth + 1);
			path.Remove(key);
			matNode2.Note = "smelted";
			matNode.Children.Add(matNode2);
		}
		return matNode;
	}
}
internal sealed class PlannerUI
{
	private const int WindowId = 202047470;

	private static readonly Color ColorOk = new Color(0.62f, 0.95f, 0.62f);

	private static readonly Color ColorMissing = new Color(1f, 0.78f, 0.42f);

	private static readonly Color ColorRaw = new Color(0.8f, 0.88f, 1f);

	private static readonly Color ColorNote = new Color(0.62f, 0.62f, 0.62f);

	private Rect _rect = new Rect(90f, 70f, 880f, 600f);

	private string _search = string.Empty;

	private List<RecipeEntry> _filtered = new List<RecipeEntry>();

	private RecipeEntry _selected;

	private int _quality = 1;

	private int _maxQuality = 1;

	private int _target = 1;

	private Vector2 _listScroll;

	private Vector2 _detailScroll;

	private GameData _data;

	private MatNode _tree;

	private Dictionary<string, int> _totals = new Dictionary<string, int>();

	private Dictionary<string, int> _have = new Dictionary<string, int>();

	private bool _prevCursorVisible;

	private CursorLockMode _prevCursorLock;

	private bool _errorLogged;

	private GUIStyle _title;

	private GUIStyle _section;

	private GUIStyle _node;

	private GUIStyle _dim;

	public bool IsOpen { get; private set; }

	public static bool Visible { get; private set; }

	public void Toggle()
	{
		if (IsOpen)
		{
			Close();
		}
		else
		{
			Open();
		}
	}

	public void Open()
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		if (!IsOpen)
		{
			Strings.Refresh();
			_prevCursorVisible = Cursor.visible;
			_prevCursorLock = Cursor.lockState;
			Cursor.visible = true;
			Cursor.lockState = (CursorLockMode)0;
			IsOpen = true;
			Visible = true;
			Stopwatch stopwatch = Stopwatch.StartNew();
			RefreshData();
			Rescan();
			if (CraftPlannerPlugin.Log != null)
			{
				CraftPlannerPlugin.Log.LogInfo((object)("Planner opened in " + stopwatch.ElapsedMilliseconds + " ms"));
			}
		}
	}

	public void Close()
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		if (IsOpen)
		{
			IsOpen = false;
			Visible = false;
			Cursor.visible = _prevCursorVisible;
			Cursor.lockState = _prevCursorLock;
		}
	}

	public void Tick()
	{
		Cursor.visible = true;
		Cursor.lockState = (CursorLockMode)0;
	}

	private void RefreshData()
	{
		try
		{
			_data = GameData.Get();
		}
		catch (Exception ex)
		{
			CraftPlannerPlugin.Log.LogError((object)("Failed to index game data: " + ex));
			_data = null;
		}
		ApplyFilter();
		if (_selected != null)
		{
			_selected = ((_data != null && _data.ByKey.TryGetValue(_selected.Key, out var value)) ? value : null);
		}
		Rebuild();
	}

	private void ApplyFilter()
	{
		_filtered.Clear();
		if (_data == null)
		{
			return;
		}
		string text = (_search ?? string.Empty).Trim();
		if (text.Length == 0)
		{
			_filtered.AddRange(_data.All);
			return;
		}
		foreach (RecipeEntry item in _data.All)
		{
			if (!string.IsNullOrEmpty(item.SearchIndex) && item.SearchIndex.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				_filtered.Add(item);
			}
		}
	}

	private void Select(RecipeEntry entry)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		_selected = entry;
		_quality = 1;
		_maxQuality = MaxQuality(entry);
		_detailScroll = Vector2.zero;
		Rebuild();
	}

	private static int MaxQuality(RecipeEntry entry)
	{
		if (entry == null || entry.IsPiece)
		{
			return 1;
		}
		if ((Object)(object)entry.Recipe == (Object)null || (Object)(object)entry.Recipe.m_item == (Object)null)
		{
			return 1;
		}
		SharedData val = ((entry.Recipe.m_item.m_itemData != null) ? entry.Recipe.m_item.m_itemData.m_shared : null);
		return (val == null) ? 1 : Mathf.Max(1, val.m_maxQuality);
	}

	private void Rebuild()
	{
		if (_selected == null || _data == null)
		{
			_tree = null;
			_totals = new Dictionary<string, int>();
		}
		else
		{
			_tree = Materials.Build(_selected.Key, Mathf.Max(1, _target), _quality);
			_totals = Materials.Totals(_tree);
		}
	}

	private void Rescan()
	{
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			_have = new Dictionary<string, int>();
		}
		else
		{
			_have = StorageScanner.Scan(Mathf.Max(1f, CraftPlannerPlugin.ScanRadius.Value));
		}
	}

	public void Draw()
	{
		//IL_0023: 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_0052: Expected O, but got Unknown
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		if (!IsOpen)
		{
			return;
		}
		try
		{
			InitStyles();
			_rect = GUILayout.Window(202047470, _rect, new WindowFunction(DrawWindow), Strings.T("Title") + "  v0.1.0", Array.Empty<GUILayoutOption>());
		}
		catch (Exception ex)
		{
			if (!_errorLogged)
			{
				_errorLogged = true;
				CraftPlannerPlugin.Log.LogError((object)("Planner UI error: " + ex));
			}
			Close();
		}
	}

	public void Prewarm()
	{
		try
		{
			InitStyles();
		}
		catch
		{
		}
	}

	private void DrawWindow(int id)
	{
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		if (_data == null && (Object)(object)Player.m_localPlayer != (Object)null)
		{
			RefreshData();
		}
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		DrawList();
		GUILayout.Space(8f);
		DrawDetails();
		GUILayout.EndHorizontal();
		GUI.DragWindow(new Rect(0f, 0f, 10000f, 22f));
	}

	private void DrawList()
	{
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_0173: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(300f) });
		GUILayout.Label(Strings.T("Search"), _dim, Array.Empty<GUILayoutOption>());
		string text = GUILayout.TextField(_search ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) });
		if (text != _search)
		{
			_search = text;
			ApplyFilter();
		}
		GUILayout.Space(6f);
		_listScroll = GUILayout.BeginScrollView(_listScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
		if (_data == null)
		{
			GUILayout.Label(Strings.T("LoadWorld"), _node, Array.Empty<GUILayoutOption>());
		}
		else
		{
			foreach (RecipeEntry item in _filtered)
			{
				bool flag = _selected != null && _selected.Key == item.Key;
				Color backgroundColor = GUI.backgroundColor;
				if (flag)
				{
					GUI.backgroundColor = new Color(0.42f, 0.62f, 0.42f);
				}
				if (GUILayout.Button(item.Display, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }))
				{
					Select(item);
				}
				GUI.backgroundColor = backgroundColor;
			}
			if (_filtered.Count == 0)
			{
				GUILayout.Label(Strings.T("NothingFound"), _dim, Array.Empty<GUILayoutOption>());
			}
		}
		GUILayout.EndScrollView();
		GUILayout.Label(Strings.T("Close") + "  •  " + _filtered.Count + " " + Strings.T("Entries"), _dim, Array.Empty<GUILayoutOption>());
		GUILayout.EndVertical();
	}

	private void DrawDetails()
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_021d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		//IL_0243: Unknown result type (might be due to invalid IL or missing references)
		//IL_0296: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
		_detailScroll = GUILayout.BeginScrollView(_detailScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
		if (_selected == null)
		{
			GUILayout.Label(Strings.T("Pick"), _node, Array.Empty<GUILayoutOption>());
		}
		else
		{
			GUILayout.Label(_selected.Display, _title, Array.Empty<GUILayoutOption>());
			if (!string.IsNullOrEmpty(_selected.Station))
			{
				GUILayout.Label(Strings.T("Station") + " " + _selected.Station + ((_selected.StationLevel > 1) ? ("  lvl " + _selected.StationLevel) : string.Empty), _dim, Array.Empty<GUILayoutOption>());
			}
			if (!_selected.IsPiece && _selected.OutputAmount > 1)
			{
				GUILayout.Label(Strings.Format("Output", _selected.OutputAmount), _dim, Array.Empty<GUILayoutOption>());
			}
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(Strings.T("Target"), _dim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) });
			_target = AmountButton(1, _target);
			_target = AmountButton(5, _target);
			_target = AmountButton(10, _target);
			_target = AmountButton(25, _target);
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			if (_maxQuality > 1)
			{
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label(Strings.T("Quality"), _dim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) });
				for (int i = 1; i <= _maxQuality; i++)
				{
					Color backgroundColor = GUI.backgroundColor;
					if (i == _quality)
					{
						GUI.backgroundColor = new Color(0.42f, 0.62f, 0.42f);
					}
					if (GUILayout.Button(i.ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }) && i != _quality)
					{
						_quality = i;
						Rebuild();
					}
					GUI.backgroundColor = backgroundColor;
				}
				GUILayout.FlexibleSpace();
				GUILayout.EndHorizontal();
			}
			GUILayout.Space(6f);
			GUILayout.Label(Strings.T("Ingredients"), _section, Array.Empty<GUILayoutOption>());
			if (_tree != null)
			{
				DrawNode(_tree, 0);
			}
			GUILayout.Space(10f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(Strings.T("Raw"), _section, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			if (GUILayout.Button(Strings.T("Rescan"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) }))
			{
				Rescan();
			}
			GUILayout.EndHorizontal();
			DrawTotals();
		}
		GUILayout.EndScrollView();
		GUILayout.EndVertical();
	}

	private int AmountButton(int value, int current)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: 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)
		Color backgroundColor = GUI.backgroundColor;
		if (current == value)
		{
			GUI.backgroundColor = new Color(0.42f, 0.62f, 0.42f);
		}
		int num = (GUILayout.Button("×" + value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(46f) }) ? value : current);
		GUI.backgroundColor = backgroundColor;
		if (num != current)
		{
			Rebuild();
		}
		return num;
	}

	private void DrawNode(MatNode node, int depth)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0123: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Space((float)depth * 18f);
		if (node.IsRaw)
		{
			GUI.color = ColorRaw;
		}
		else
		{
			GUI.color = Color.white;
		}
		GUILayout.Label(node.Display + "  ×" + node.Amount, _node, Array.Empty<GUILayoutOption>());
		if (!string.IsNullOrEmpty(node.Note))
		{
			GUI.color = ColorNote;
			GUILayout.Label("[" + NoteName(node.Note) + "]", _dim, Array.Empty<GUILayoutOption>());
		}
		GUI.color = Color.white;
		if (node.IsRaw)
		{
			_have.TryGetValue(node.Key, out var value);
			GUI.color = ((value >= node.Amount) ? ColorOk : ColorMissing);
			GUILayout.Label(Strings.T("Have") + " " + value, _dim, Array.Empty<GUILayoutOption>());
			GUI.color = Color.white;
		}
		GUILayout.FlexibleSpace();
		GUILayout.EndHorizontal();
		foreach (MatNode child in node.Children)
		{
			DrawNode(child, depth + 1);
		}
	}

	private static string NoteName(string note)
	{
		return note switch
		{
			"build" => Strings.T("Build"), 
			"craft" => Strings.T("Craft"), 
			"smelted" => Strings.T("Smelted"), 
			_ => note, 
		};
	}

	private void DrawTotals()
	{
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
		if (_totals == null || _totals.Count == 0)
		{
			GUILayout.Label(Strings.T("NoMaterials"), _dim, Array.Empty<GUILayoutOption>());
			return;
		}
		List<string> list = new List<string>(_totals.Keys);
		list.Sort((string a, string b) => string.Compare(_data.DisplayOf(a), _data.DisplayOf(b), StringComparison.CurrentCultureIgnoreCase));
		foreach (string item in list)
		{
			int num = _totals[item];
			_have.TryGetValue(item, out var value);
			GUI.color = ((value >= num) ? ColorOk : ColorMissing);
			GUILayout.Label(_data.DisplayOf(item) + "   " + value + " / " + num, _node, Array.Empty<GUILayoutOption>());
			GUI.color = Color.white;
		}
	}

	private void InitStyles()
	{
		//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_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		//IL_0044: 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_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Expected O, but got Unknown
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Expected O, but got Unknown
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Expected O, but got Unknown
		if (_title == null)
		{
			_title = new GUIStyle(GUI.skin.label)
			{
				fontSize = 19,
				fontStyle = (FontStyle)1
			};
			_section = new GUIStyle(GUI.skin.label)
			{
				fontSize = 13,
				fontStyle = (FontStyle)1
			};
			_node = new GUIStyle(GUI.skin.label)
			{
				fontSize = 13
			};
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				fontSize = 11
			};
			val.normal.textColor = new Color(0.72f, 0.72f, 0.72f);
			_dim = val;
		}
	}
}
[BepInPlugin("opencode.craftplanner", "Craft Planner", "0.1.0")]
public class CraftPlannerPlugin : BaseUnityPlugin
{
	public const string Guid = "opencode.craftplanner";

	public const string Version = "0.1.0";

	internal static CraftPlannerPlugin Instance;

	internal static ManualLogSource Log;

	internal static ConfigEntry<KeyCode> ToggleKey;

	internal static ConfigEntry<float> ScanRadius;

	internal static ConfigEntry<bool> DecomposeProcessing;

	internal static ConfigEntry<string> UiLanguage;

	private PlannerUI _ui;

	private Harmony _harmony;

	private void Awake()
	{
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Expected O, but got Unknown
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Expected O, but got Unknown
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "ToggleKey", (KeyCode)287, "Key that opens/closes the craft planner window.");
		ScanRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ScanRadius", 40f, "Radius in meters used when counting materials in nearby containers.");
		DecomposeProcessing = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DecomposeProcessing", true, "Show smelted bars and milled goods as their source material (ore, barley).");
		UiLanguage = ((BaseUnityPlugin)this).Config.Bind<string>("General", "UiLanguage", "auto", new ConfigDescription("UI language: auto (follow the game), en or ru.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "auto", "en", "ru" }), Array.Empty<object>()));
		Strings.Refresh();
		_ui = new PlannerUI();
		_harmony = new Harmony("opencode.craftplanner");
		_harmony.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Craft Planner v0.1.0 loaded. Toggle with " + ((object)ToggleKey.Value/*cast due to .constrained prefix*/).ToString()));
	}

	private void Update()
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		if (_ui != null)
		{
			if (Input.GetKeyDown(ToggleKey.Value) && !Menu.IsVisible() && !TextInput.IsVisible())
			{
				_ui.Toggle();
			}
			if (_ui.IsOpen)
			{
				_ui.Tick();
			}
		}
	}

	private void OnGUI()
	{
		if (_ui != null)
		{
			if (!_ui.IsOpen)
			{
				_ui.Prewarm();
			}
			_ui.Draw();
		}
	}

	private void OnDestroy()
	{
		_ui?.Close();
		_ui = null;
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		_harmony = null;
	}
}
public static class StorageScanner
{
	public static Dictionary<string, int> Scan(float radius)
	{
		//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_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		Dictionary<string, int> result = new Dictionary<string, int>();
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return result;
		}
		Accumulate(((Humanoid)localPlayer).GetInventory(), result);
		Vector3 position = ((Component)localPlayer).transform.position;
		float num = radius * radius;
		Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
		Container[] array2 = array;
		foreach (Container val in array2)
		{
			if (!((Object)(object)val == (Object)null))
			{
				Vector3 val2 = ((Component)val).transform.position - position;
				if (!(((Vector3)(ref val2)).sqrMagnitude > num))
				{
					Accumulate(val.GetInventory(), result);
				}
			}
		}
		return result;
	}

	private static void Accumulate(Inventory inventory, Dictionary<string, int> result)
	{
		if (inventory == null)
		{
			return;
		}
		foreach (ItemData allItem in inventory.GetAllItems())
		{
			if (allItem != null && allItem.m_shared != null)
			{
				string name = allItem.m_shared.m_name;
				if (!string.IsNullOrEmpty(name))
				{
					result.TryGetValue(name, out var value);
					result[name] = value + allItem.m_stack;
				}
			}
		}
	}
}
internal static class Strings
{
	private static readonly Dictionary<string, string> En = new Dictionary<string, string>
	{
		{ "Title", "Craft Planner" },
		{ "Search", "Search (ru/en)" },
		{ "NothingFound", "Nothing found." },
		{ "LoadWorld", "Load a world to use the planner." },
		{ "Pick", "Pick an item or a building piece on the left." },
		{ "Target", "Target:" },
		{ "Quality", "Quality:" },
		{ "Ingredients", "Ingredients" },
		{ "Raw", "Needed raw materials" },
		{ "Rescan", "Rescan containers" },
		{ "Close", "F6 to close" },
		{ "Entries", "entries" },
		{ "Station", "Station:" },
		{ "Output", "Output: x{0} per craft" },
		{ "Have", "have" },
		{ "NoMaterials", "No materials needed." },
		{ "Build", "build" },
		{ "Craft", "craft" },
		{ "Smelted", "smelted" }
	};

	private static readonly Dictionary<string, string> Ru = new Dictionary<string, string>
	{
		{ "Title", "Планировщик крафта" },
		{ "Search", "Поиск (рус/англ)" },
		{ "NothingFound", "Ничего не найдено." },
		{ "LoadWorld", "Загрузи мир, чтобы пользоваться планировщиком." },
		{ "Pick", "Выбери предмет или постройку слева." },
		{ "Target", "Нужно:" },
		{ "Quality", "Качество:" },
		{ "Ingredients", "Состав" },
		{ "Raw", "Нужно сырья" },
		{ "Rescan", "Обновить сундуки" },
		{ "Close", "F6 — закрыть" },
		{ "Entries", "позиций" },
		{ "Station", "Станция:" },
		{ "Output", "Выход: x{0} за крафт" },
		{ "Have", "есть" },
		{ "NoMaterials", "Материалы не нужны." },
		{ "Build", "постройка" },
		{ "Craft", "крафт" },
		{ "Smelted", "плавка" }
	};

	public static bool IsRussian { get; private set; }

	public static void Refresh()
	{
		string a = ((CraftPlannerPlugin.UiLanguage != null) ? CraftPlannerPlugin.UiLanguage.Value : "auto");
		if (string.Equals(a, "ru", StringComparison.OrdinalIgnoreCase))
		{
			IsRussian = true;
			return;
		}
		if (string.Equals(a, "en", StringComparison.OrdinalIgnoreCase))
		{
			IsRussian = false;
			return;
		}
		bool isRussian = false;
		try
		{
			Localization instance = Localization.instance;
			string text = ((instance != null) ? instance.GetSelectedLanguage() : null);
			isRussian = !string.IsNullOrEmpty(text) && text.IndexOf("Russian", StringComparison.OrdinalIgnoreCase) >= 0;
		}
		catch
		{
		}
		IsRussian = isRussian;
	}

	public static string T(string key)
	{
		Dictionary<string, string> dictionary = (IsRussian ? Ru : En);
		string value;
		return dictionary.TryGetValue(key, out value) ? value : key;
	}

	public static string Format(string key, object arg)
	{
		return string.Format(T(key), arg);
	}
}

obj/Debug/CraftPlanner.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace CraftPlanner;

internal static class CameraPatches
{
	[HarmonyPatch(typeof(GameCamera), "LateUpdate")]
	private static class GameCameraLateUpdatePatch
	{
		private static bool Prefix()
		{
			return !PlannerUI.Visible;
		}
	}

	[HarmonyPatch(typeof(InventoryGui), "IsVisible")]
	private static class InventoryGuiVisiblePatch
	{
		private static void Postfix(ref bool __result)
		{
			if (PlannerUI.Visible)
			{
				__result = true;
			}
		}
	}
}
public sealed class RecipeEntry
{
	public string Key;

	public string Display;

	public string SearchIndex = string.Empty;

	public string Station;

	public int StationLevel;

	public int OutputAmount = 1;

	public bool IsPiece;

	public Recipe Recipe;

	public Piece Piece;

	public Requirement[] Resources => IsPiece ? Piece.m_resources : Recipe.m_resources;
}
public sealed class GameData
{
	private static GameData _cached;

	private static ObjectDB _cachedDb;

	public readonly Dictionary<string, RecipeEntry> ByKey = new Dictionary<string, RecipeEntry>();

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

	public readonly List<RecipeEntry> All = new List<RecipeEntry>();

	private Dictionary<string, string> _processing;

	private static FieldRef<ObjectDB, List<Piece>> _buildPiecesRef;

	private static bool _buildPiecesResolved;

	public Dictionary<string, string> Processing
	{
		get
		{
			if (_processing == null)
			{
				BuildProcessing();
			}
			return _processing;
		}
	}

	public static GameData Get()
	{
		ObjectDB instance = ObjectDB.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return null;
		}
		if (_cached != null && (Object)(object)_cachedDb == (Object)(object)instance)
		{
			return _cached;
		}
		_cachedDb = instance;
		_cached = Build(instance);
		return _cached;
	}

	public string DisplayOf(string key)
	{
		if (string.IsNullOrEmpty(key))
		{
			return "?";
		}
		if (DisplayNames.TryGetValue(key, out var value))
		{
			return value;
		}
		return Loc(key);
	}

	public static string Loc(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return s;
		}
		try
		{
			Localization instance = Localization.instance;
			if (instance != null)
			{
				return instance.Localize(s);
			}
		}
		catch
		{
		}
		return s;
	}

	private static GameData Build(ObjectDB odb)
	{
		GameData gameData = new GameData();
		if (odb.m_items != null)
		{
			foreach (GameObject item2 in odb.m_items)
			{
				if ((Object)(object)item2 == (Object)null)
				{
					continue;
				}
				ItemDrop component = item2.GetComponent<ItemDrop>();
				if (!((Object)(object)component == (Object)null) && component.m_itemData != null && component.m_itemData.m_shared != null)
				{
					string name = component.m_itemData.m_shared.m_name;
					if (!string.IsNullOrEmpty(name) && !gameData.DisplayNames.ContainsKey(name))
					{
						gameData.DisplayNames[name] = Loc(name);
					}
				}
			}
		}
		if (odb.m_recipes != null)
		{
			foreach (Recipe recipe in odb.m_recipes)
			{
				if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || recipe.m_resources == null || recipe.m_resources.Length == 0)
				{
					continue;
				}
				ItemDrop item = recipe.m_item;
				if ((Object)(object)item == (Object)null || item.m_itemData == null || item.m_itemData.m_shared == null)
				{
					continue;
				}
				string name2 = item.m_itemData.m_shared.m_name;
				if (!string.IsNullOrEmpty(name2))
				{
					RecipeEntry recipeEntry = new RecipeEntry
					{
						Key = name2,
						Display = gameData.DisplayOf(name2),
						Station = (((Object)(object)recipe.m_craftingStation != (Object)null) ? Loc(recipe.m_craftingStation.m_name) : null),
						StationLevel = recipe.m_minStationLevel,
						OutputAmount = Mathf.Max(1, recipe.m_amount),
						IsPiece = false,
						Recipe = recipe
					};
					recipeEntry.SearchIndex = BuildSearchIndex(gameData, name2, recipeEntry.Station);
					gameData.All.Add(recipeEntry);
					if (!gameData.ByKey.ContainsKey(name2))
					{
						gameData.ByKey[name2] = recipeEntry;
					}
				}
			}
		}
		List<Piece> buildPieces = GetBuildPieces(odb);
		if (buildPieces != null)
		{
			foreach (Piece item3 in buildPieces)
			{
				if (!((Object)(object)item3 == (Object)null) && item3.m_resources != null && item3.m_resources.Length != 0 && !string.IsNullOrEmpty(item3.m_name))
				{
					string name3 = item3.m_name;
					if (!gameData.ByKey.ContainsKey(name3))
					{
						RecipeEntry recipeEntry2 = new RecipeEntry
						{
							Key = name3,
							Display = gameData.DisplayOf(name3),
							Station = null,
							StationLevel = 0,
							OutputAmount = 1,
							IsPiece = true,
							Piece = item3
						};
						recipeEntry2.SearchIndex = BuildSearchIndex(gameData, name3, null);
						gameData.All.Add(recipeEntry2);
						gameData.ByKey[name3] = recipeEntry2;
					}
				}
			}
		}
		gameData.All.Sort((RecipeEntry a, RecipeEntry b) => string.Compare(a.Display, b.Display, StringComparison.CurrentCultureIgnoreCase));
		if (CraftPlannerPlugin.Log != null)
		{
			CraftPlannerPlugin.Log.LogInfo((object)("Indexed " + gameData.All.Count + " recipes/pieces"));
		}
		return gameData;
	}

	private static string BuildSearchIndex(GameData data, string key, string station)
	{
		StringBuilder stringBuilder = new StringBuilder();
		string value = data.DisplayOf(key);
		if (!string.IsNullOrEmpty(value))
		{
			stringBuilder.Append(value);
		}
		stringBuilder.Append(' ').Append(key.TrimStart('$').Replace('_', ' '));
		if (!string.IsNullOrEmpty(station))
		{
			stringBuilder.Append(' ').Append(station);
		}
		return stringBuilder.ToString().ToLowerInvariant();
	}

	private static List<Piece> GetBuildPieces(ObjectDB odb)
	{
		if (!_buildPiecesResolved)
		{
			_buildPiecesResolved = true;
			try
			{
				_buildPiecesRef = AccessTools.FieldRefAccess<ObjectDB, List<Piece>>("m_buildPieces");
			}
			catch (Exception)
			{
				_buildPiecesRef = null;
			}
		}
		if (_buildPiecesRef != null)
		{
			try
			{
				return _buildPiecesRef.Invoke(odb);
			}
			catch (Exception)
			{
			}
		}
		try
		{
			List<Piece> list = new List<Piece>();
			Piece[] array = Resources.FindObjectsOfTypeAll<Piece>();
			foreach (Piece val in array)
			{
				if (!((Object)(object)val == (Object)null) && val.m_resources != null && val.m_resources.Length != 0 && !string.IsNullOrEmpty(val.m_name))
				{
					list.Add(val);
				}
			}
			return list;
		}
		catch (Exception)
		{
			return null;
		}
	}

	private void BuildProcessing()
	{
		_processing = new Dictionary<string, string>();
		ZNetScene instance = ZNetScene.instance;
		if ((Object)(object)instance == (Object)null || instance.m_prefabs == null)
		{
			return;
		}
		HashSet<string> hashSet = new HashSet<string>();
		foreach (GameObject prefab in instance.m_prefabs)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				continue;
			}
			Smelter component = prefab.GetComponent<Smelter>();
			if ((Object)(object)component == (Object)null || component.m_conversion == null)
			{
				continue;
			}
			foreach (ItemConversion item in component.m_conversion)
			{
				if (item == null || (Object)(object)item.m_from == (Object)null || (Object)(object)item.m_to == (Object)null || item.m_from.m_itemData == null || item.m_from.m_itemData.m_shared == null || item.m_to.m_itemData == null || item.m_to.m_itemData.m_shared == null)
				{
					continue;
				}
				string name = item.m_from.m_itemData.m_shared.m_name;
				string name2 = item.m_to.m_itemData.m_shared.m_name;
				if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(name2) || name == name2)
				{
					continue;
				}
				if (_processing.TryGetValue(name2, out var value))
				{
					if (value != name)
					{
						hashSet.Add(name2);
					}
				}
				else
				{
					_processing[name2] = name;
				}
			}
		}
		foreach (string item2 in hashSet)
		{
			_processing.Remove(item2);
		}
	}
}
public sealed class MatNode
{
	public string Key;

	public string Display;

	public int Amount;

	public string Note;

	public readonly List<MatNode> Children = new List<MatNode>();

	public bool IsRaw => Children.Count == 0;
}
public static class Materials
{
	private const int MaxDepth = 16;

	public static MatNode Build(string key, int amount, int quality)
	{
		GameData gameData = GameData.Get();
		if (gameData == null)
		{
			return null;
		}
		HashSet<string> path = new HashSet<string>();
		return Expand(key, amount, quality, gameData, path, 0);
	}

	public static Dictionary<string, int> Totals(MatNode root)
	{
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		if (root == null)
		{
			return dictionary;
		}
		Collect(root, dictionary);
		return dictionary;
	}

	private static void Collect(MatNode node, Dictionary<string, int> totals)
	{
		if (node.IsRaw)
		{
			totals.TryGetValue(node.Key, out var value);
			totals[node.Key] = value + node.Amount;
			return;
		}
		foreach (MatNode child in node.Children)
		{
			Collect(child, totals);
		}
	}

	private static MatNode Expand(string key, int amount, int quality, GameData data, HashSet<string> path, int depth)
	{
		MatNode matNode = new MatNode
		{
			Key = key,
			Display = data.DisplayOf(key),
			Amount = amount
		};
		if (amount <= 0)
		{
			return matNode;
		}
		if (depth >= 16 || path.Contains(key))
		{
			return matNode;
		}
		if (data.ByKey.TryGetValue(key, out var value))
		{
			Requirement[] resources = value.Resources;
			if (resources == null || resources.Length == 0)
			{
				return matNode;
			}
			int num = Math.Max(1, value.OutputAmount);
			int num2 = (amount + num - 1) / num;
			matNode.Note = (value.IsPiece ? "build" : ((value.Station != null) ? (value.Station + ((value.StationLevel > 1) ? (" lvl " + value.StationLevel) : "")) : "craft"));
			path.Add(key);
			Requirement[] array = resources;
			foreach (Requirement val in array)
			{
				if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_resItem.m_itemData != null && val.m_resItem.m_itemData.m_shared != null)
				{
					int amount2 = val.GetAmount(quality);
					if (amount2 > 0)
					{
						string name = val.m_resItem.m_itemData.m_shared.m_name;
						MatNode item = Expand(name, amount2 * num2, quality, data, path, depth + 1);
						matNode.Children.Add(item);
					}
				}
			}
			path.Remove(key);
			return matNode;
		}
		if (CraftPlannerPlugin.DecomposeProcessing != null && CraftPlannerPlugin.DecomposeProcessing.Value && data.Processing.TryGetValue(key, out var value2))
		{
			path.Add(key);
			MatNode matNode2 = Expand(value2, amount, quality, data, path, depth + 1);
			path.Remove(key);
			matNode2.Note = "smelted";
			matNode.Children.Add(matNode2);
		}
		return matNode;
	}
}
internal sealed class PlannerUI
{
	private const int WindowId = 202047470;

	private static readonly Color ColorOk = new Color(0.62f, 0.95f, 0.62f);

	private static readonly Color ColorMissing = new Color(1f, 0.78f, 0.42f);

	private static readonly Color ColorRaw = new Color(0.8f, 0.88f, 1f);

	private static readonly Color ColorNote = new Color(0.62f, 0.62f, 0.62f);

	private Rect _rect = new Rect(90f, 70f, 880f, 600f);

	private string _search = string.Empty;

	private List<RecipeEntry> _filtered = new List<RecipeEntry>();

	private RecipeEntry _selected;

	private int _quality = 1;

	private int _maxQuality = 1;

	private int _target = 1;

	private Vector2 _listScroll;

	private Vector2 _detailScroll;

	private GameData _data;

	private MatNode _tree;

	private Dictionary<string, int> _totals = new Dictionary<string, int>();

	private Dictionary<string, int> _have = new Dictionary<string, int>();

	private bool _prevCursorVisible;

	private CursorLockMode _prevCursorLock;

	private bool _errorLogged;

	private GUIStyle _title;

	private GUIStyle _section;

	private GUIStyle _node;

	private GUIStyle _dim;

	public bool IsOpen { get; private set; }

	public static bool Visible { get; private set; }

	public void Toggle()
	{
		if (IsOpen)
		{
			Close();
		}
		else
		{
			Open();
		}
	}

	public void Open()
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		if (!IsOpen)
		{
			Strings.Refresh();
			_prevCursorVisible = Cursor.visible;
			_prevCursorLock = Cursor.lockState;
			Cursor.visible = true;
			Cursor.lockState = (CursorLockMode)0;
			IsOpen = true;
			Visible = true;
			Stopwatch stopwatch = Stopwatch.StartNew();
			RefreshData();
			Rescan();
			if (CraftPlannerPlugin.Log != null)
			{
				CraftPlannerPlugin.Log.LogInfo((object)("Planner opened in " + stopwatch.ElapsedMilliseconds + " ms"));
			}
		}
	}

	public void Close()
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		if (IsOpen)
		{
			IsOpen = false;
			Visible = false;
			Cursor.visible = _prevCursorVisible;
			Cursor.lockState = _prevCursorLock;
		}
	}

	public void Tick()
	{
		Cursor.visible = true;
		Cursor.lockState = (CursorLockMode)0;
	}

	private void RefreshData()
	{
		try
		{
			_data = GameData.Get();
		}
		catch (Exception ex)
		{
			CraftPlannerPlugin.Log.LogError((object)("Failed to index game data: " + ex));
			_data = null;
		}
		ApplyFilter();
		if (_selected != null)
		{
			_selected = ((_data != null && _data.ByKey.TryGetValue(_selected.Key, out var value)) ? value : null);
		}
		Rebuild();
	}

	private void ApplyFilter()
	{
		_filtered.Clear();
		if (_data == null)
		{
			return;
		}
		string text = (_search ?? string.Empty).Trim();
		if (text.Length == 0)
		{
			_filtered.AddRange(_data.All);
			return;
		}
		foreach (RecipeEntry item in _data.All)
		{
			if (!string.IsNullOrEmpty(item.SearchIndex) && item.SearchIndex.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				_filtered.Add(item);
			}
		}
	}

	private void Select(RecipeEntry entry)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		_selected = entry;
		_quality = 1;
		_maxQuality = MaxQuality(entry);
		_detailScroll = Vector2.zero;
		Rebuild();
	}

	private static int MaxQuality(RecipeEntry entry)
	{
		if (entry == null || entry.IsPiece)
		{
			return 1;
		}
		if ((Object)(object)entry.Recipe == (Object)null || (Object)(object)entry.Recipe.m_item == (Object)null)
		{
			return 1;
		}
		SharedData val = ((entry.Recipe.m_item.m_itemData != null) ? entry.Recipe.m_item.m_itemData.m_shared : null);
		return (val == null) ? 1 : Mathf.Max(1, val.m_maxQuality);
	}

	private void Rebuild()
	{
		if (_selected == null || _data == null)
		{
			_tree = null;
			_totals = new Dictionary<string, int>();
		}
		else
		{
			_tree = Materials.Build(_selected.Key, Mathf.Max(1, _target), _quality);
			_totals = Materials.Totals(_tree);
		}
	}

	private void Rescan()
	{
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			_have = new Dictionary<string, int>();
		}
		else
		{
			_have = StorageScanner.Scan(Mathf.Max(1f, CraftPlannerPlugin.ScanRadius.Value));
		}
	}

	public void Draw()
	{
		//IL_0023: 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_0052: Expected O, but got Unknown
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		if (!IsOpen)
		{
			return;
		}
		try
		{
			InitStyles();
			_rect = GUILayout.Window(202047470, _rect, new WindowFunction(DrawWindow), Strings.T("Title") + "  v0.1.0", Array.Empty<GUILayoutOption>());
		}
		catch (Exception ex)
		{
			if (!_errorLogged)
			{
				_errorLogged = true;
				CraftPlannerPlugin.Log.LogError((object)("Planner UI error: " + ex));
			}
			Close();
		}
	}

	public void Prewarm()
	{
		try
		{
			InitStyles();
		}
		catch
		{
		}
	}

	private void DrawWindow(int id)
	{
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		if (_data == null && (Object)(object)Player.m_localPlayer != (Object)null)
		{
			RefreshData();
		}
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		DrawList();
		GUILayout.Space(8f);
		DrawDetails();
		GUILayout.EndHorizontal();
		GUI.DragWindow(new Rect(0f, 0f, 10000f, 22f));
	}

	private void DrawList()
	{
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_0173: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(300f) });
		GUILayout.Label(Strings.T("Search"), _dim, Array.Empty<GUILayoutOption>());
		string text = GUILayout.TextField(_search ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) });
		if (text != _search)
		{
			_search = text;
			ApplyFilter();
		}
		GUILayout.Space(6f);
		_listScroll = GUILayout.BeginScrollView(_listScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
		if (_data == null)
		{
			GUILayout.Label(Strings.T("LoadWorld"), _node, Array.Empty<GUILayoutOption>());
		}
		else
		{
			foreach (RecipeEntry item in _filtered)
			{
				bool flag = _selected != null && _selected.Key == item.Key;
				Color backgroundColor = GUI.backgroundColor;
				if (flag)
				{
					GUI.backgroundColor = new Color(0.42f, 0.62f, 0.42f);
				}
				if (GUILayout.Button(item.Display, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }))
				{
					Select(item);
				}
				GUI.backgroundColor = backgroundColor;
			}
			if (_filtered.Count == 0)
			{
				GUILayout.Label(Strings.T("NothingFound"), _dim, Array.Empty<GUILayoutOption>());
			}
		}
		GUILayout.EndScrollView();
		GUILayout.Label(Strings.T("Close") + "  •  " + _filtered.Count + " " + Strings.T("Entries"), _dim, Array.Empty<GUILayoutOption>());
		GUILayout.EndVertical();
	}

	private void DrawDetails()
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_021d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		//IL_0243: Unknown result type (might be due to invalid IL or missing references)
		//IL_0296: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
		_detailScroll = GUILayout.BeginScrollView(_detailScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
		if (_selected == null)
		{
			GUILayout.Label(Strings.T("Pick"), _node, Array.Empty<GUILayoutOption>());
		}
		else
		{
			GUILayout.Label(_selected.Display, _title, Array.Empty<GUILayoutOption>());
			if (!string.IsNullOrEmpty(_selected.Station))
			{
				GUILayout.Label(Strings.T("Station") + " " + _selected.Station + ((_selected.StationLevel > 1) ? ("  lvl " + _selected.StationLevel) : string.Empty), _dim, Array.Empty<GUILayoutOption>());
			}
			if (!_selected.IsPiece && _selected.OutputAmount > 1)
			{
				GUILayout.Label(Strings.Format("Output", _selected.OutputAmount), _dim, Array.Empty<GUILayoutOption>());
			}
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(Strings.T("Target"), _dim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) });
			_target = AmountButton(1, _target);
			_target = AmountButton(5, _target);
			_target = AmountButton(10, _target);
			_target = AmountButton(25, _target);
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			if (_maxQuality > 1)
			{
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label(Strings.T("Quality"), _dim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) });
				for (int i = 1; i <= _maxQuality; i++)
				{
					Color backgroundColor = GUI.backgroundColor;
					if (i == _quality)
					{
						GUI.backgroundColor = new Color(0.42f, 0.62f, 0.42f);
					}
					if (GUILayout.Button(i.ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }) && i != _quality)
					{
						_quality = i;
						Rebuild();
					}
					GUI.backgroundColor = backgroundColor;
				}
				GUILayout.FlexibleSpace();
				GUILayout.EndHorizontal();
			}
			GUILayout.Space(6f);
			GUILayout.Label(Strings.T("Ingredients"), _section, Array.Empty<GUILayoutOption>());
			if (_tree != null)
			{
				DrawNode(_tree, 0);
			}
			GUILayout.Space(10f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(Strings.T("Raw"), _section, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			if (GUILayout.Button(Strings.T("Rescan"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) }))
			{
				Rescan();
			}
			GUILayout.EndHorizontal();
			DrawTotals();
		}
		GUILayout.EndScrollView();
		GUILayout.EndVertical();
	}

	private int AmountButton(int value, int current)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: 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)
		Color backgroundColor = GUI.backgroundColor;
		if (current == value)
		{
			GUI.backgroundColor = new Color(0.42f, 0.62f, 0.42f);
		}
		int num = (GUILayout.Button("×" + value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(46f) }) ? value : current);
		GUI.backgroundColor = backgroundColor;
		if (num != current)
		{
			Rebuild();
		}
		return num;
	}

	private void DrawNode(MatNode node, int depth)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0123: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Space((float)depth * 18f);
		if (node.IsRaw)
		{
			GUI.color = ColorRaw;
		}
		else
		{
			GUI.color = Color.white;
		}
		GUILayout.Label(node.Display + "  ×" + node.Amount, _node, Array.Empty<GUILayoutOption>());
		if (!string.IsNullOrEmpty(node.Note))
		{
			GUI.color = ColorNote;
			GUILayout.Label("[" + NoteName(node.Note) + "]", _dim, Array.Empty<GUILayoutOption>());
		}
		GUI.color = Color.white;
		if (node.IsRaw)
		{
			_have.TryGetValue(node.Key, out var value);
			GUI.color = ((value >= node.Amount) ? ColorOk : ColorMissing);
			GUILayout.Label(Strings.T("Have") + " " + value, _dim, Array.Empty<GUILayoutOption>());
			GUI.color = Color.white;
		}
		GUILayout.FlexibleSpace();
		GUILayout.EndHorizontal();
		foreach (MatNode child in node.Children)
		{
			DrawNode(child, depth + 1);
		}
	}

	private static string NoteName(string note)
	{
		return note switch
		{
			"build" => Strings.T("Build"), 
			"craft" => Strings.T("Craft"), 
			"smelted" => Strings.T("Smelted"), 
			_ => note, 
		};
	}

	private void DrawTotals()
	{
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
		if (_totals == null || _totals.Count == 0)
		{
			GUILayout.Label(Strings.T("NoMaterials"), _dim, Array.Empty<GUILayoutOption>());
			return;
		}
		List<string> list = new List<string>(_totals.Keys);
		list.Sort((string a, string b) => string.Compare(_data.DisplayOf(a), _data.DisplayOf(b), StringComparison.CurrentCultureIgnoreCase));
		foreach (string item in list)
		{
			int num = _totals[item];
			_have.TryGetValue(item, out var value);
			GUI.color = ((value >= num) ? ColorOk : ColorMissing);
			GUILayout.Label(_data.DisplayOf(item) + "   " + value + " / " + num, _node, Array.Empty<GUILayoutOption>());
			GUI.color = Color.white;
		}
	}

	private void InitStyles()
	{
		//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_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		//IL_0044: 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_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Expected O, but got Unknown
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Expected O, but got Unknown
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Expected O, but got Unknown
		if (_title == null)
		{
			_title = new GUIStyle(GUI.skin.label)
			{
				fontSize = 19,
				fontStyle = (FontStyle)1
			};
			_section = new GUIStyle(GUI.skin.label)
			{
				fontSize = 13,
				fontStyle = (FontStyle)1
			};
			_node = new GUIStyle(GUI.skin.label)
			{
				fontSize = 13
			};
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				fontSize = 11
			};
			val.normal.textColor = new Color(0.72f, 0.72f, 0.72f);
			_dim = val;
		}
	}
}
[BepInPlugin("opencode.craftplanner", "Craft Planner", "0.1.0")]
public class CraftPlannerPlugin : BaseUnityPlugin
{
	public const string Guid = "opencode.craftplanner";

	public const string Version = "0.1.0";

	internal static CraftPlannerPlugin Instance;

	internal static ManualLogSource Log;

	internal static ConfigEntry<KeyCode> ToggleKey;

	internal static ConfigEntry<float> ScanRadius;

	internal static ConfigEntry<bool> DecomposeProcessing;

	internal static ConfigEntry<string> UiLanguage;

	private PlannerUI _ui;

	private Harmony _harmony;

	private void Awake()
	{
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Expected O, but got Unknown
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Expected O, but got Unknown
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "ToggleKey", (KeyCode)287, "Key that opens/closes the craft planner window.");
		ScanRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ScanRadius", 40f, "Radius in meters used when counting materials in nearby containers.");
		DecomposeProcessing = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DecomposeProcessing", true, "Show smelted bars and milled goods as their source material (ore, barley).");
		UiLanguage = ((BaseUnityPlugin)this).Config.Bind<string>("General", "UiLanguage", "auto", new ConfigDescription("UI language: auto (follow the game), en or ru.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "auto", "en", "ru" }), Array.Empty<object>()));
		Strings.Refresh();
		_ui = new PlannerUI();
		_harmony = new Harmony("opencode.craftplanner");
		_harmony.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Craft Planner v0.1.0 loaded. Toggle with " + ((object)ToggleKey.Value/*cast due to .constrained prefix*/).ToString()));
	}

	private void Update()
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		if (_ui != null)
		{
			if (Input.GetKeyDown(ToggleKey.Value) && !Menu.IsVisible() && !TextInput.IsVisible())
			{
				_ui.Toggle();
			}
			if (_ui.IsOpen)
			{
				_ui.Tick();
			}
		}
	}

	private void OnGUI()
	{
		if (_ui != null)
		{
			if (!_ui.IsOpen)
			{
				_ui.Prewarm();
			}
			_ui.Draw();
		}
	}

	private void OnDestroy()
	{
		_ui?.Close();
		_ui = null;
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		_harmony = null;
	}
}
public static class StorageScanner
{
	public static Dictionary<string, int> Scan(float radius)
	{
		//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_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		Dictionary<string, int> result = new Dictionary<string, int>();
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return result;
		}
		Accumulate(((Humanoid)localPlayer).GetInventory(), result);
		Vector3 position = ((Component)localPlayer).transform.position;
		float num = radius * radius;
		Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
		Container[] array2 = array;
		foreach (Container val in array2)
		{
			if (!((Object)(object)val == (Object)null))
			{
				Vector3 val2 = ((Component)val).transform.position - position;
				if (!(((Vector3)(ref val2)).sqrMagnitude > num))
				{
					Accumulate(val.GetInventory(), result);
				}
			}
		}
		return result;
	}

	private static void Accumulate(Inventory inventory, Dictionary<string, int> result)
	{
		if (inventory == null)
		{
			return;
		}
		foreach (ItemData allItem in inventory.GetAllItems())
		{
			if (allItem != null && allItem.m_shared != null)
			{
				string name = allItem.m_shared.m_name;
				if (!string.IsNullOrEmpty(name))
				{
					result.TryGetValue(name, out var value);
					result[name] = value + allItem.m_stack;
				}
			}
		}
	}
}
internal static class Strings
{
	private static readonly Dictionary<string, string> En = new Dictionary<string, string>
	{
		{ "Title", "Craft Planner" },
		{ "Search", "Search (ru/en)" },
		{ "NothingFound", "Nothing found." },
		{ "LoadWorld", "Load a world to use the planner." },
		{ "Pick", "Pick an item or a building piece on the left." },
		{ "Target", "Target:" },
		{ "Quality", "Quality:" },
		{ "Ingredients", "Ingredients" },
		{ "Raw", "Needed raw materials" },
		{ "Rescan", "Rescan containers" },
		{ "Close", "F6 to close" },
		{ "Entries", "entries" },
		{ "Station", "Station:" },
		{ "Output", "Output: x{0} per craft" },
		{ "Have", "have" },
		{ "NoMaterials", "No materials needed." },
		{ "Build", "build" },
		{ "Craft", "craft" },
		{ "Smelted", "smelted" }
	};

	private static readonly Dictionary<string, string> Ru = new Dictionary<string, string>
	{
		{ "Title", "Планировщик крафта" },
		{ "Search", "Поиск (рус/англ)" },
		{ "NothingFound", "Ничего не найдено." },
		{ "LoadWorld", "Загрузи мир, чтобы пользоваться планировщиком." },
		{ "Pick", "Выбери предмет или постройку слева." },
		{ "Target", "Нужно:" },
		{ "Quality", "Качество:" },
		{ "Ingredients", "Состав" },
		{ "Raw", "Нужно сырья" },
		{ "Rescan", "Обновить сундуки" },
		{ "Close", "F6 — закрыть" },
		{ "Entries", "позиций" },
		{ "Station", "Станция:" },
		{ "Output", "Выход: x{0} за крафт" },
		{ "Have", "есть" },
		{ "NoMaterials", "Материалы не нужны." },
		{ "Build", "постройка" },
		{ "Craft", "крафт" },
		{ "Smelted", "плавка" }
	};

	public static bool IsRussian { get; private set; }

	public static void Refresh()
	{
		string a = ((CraftPlannerPlugin.UiLanguage != null) ? CraftPlannerPlugin.UiLanguage.Value : "auto");
		if (string.Equals(a, "ru", StringComparison.OrdinalIgnoreCase))
		{
			IsRussian = true;
			return;
		}
		if (string.Equals(a, "en", StringComparison.OrdinalIgnoreCase))
		{
			IsRussian = false;
			return;
		}
		bool isRussian = false;
		try
		{
			Localization instance = Localization.instance;
			string text = ((instance != null) ? instance.GetSelectedLanguage() : null);
			isRussian = !string.IsNullOrEmpty(text) && text.IndexOf("Russian", StringComparison.OrdinalIgnoreCase) >= 0;
		}
		catch
		{
		}
		IsRussian = isRussian;
	}

	public static string T(string key)
	{
		Dictionary<string, string> dictionary = (IsRussian ? Ru : En);
		string value;
		return dictionary.TryGetValue(key, out value) ? value : key;
	}

	public static string Format(string key, object arg)
	{
		return string.Format(T(key), arg);
	}
}