Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Crafting Achievement Tracker v1.0.0
ArtisanTracker.dll
Decompiled 11 hours agousing 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); } } } }