Decompiled source of Crafting Achievement Tracker v1.0.0

ArtisanTracker.dll

Decompiled 11 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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 Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("ArtisanTracker")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Artisan Tracker")]
[assembly: AssemblyTitle("Artisan Tracker")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ArtisanTracker
{
	internal static class GameStats
	{
		internal class Req
		{
			public string Stat;

			public float Amount;
		}

		internal class CraftAch
		{
			public string Id;

			public string Name;

			public string Display;

			public bool Unlocked;

			public List<Req> Reqs = new List<Req>();
		}

		private const BindingFlags Any = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static bool _fallbackLogged;

		internal static object Get(object obj, string name)
		{
			if (obj == null)
			{
				return null;
			}
			Type type = obj.GetType();
			while (type != null)
			{
				FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					try
					{
						return field.GetValue(obj);
					}
					catch (Exception)
					{
						return null;
					}
				}
				type = type.BaseType;
			}
			return null;
		}

		private static float ToFloat(object o)
		{
			try
			{
				return (o == null) ? 0f : Convert.ToSingle(o);
			}
			catch (Exception)
			{
				return 0f;
			}
		}

		internal static string Loc(string token)
		{
			if (string.IsNullOrEmpty(token))
			{
				return token;
			}
			if (Localization.instance == null)
			{
				return token;
			}
			return Localization.instance.Localize(token);
		}

		internal static Dictionary<string, float> CraftedTotals()
		{
			Dictionary<string, float> dictionary = new Dictionary<string, float>();
			PlayerProfile val = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile() : null);
			if (val == null)
			{
				return dictionary;
			}
			if (!(Get(val, "m_playerStats") is IEnumerable enumerable))
			{
				return dictionary;
			}
			foreach (object item in enumerable)
			{
				if (!(Get(item, "m_itemCraftStats") is IDictionary dictionary2))
				{
					continue;
				}
				foreach (DictionaryEntry item2 in dictionary2)
				{
					if (item2.Key is string key)
					{
						float num = ToFloat(item2.Value);
						if (!dictionary.TryGetValue(key, out var value) || num > value)
						{
							dictionary[key] = num;
						}
					}
				}
			}
			return dictionary;
		}

		internal static HashSet<string> CraftedNames()
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, float> item in CraftedTotals())
			{
				if (item.Value > 0f)
				{
					hashSet.Add(item.Key);
				}
			}
			return hashSet;
		}

		internal static bool Met(Dictionary<string, float> crafted, Req r)
		{
			if (!crafted.TryGetValue(r.Stat, out var value))
			{
				return false;
			}
			return value >= ((r.Amount > 0f) ? r.Amount : 1f);
		}

		internal static List<CraftAch> CraftAchievements()
		{
			List<CraftAch> list = new List<CraftAch>();
			Type type = typeof(Player).Assembly.GetType("Achievements");
			if (type == null)
			{
				return list;
			}
			FieldInfo field = type.GetField("m_instance", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			object obj = null;
			if (field != null)
			{
				try
				{
					obj = field.GetValue(null);
				}
				catch (Exception)
				{
				}
			}
			if (!(Get(obj, "m_achievementLists") is IEnumerable enumerable))
			{
				return list;
			}
			foreach (object item in enumerable)
			{
				if (!(Get(item, "m_achievements") is IEnumerable enumerable2))
				{
					continue;
				}
				foreach (object item2 in enumerable2)
				{
					if (!(Get(item2, "m_itemCraftTriggers") is IEnumerable enumerable3))
					{
						continue;
					}
					CraftAch craftAch = new CraftAch();
					foreach (object item3 in enumerable3)
					{
						string text = Get(item3, "m_stat") as string;
						if (!string.IsNullOrEmpty(text))
						{
							craftAch.Reqs.Add(new Req
							{
								Stat = text,
								Amount = ToFloat(Get(item3, "m_amount"))
							});
						}
					}
					if (craftAch.Reqs.Count != 0)
					{
						craftAch.Id = (Get(item2, "m_id") as string) ?? "?";
						craftAch.Name = (Get(item2, "m_name") as string) ?? craftAch.Id;
						craftAch.Display = Loc(craftAch.Name);
						object obj2 = Get(item2, "m_unlocked");
						craftAch.Unlocked = obj2 is bool && (bool)obj2;
						list.Add(craftAch);
					}
				}
			}
			return list;
		}

		internal static CraftAch Tracked(List<CraftAch> all)
		{
			if (all == null || all.Count == 0)
			{
				return null;
			}
			string[] array = (from k in (ArtisanTrackerPlugin.TrackedAchievement.Value ?? "").Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries)
				select k.Trim() into k
				where k.Length > 0
				select k).ToArray();
			foreach (CraftAch item in all)
			{
				string[] array2 = array;
				foreach (string kw in array2)
				{
					if (Has(item.Id, kw) || Has(item.Name, kw) || Has(item.Display, kw))
					{
						return item;
					}
				}
			}
			CraftAch craftAch = all.OrderByDescending((CraftAch a) => a.Reqs.Count).First();
			if (!_fallbackLogged)
			{
				_fallbackLogged = true;
				ArtisanTrackerPlugin.Warn("No achievement matches '" + ArtisanTrackerPlugin.TrackedAchievement.Value + "'. Using the largest one: " + craftAch.Id + " (" + craftAch.Reqs.Count + " items). Adjust TrackedAchievement in the config.");
			}
			return craftAch;
		}

		private static bool Has(string s, string kw)
		{
			if (s != null)
			{
				return s.IndexOf(kw, StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		internal static bool StatusSets(out HashSet<string> pending, out HashSet<string> done)
		{
			pending = new HashSet<string>(StringComparer.Ordinal);
			done = new HashSet<string>(StringComparer.Ordinal);
			CraftAch craftAch = Tracked(CraftAchievements());
			if (craftAch == null)
			{
				return false;
			}
			Dictionary<string, float> crafted = CraftedTotals();
			foreach (Req req in craftAch.Reqs)
			{
				if (Met(crafted, req))
				{
					done.Add(req.Stat);
				}
				else
				{
					pending.Add(req.Stat);
				}
			}
			return true;
		}
	}
	internal static class Lang
	{
		private static bool _spanish;

		private static float _nextCheck;

		private static readonly Dictionary<string, string> En = new Dictionary<string, string>
		{
			{ "panel.nodata", "Artisan Tracker: no game data yet." },
			{ "panel.summary", "Summary by station" },
			{ "panel.complete", "complete" },
			{ "panel.missing_of", "{0} missing of {1}" },
			{ "panel.nothing_missing", "Nothing missing in this list." },
			{ "panel.to_craft", "Still to craft ({0})" },
			{ "panel.completed", "COMPLETED!" },
			{ "panel.missing", "{0} missing" },
			{ "panel.unlocked", "(achievement unlocked)" },
			{ "panel.lists", "Lists:" },
			{ "panel.footer", "{0}: next list   ·   Left/Right or PgUp/PgDn: page   ·   Esc: close        page {1}/{2}" },
			{ "panel.lvl", "lv" },
			{ "panel.norecipe", "Cooked / no station recipe" },
			{ "panel.nostation", "By hand (no station)" },
			{ "cmd.enter_world", "[Artisan] Enter a world first." },
			{ "cmd.usage", "[Artisan] Usage: artisan status | pending [type] | done [type] | debug" },
			{ "cmd.no_ach", "[Artisan] No crafting achievements found (is a world loaded?)." },
			{ "cmd.status_head", "[Artisan] Progress according to the game itself (> = list you follow):" },
			{ "cmd.unlocked", "UNLOCKED" },
			{ "cmd.status_hint", "   To follow another list, change TrackedAchievement in the config (ids above)." },
			{ "cmd.pending", "Still to craft" },
			{ "cmd.crafted", "Crafted" },
			{ "cmd.type_contains", " (type contains '{0}')" },
			{ "cmd.more", "   ... use a type filter to see the rest (e.g. artisan pending Chest)" }
		};

		private static readonly Dictionary<string, string> Es = new Dictionary<string, string>
		{
			{ "panel.nodata", "Artisan Tracker: sin datos del juego aun." },
			{ "panel.summary", "Resumen por mesa" },
			{ "panel.complete", "completo" },
			{ "panel.missing_of", "faltan {0} de {1}" },
			{ "panel.nothing_missing", "No falta nada en esta lista." },
			{ "panel.to_craft", "Faltan por craftear ({0})" },
			{ "panel.completed", "¡COMPLETADO!" },
			{ "panel.missing", "faltan {0}" },
			{ "panel.unlocked", "(logro desbloqueado)" },
			{ "panel.lists", "Listas:" },
			{ "panel.footer", "{0}: siguiente lista   ·   ←/→ o RePág/AvPág: página   ·   Esc: cerrar        página {1}/{2}" },
			{ "panel.lvl", "nv" },
			{ "panel.norecipe", "Cocinado / sin receta de mesa" },
			{ "panel.nostation", "A mano (sin mesa)" },
			{ "cmd.enter_world", "[Artisan] Entra a un mundo primero." },
			{ "cmd.usage", "[Artisan] Uso: artisan status | pending [tipo] | done [tipo] | debug" },
			{ "cmd.no_ach", "[Artisan] No encuentro logros de crafteo en el juego (¿mundo cargado?)." },
			{ "cmd.status_head", "[Artisan] Progreso segun el propio juego (> = lista que sigues):" },
			{ "cmd.unlocked", "DESBLOQUEADO" },
			{ "cmd.status_hint", "   Para seguir otra lista, cambia TrackedAchievement en la config (ids de arriba)." },
			{ "cmd.pending", "Pendientes" },
			{ "cmd.crafted", "Crafteados" },
			{ "cmd.type_contains", " (tipo contiene '{0}')" },
			{ "cmd.more", "   ... usa un filtro de tipo para ver el resto (ej: artisan pending Chest)" }
		};

		private static bool IsSpanish()
		{
			if (Time.unscaledTime < _nextCheck)
			{
				return _spanish;
			}
			_nextCheck = Time.unscaledTime + 2f;
			string text = ((ArtisanTrackerPlugin.Language != null) ? ArtisanTrackerPlugin.Language.Value : "Auto");
			if (text == "Spanish")
			{
				_spanish = true;
			}
			else if (text == "English")
			{
				_spanish = false;
			}
			else
			{
				_spanish = PlayerPrefs.GetString("language", "English").StartsWith("Spanish", StringComparison.OrdinalIgnoreCase);
			}
			return _spanish;
		}

		public static string T(string key)
		{
			if (!(IsSpanish() ? Es : En).TryGetValue(key, out var value))
			{
				return key;
			}
			return value;
		}

		public static string F(string key, params object[] args)
		{
			return string.Format(T(key), args);
		}
	}
	internal static class Panel
	{
		private class Group
		{
			public string Name;

			public int Level;

			public int Total;

			public int Done;

			public List<string> Missing = new List<string>();

			public int MissingCount => Total - Done;
		}

		private static int _index = -1;

		private static int _pageNo;

		private static int _pages = 1;

		private static bool _dirty;

		private static float _nextRefresh;

		private static string _title = "";

		private static string _tabs = "";

		private static List<string> _lines = new List<string>();

		private static GUIStyle _label;

		private static GUIStyle _box;

		private static Texture2D _bg;

		public static void MarkDirty()
		{
			_dirty = true;
		}

		public static void Tick()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				_index = -1;
				return;
			}
			bool flag = Console.IsVisible() || ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus());
			if (!flag && Input.GetKeyDown(ArtisanTrackerPlugin.SummaryKey.Value))
			{
				_index++;
				_pageNo = 0;
				if (_index == 0 && GameStats.CraftAchievements().Count == 0)
				{
					_index = -1;
					((Character)Player.m_localPlayer).Message((MessageType)2, Lang.T("panel.nodata"), 0, (Sprite)null, false);
					return;
				}
				_dirty = true;
			}
			if (_index < 0)
			{
				return;
			}
			if (!flag)
			{
				if (Input.GetKeyDown((KeyCode)27))
				{
					_index = -1;
					return;
				}
				if (Input.GetKeyDown((KeyCode)275) || Input.GetKeyDown((KeyCode)281))
				{
					_pageNo++;
				}
				if (Input.GetKeyDown((KeyCode)276) || Input.GetKeyDown((KeyCode)280))
				{
					_pageNo--;
				}
				if (Input.GetKeyDown((KeyCode)278))
				{
					_pageNo = 0;
				}
				_pageNo = Mathf.Clamp(_pageNo, 0, Mathf.Max(0, _pages - 1));
			}
			if (_dirty || Time.unscaledTime >= _nextRefresh)
			{
				Refresh();
			}
		}

		private static List<GameStats.CraftAch> Ordered()
		{
			List<GameStats.CraftAch> list = GameStats.CraftAchievements();
			GameStats.CraftAch tracked = GameStats.Tracked(list);
			return (from a in list
				orderby (a != tracked) ? 1 : 0, a.Reqs.Count descending, a.Id
				select a).ToList();
		}

		private static void Refresh()
		{
			_dirty = false;
			_nextRefresh = Time.unscaledTime + 1.5f;
			try
			{
				List<GameStats.CraftAch> list = Ordered();
				if (list.Count == 0 || _index >= list.Count)
				{
					_index = -1;
				}
				else
				{
					Build(list, _index);
				}
			}
			catch (Exception ex)
			{
				ArtisanTrackerPlugin.Warn("Panel fallo: " + ex.Message);
				_index = -1;
			}
		}

		private static string GroupLabel(Group g)
		{
			if (g.Level <= 0)
			{
				return g.Name;
			}
			return g.Name + " " + Lang.T("panel.lvl") + g.Level;
		}

		private static void Build(List<GameStats.CraftAch> all, int index)
		{
			GameStats.CraftAch craftAch = all[index];
			Dictionary<string, float> crafted = GameStats.CraftedTotals();
			Dictionary<string, Recipe> dictionary = Commands.RecipeMap();
			Dictionary<string, Group> dictionary2 = new Dictionary<string, Group>();
			int num = 0;
			foreach (GameStats.Req req in craftAch.Reqs)
			{
				int level = 0;
				string text;
				if (!dictionary.TryGetValue(req.Stat, out var value))
				{
					text = Lang.T("panel.norecipe");
				}
				else if ((Object)(object)value.m_craftingStation == (Object)null)
				{
					text = Lang.T("panel.nostation");
				}
				else
				{
					text = GameStats.Loc(value.m_craftingStation.m_name);
					level = value.m_minStationLevel;
				}
				string key = text + "|" + level;
				if (!dictionary2.TryGetValue(key, out var value2))
				{
					value2 = (dictionary2[key] = new Group
					{
						Name = text,
						Level = level
					});
				}
				value2.Total++;
				if (GameStats.Met(crafted, req))
				{
					value2.Done++;
					num++;
				}
				else
				{
					value2.Missing.Add(GameStats.Loc(req.Stat));
				}
			}
			int num2 = craftAch.Reqs.Count - num;
			_title = "<b>" + craftAch.Display + "</b>    " + num + "/" + craftAch.Reqs.Count + "    ·    " + ((num2 > 0) ? Lang.F("panel.missing", num2) : ("<color=#7CFC7C>" + Lang.T("panel.completed") + "</color>")) + (craftAch.Unlocked ? ("    " + Lang.T("panel.unlocked")) : "");
			StringBuilder stringBuilder = new StringBuilder(Lang.T("panel.lists") + "   ");
			for (int i = 0; i < all.Count; i++)
			{
				if (i == index)
				{
					stringBuilder.Append("<color=#FFD24D><b>[").Append(all[i].Display).Append("]</b></color>");
				}
				else
				{
					stringBuilder.Append(all[i].Display);
				}
				stringBuilder.Append("     ");
			}
			_tabs = stringBuilder.ToString();
			List<Group> list = (from x in dictionary2.Values
				orderby x.MissingCount descending, x.Name, x.Level
				select x).ToList();
			List<string> list2 = new List<string>();
			list2.Add("<b>" + Lang.T("panel.summary") + "</b>");
			foreach (Group item in list)
			{
				if (item.MissingCount == 0)
				{
					list2.Add("<color=#7CFC7C>" + GroupLabel(item) + ": " + Lang.T("panel.complete") + "</color>");
				}
				else
				{
					list2.Add(GroupLabel(item) + ": " + Lang.F("panel.missing_of", "<color=#FFB84D>" + item.MissingCount + "</color>", item.Total));
				}
			}
			list2.Add("");
			if (num2 == 0)
			{
				list2.Add(Lang.T("panel.nothing_missing"));
			}
			else
			{
				list2.Add("<b>" + Lang.F("panel.to_craft", num2) + "</b>");
				foreach (Group item2 in list)
				{
					if (item2.MissingCount == 0)
					{
						continue;
					}
					list2.Add("");
					list2.Add("<color=#9FD4FF>" + GroupLabel(item2) + " (" + item2.MissingCount + ")</color>");
					foreach (string item3 in item2.Missing.OrderBy((string x) => x))
					{
						list2.Add("  " + item3);
					}
				}
			}
			_lines = list2;
		}

		private static void EnsureStyles()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Expected O, but got Unknown
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			if (_label == null || _box == null || !((Object)(object)_bg != (Object)null))
			{
				_bg = new Texture2D(1, 1);
				_bg.SetPixel(0, 0, new Color(0.05f, 0.05f, 0.07f, 0.9f));
				_bg.Apply();
				((Object)_bg).hideFlags = (HideFlags)61;
				_box = new GUIStyle(GUI.skin.box);
				_box.normal.background = _bg;
				_label = new GUIStyle(GUI.skin.label);
				_label.richText = true;
				_label.wordWrap = false;
				_label.normal.textColor = Color.white;
			}
		}

		public static void Draw()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			if (_index >= 0 && _lines != null && Event.current != null && (int)Event.current.type == 7)
			{
				EnsureStyles();
				float num = Mathf.Max(0.75f, (float)Screen.height / 1080f);
				int num2 = Mathf.Max(10, Mathf.RoundToInt((float)ArtisanTrackerPlugin.PanelFontSize.Value * num));
				_label.fontSize = num2;
				float num3 = (float)num2 * 1.5f;
				float num4 = 14f * num;
				float num5 = Mathf.Min((float)Screen.width - 40f, 1250f * num);
				float num6 = Mathf.Min((float)Screen.height - 60f, 900f * num);
				float num7 = ((float)Screen.width - num5) / 2f;
				float num8 = ((float)Screen.height - num6) / 2f;
				float num9 = num5 - 2f * num4;
				GUI.Box(new Rect(num7, num8, num5, num6), GUIContent.none, _box);
				float num10 = num7 + num4;
				float num11 = num8 + num4;
				GUI.Label(new Rect(num10, num11, num9, num3), _title, _label);
				num11 += num3;
				GUI.Label(new Rect(num10, num11, num9, num3), _tabs, _label);
				num11 += num3 * 1.4f;
				float num12 = num11;
				float num13 = num8 + num6 - num4 - num3;
				int num14 = Mathf.Max(1, Mathf.FloorToInt((num13 - num12) / num3));
				int num15 = Mathf.Max(1, Mathf.FloorToInt(num9 / (380f * num)));
				float num16 = num9 / (float)num15;
				int num17 = num14 * num15;
				_pages = Mathf.Max(1, (_lines.Count + num17 - 1) / num17);
				int num18 = Mathf.Clamp(_pageNo, 0, _pages - 1);
				int num19 = num18 * num17;
				for (int i = 0; i < num17 && num19 + i < _lines.Count; i++)
				{
					int num20 = i / num14;
					int num21 = i % num14;
					GUI.Label(new Rect(num10 + (float)num20 * num16, num12 + (float)num21 * num3, num16, num3), _lines[num19 + i], _label);
				}
				string text = Lang.F("panel.footer", ArtisanTrackerPlugin.SummaryKey.Value, num18 + 1, _pages);
				GUI.Label(new Rect(num10, num8 + num6 - num4 - num3, num9, num3), text, _label);
			}
		}
	}
	[BepInPlugin("com.maoba.artisantracker", "Artisan Tracker", "1.0.0")]
	public class ArtisanTrackerPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.maoba.artisantracker";

		public const string PluginName = "Artisan Tracker";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<KeyCode> SummaryKey;

		internal static ConfigEntry<bool> MarkPendingInMenu;

		internal static ConfigEntry<string> PendingMarker;

		internal static ConfigEntry<string> CraftedMarker;

		internal static ConfigEntry<bool> ShowPopup;

		internal static ConfigEntry<string> TrackedAchievement;

		internal static ConfigEntry<int> PanelFontSize;

		internal static ConfigEntry<string> Language;

		internal static ConfigEntry<bool> VerboseLogging;

		internal static void Info(string msg)
		{
			if (VerboseLogging != null && VerboseLogging.Value && Log != null)
			{
				Log.LogInfo((object)msg);
			}
		}

		internal static void Warn(string msg)
		{
			if (VerboseLogging != null && VerboseLogging.Value && Log != null)
			{
				Log.LogWarning((object)msg);
			}
		}

		private void Awake()
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Expected O, but got Unknown
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseLogging", false, "Write diagnostic messages to the BepInEx log. Leave off unless you are reporting a bug.");
			Language = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Language", "Auto", new ConfigDescription("Language of the mod's own texts. Auto follows the game's language.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Auto", "English", "Spanish" }), Array.Empty<object>()));
			SummaryKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "SummaryKey", (KeyCode)289, "Key that opens the progress panel. Each press moves to the next crafting list and the last press closes it.");
			PanelFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("General", "PanelFontSize", 15, "Font size of the progress panel (scaled with the screen resolution).");
			ShowPopup = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowPopup", true, "Show a notice whenever you craft an item that counts for an achievement.");
			MarkPendingInMenu = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "MarkPendingInMenu", true, "Mark items in the crafting menu that count for the followed achievement.");
			PendingMarker = ((BaseUnityPlugin)this).Config.Bind<string>("General", "PendingMarker", "[ ]", "Text added to items you STILL NEED to craft (empty = no mark). Do not start with a space.");
			CraftedMarker = ((BaseUnityPlugin)this).Config.Bind<string>("General", "CraftedMarker", "[✓]", "Text added to items you HAVE ALREADY crafted (empty = no mark). Do not start with a space.");
			TrackedAchievement = ((BaseUnityPlugin)this).Config.Bind<string>("General", "TrackedAchievement", "artisan|artesano", "Keywords (separated by |) that pick which achievement list is followed for the menu marks. Matched against the achievement id, name and translated name. Use 'artisan status' in the console to see the ids.");
			new Harmony("com.maoba.artisantracker").PatchAll();
		}

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

		private void OnGUI()
		{
			Panel.Draw();
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class DoCraftingPatch
	{
		private static void Prefix(out HashSet<string> __state)
		{
			__state = null;
			try
			{
				__state = GameStats.CraftedNames();
			}
			catch (Exception ex)
			{
				ArtisanTrackerPlugin.Warn("Prefix DoCrafting fallo: " + ex.Message);
			}
		}

		private static void Postfix(HashSet<string> __state)
		{
			try
			{
				if (__state == null)
				{
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					return;
				}
				List<string> list = (from n in GameStats.CraftedNames()
					where !__state.Contains(n)
					select n).ToList();
				if (list.Count == 0)
				{
					return;
				}
				Panel.MarkDirty();
				List<GameStats.CraftAch> list2 = GameStats.CraftAchievements();
				GameStats.CraftAch tracked = GameStats.Tracked(list2);
				Dictionary<string, float> crafted = GameStats.CraftedTotals();
				bool flag = false;
				foreach (GameStats.CraftAch ach in list2.OrderBy((GameStats.CraftAch a) => (a != tracked) ? 1 : 0))
				{
					string text = list.FirstOrDefault((string n) => ach.Reqs.Any((GameStats.Req r) => r.Stat == n));
					if (text != null)
					{
						flag = true;
						int num = ach.Reqs.Count((GameStats.Req r) => GameStats.Met(crafted, r));
						ArtisanTrackerPlugin.Info("[craft] nuevo: " + text + " -> " + ach.Id + " " + num + "/" + ach.Reqs.Count);
						if (ArtisanTrackerPlugin.ShowPopup.Value)
						{
							((Character)localPlayer).Message((MessageType)1, ach.Display + " +1: " + GameStats.Loc(text) + "  (" + num + "/" + ach.Reqs.Count + ")", 0, (Sprite)null, false);
						}
						break;
					}
				}
				if (!flag)
				{
					ArtisanTrackerPlugin.Info("[craft] nuevo en el juego pero no esta en ningun logro: " + string.Join(", ", list.ToArray()));
				}
			}
			catch (Exception ex)
			{
				ArtisanTrackerPlugin.Warn("Postfix DoCrafting fallo: " + ex.Message);
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList")]
	internal static class RecipeListMarkerPatch
	{
		private const BindingFlags Inst = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static void Postfix(InventoryGui __instance)
		{
			try
			{
				if (!ArtisanTrackerPlugin.MarkPendingInMenu.Value || (Object)(object)Player.m_localPlayer == (Object)null || !GameStats.StatusSets(out var pending, out var done) || !(GetMember(__instance, "m_availableRecipes") is IList { Count: not 0 } list))
				{
					return;
				}
				IList list2 = null;
				bool flag = false;
				for (int i = 0; i < list.Count; i++)
				{
					if (!TryExtract(list[i], out var recipe, out var upgrade, out var element) || upgrade != null || (Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null)
					{
						continue;
					}
					string name = recipe.m_item.m_itemData.m_shared.m_name;
					string value;
					if (pending.Contains(name))
					{
						value = ArtisanTrackerPlugin.PendingMarker.Value;
					}
					else
					{
						if (!done.Contains(name))
						{
							continue;
						}
						value = ArtisanTrackerPlugin.CraftedMarker.Value;
					}
					if ((Object)(object)element != (Object)null && SetMarker(element, value))
					{
						continue;
					}
					if (!flag)
					{
						flag = true;
						list2 = FindElementList(__instance, list);
					}
					if (list2 != null && i < list2.Count)
					{
						GameObject val = AsGameObject(list2[i]);
						if ((Object)(object)val != (Object)null)
						{
							SetMarker(val, value);
						}
					}
				}
			}
			catch (Exception ex)
			{
				ArtisanTrackerPlugin.Warn("Marcador de menu fallo: " + ex.Message);
			}
		}

		private static object GetMember(object obj, string name)
		{
			FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), name);
			if (!(fieldInfo != null))
			{
				return null;
			}
			return fieldInfo.GetValue(obj);
		}

		private static GameObject AsGameObject(object o)
		{
			if (o == null)
			{
				return null;
			}
			GameObject val = (GameObject)((o is GameObject) ? o : null);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			Component val2 = (Component)((o is Component) ? o : null);
			if ((Object)(object)val2 != (Object)null)
			{
				return val2.gameObject;
			}
			FieldInfo[] fields = o.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				object value;
				try
				{
					value = fieldInfo.GetValue(o);
				}
				catch (Exception)
				{
					continue;
				}
				val = (GameObject)((value is GameObject) ? value : null);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
				val2 = (Component)((value is Component) ? value : null);
				if ((Object)(object)val2 != (Object)null)
				{
					return val2.gameObject;
				}
			}
			return null;
		}

		private static bool SetMarker(GameObject go, string marker)
		{
			Transform val = go.transform.Find("name");
			if ((Object)(object)val == (Object)null)
			{
				Transform[] componentsInChildren = go.GetComponentsInChildren<Transform>(true);
				foreach (Transform val2 in componentsInChildren)
				{
					if (string.Equals(((Object)val2).name, "name", StringComparison.OrdinalIgnoreCase))
					{
						val = val2;
						break;
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Component[] components = ((Component)val).GetComponents<Component>();
			foreach (Component val3 in components)
			{
				PropertyInfo property = ((object)val3).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (!(property == null) && !(property.PropertyType != typeof(string)) && property.CanRead && property.CanWrite && property.GetValue(val3, null) is string s)
				{
					string s2 = StripMarker(s, ArtisanTrackerPlugin.PendingMarker.Value);
					s2 = StripMarker(s2, ArtisanTrackerPlugin.CraftedMarker.Value);
					if (!string.IsNullOrEmpty(marker))
					{
						s2 = s2 + " " + marker;
					}
					property.SetValue(val3, s2, null);
					return true;
				}
			}
			return false;
		}

		private static string StripMarker(string s, string marker)
		{
			if (string.IsNullOrEmpty(marker))
			{
				return s;
			}
			string text = " " + marker;
			if (!s.EndsWith(text, StringComparison.Ordinal))
			{
				return s;
			}
			return s.Substring(0, s.Length - text.Length);
		}

		private static IList FindElementList(InventoryGui gui, IList available)
		{
			FieldInfo[] fields = typeof(InventoryGui).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				IList list;
				try
				{
					list = fieldInfo.GetValue(gui) as IList;
				}
				catch (Exception)
				{
					continue;
				}
				if (list != null && list != available && list.Count == available.Count && list.Count != 0 && !((Object)(object)AsGameObject(list[0]) == (Object)null))
				{
					return list;
				}
			}
			return null;
		}

		private static bool TryExtract(object entry, out Recipe recipe, out ItemData upgrade, out GameObject element)
		{
			recipe = null;
			upgrade = null;
			element = null;
			if (entry == null)
			{
				return false;
			}
			recipe = (Recipe)((entry is Recipe) ? entry : null);
			if ((Object)(object)recipe != (Object)null)
			{
				return true;
			}
			Type type = entry.GetType();
			FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				object value;
				try
				{
					value = fieldInfo.GetValue(entry);
				}
				catch (Exception)
				{
					continue;
				}
				Assign(value, ref recipe, ref upgrade, ref element);
			}
			PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (PropertyInfo propertyInfo in properties)
			{
				if (propertyInfo.CanRead && propertyInfo.GetIndexParameters().Length == 0)
				{
					object value2;
					try
					{
						value2 = propertyInfo.GetValue(entry, null);
					}
					catch (Exception)
					{
						continue;
					}
					Assign(value2, ref recipe, ref upgrade, ref element);
				}
			}
			return (Object)(object)recipe != (Object)null;
		}

		private static void Assign(object v, ref Recipe r, ref ItemData u, ref GameObject g)
		{
			if (v == null)
			{
				return;
			}
			if ((Object)(object)r == (Object)null)
			{
				r = (Recipe)((v is Recipe) ? v : null);
			}
			if (u == null)
			{
				u = (ItemData)((v is ItemData) ? v : null);
			}
			if (!((Object)(object)g == (Object)null))
			{
				return;
			}
			GameObject val = (GameObject)((v is GameObject) ? v : null);
			if ((Object)(object)val != (Object)null)
			{
				g = val;
				return;
			}
			Component val2 = (Component)((v is Component) ? v : null);
			if ((Object)(object)val2 != (Object)null)
			{
				g = val2.gameObject;
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class TerminalPatch
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Run;
		}

		private static void Postfix()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			object obj = <>O.<0>__Run;
			if (obj == null)
			{
				ConsoleEvent val = Commands.Run;
				<>O.<0>__Run = val;
				obj = (object)val;
			}
			new ConsoleCommand("artisan", "Artisan Tracker: status | pending [type] | done [type] | debug", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}
	}
	internal static class Commands
	{
		private class Row
		{
			public string Stat;

			public string Name;

			public string Type;

			public string Station;
		}

		private const int MaxLines = 250;

		public static void Run(ConsoleEventArgs args)
		{
			Terminal context = args.Context;
			string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "status");
			string filter = ((args.Length > 2) ? args[2] : null);
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				context.AddString(Lang.T("cmd.enter_world"));
				return;
			}
			switch (text)
			{
			case "status":
				Status(context);
				break;
			case "pending":
				List(context, filter, wantDone: false);
				break;
			case "done":
				List(context, filter, wantDone: true);
				break;
			case "debug":
				DebugInfo(context);
				break;
			default:
				context.AddString(Lang.T("cmd.usage"));
				break;
			}
		}

		private static void Status(Terminal ctx)
		{
			List<GameStats.CraftAch> list = GameStats.CraftAchievements();
			if (list.Count == 0)
			{
				ctx.AddString(Lang.T("cmd.no_ach"));
				return;
			}
			GameStats.CraftAch craftAch = GameStats.Tracked(list);
			Dictionary<string, float> crafted = GameStats.CraftedTotals();
			ctx.AddString(Lang.T("cmd.status_head"));
			foreach (GameStats.CraftAch item in list)
			{
				int num = item.Reqs.Count((GameStats.Req r) => GameStats.Met(crafted, r));
				int num2 = (int)Math.Round(100.0 * (double)num / (double)item.Reqs.Count);
				ctx.AddString(((item == craftAch) ? " > " : "   ") + item.Display + " [" + item.Id + "]: " + num + "/" + item.Reqs.Count + " (" + num2 + "%)" + (item.Unlocked ? ("  " + Lang.T("cmd.unlocked")) : ""));
			}
			ctx.AddString(Lang.T("cmd.status_hint"));
		}

		private static void DebugInfo(Terminal ctx)
		{
			List<GameStats.CraftAch> all = GameStats.CraftAchievements();
			Dictionary<string, float> crafted = GameStats.CraftedTotals();
			List<string> list = (from kv in crafted
				where kv.Value > 0f
				select kv.Key).ToList();
			ctx.AddString("[Artisan] Craft achievements found: " + all.Count);
			ctx.AddString("[Artisan] Items with a counter > 0 in the game: " + list.Count);
			foreach (GameStats.CraftAch item in all)
			{
				HashSet<string> stats = new HashSet<string>(item.Reqs.Select((GameStats.Req x) => x.Stat));
				int num = list.Count((string k) => stats.Contains(k));
				int num2 = item.Reqs.Count((GameStats.Req x) => GameStats.Met(crafted, x));
				ctx.AddString("   id=" + item.Id + " items=" + item.Reqs.Count + " matching=" + num + " met=" + num2);
			}
			GameStats.CraftAch craftAch = GameStats.Tracked(all);
			ctx.AddString("[Artisan] Following: " + ((craftAch != null) ? craftAch.Id : "none") + "   version 1.0.0");
			List<string> list2 = list.Where((string k) => !all.Any((GameStats.CraftAch a) => a.Reqs.Any((GameStats.Req x) => x.Stat == k))).ToList();
			if (list2.Count > 0)
			{
				ctx.AddString("   Crafted but not in any list: " + string.Join(", ", list2.Take(10).ToArray()).Replace("$", ""));
			}
		}

		internal static Dictionary<string, Recipe> RecipeMap()
		{
			Dictionary<string, Recipe> dictionary = new Dictionary<string, Recipe>();
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return dictionary;
			}
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_item == (Object)null))
				{
					string name = recipe.m_item.m_itemData.m_shared.m_name;
					if (!dictionary.ContainsKey(name))
					{
						dictionary[name] = recipe;
					}
				}
			}
			return dictionary;
		}

		private static void List(Terminal ctx, string filter, bool wantDone)
		{
			GameStats.CraftAch craftAch = GameStats.Tracked(GameStats.CraftAchievements());
			if (craftAch == null)
			{
				ctx.AddString(Lang.T("cmd.no_ach"));
				return;
			}
			Dictionary<string, float> crafted = GameStats.CraftedTotals();
			Dictionary<string, Recipe> dictionary = RecipeMap();
			List<Row> list = new List<Row>();
			foreach (GameStats.Req req in craftAch.Reqs)
			{
				if (GameStats.Met(crafted, req) != wantDone)
				{
					continue;
				}
				Row row = new Row
				{
					Stat = req.Stat,
					Name = GameStats.Loc(req.Stat),
					Type = "?",
					Station = "-"
				};
				if (dictionary.TryGetValue(req.Stat, out var value))
				{
					row.Type = ((object)Unsafe.As<ItemType, ItemType>(ref value.m_item.m_itemData.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString();
					if ((Object)(object)value.m_craftingStation != (Object)null)
					{
						row.Station = GameStats.Loc(value.m_craftingStation.m_name) + " " + Lang.T("panel.lvl") + value.m_minStationLevel;
					}
				}
				if (string.IsNullOrEmpty(filter) || row.Type.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(row);
				}
			}
			list = (from r in list
				orderby r.Type, r.Name
				select r).ToList();
			ctx.AddString("[Artisan] " + craftAch.Display + " - " + Lang.T(wantDone ? "cmd.crafted" : "cmd.pending") + ": " + list.Count + (string.IsNullOrEmpty(filter) ? "" : Lang.F("cmd.type_contains", filter)));
			int num = 0;
			foreach (Row item in list)
			{
				if (num++ >= 250)
				{
					ctx.AddString(Lang.T("cmd.more"));
					break;
				}
				ctx.AddString("   [" + item.Type + "] " + item.Name + " - " + item.Station);
			}
		}
	}
}