using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("CraftMeOnce")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CraftMeOnce")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("7055ccb8-ebe8-49ab-ad51-d72bad2a5c42")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace CraftMeOnce;
public static class Caching
{
[HarmonyPatch(typeof(Player), "OnSpawned")]
public static class Player_OnSpawned
{
private static void Postfix(bool spawnValkyrie)
{
Logger.Log("Player_OnSpawned");
AddItemDrops();
}
}
public static readonly Dictionary<string, string> itemDropTranslatedKeys = new Dictionary<string, string>();
public static void AddItemDrops()
{
itemDropTranslatedKeys.Clear();
Recipe[] array = Resources.FindObjectsOfTypeAll<Recipe>();
foreach (Recipe val in array)
{
if ((Object)(object)val.m_item != (Object)null && val.m_item.m_itemData != null && val.m_item.m_itemData.m_shared != null)
{
string key = Localization.instance.Localize(val.m_item.m_itemData.m_shared.m_name);
if (!itemDropTranslatedKeys.ContainsKey(key))
{
itemDropTranslatedKeys.Add(key, val.m_item.m_itemData.m_shared.m_name);
}
}
}
}
}
[HarmonyPatch(typeof(Localization), "SetLanguage")]
public class Localization_SetLanguage_Patch
{
private static void Postfix(string language)
{
Logger.Log("Language changed to: " + language);
Caching.AddItemDrops();
}
}
internal class ConfigurationFile
{
public enum Toggle
{
Off,
On
}
public static ConfigEntry<Toggle> modEnabled;
public static ConfigEntry<Toggle> debug;
public static ConfigEntry<Toggle> showExclamation;
public static ConfigEntry<Vector2> btnPosition;
public static ConfigEntry<Vector2> btnSize;
public static ConfigEntry<string> characterForNotcraftedItems;
public static ConfigFile configFile;
private static readonly string ConfigFileName = "Turbero.CraftMeOnce.cfg";
private static readonly string ConfigFileFullPath;
internal static void LoadConfig(BaseUnityPlugin plugin)
{
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
configFile = plugin.Config;
modEnabled = configFile.Bind<Toggle>("1 - General", "Mod Enabled", Toggle.On, "Enabling/Disabling this mod (default = On)");
debug = configFile.Bind<Toggle>("1 - General", "Debug Mode", Toggle.Off, "Enabling/Disabling the debugging in the console (default = Off)");
showExclamation = configFile.Bind<Toggle>("1 - General", "Show Exclamation", Toggle.Off, "Turn on/off the exclamation mark in the names (default = On)");
btnPosition = configFile.Bind<Vector2>("2 - Config", "Button exclamation Position", new Vector2(-268f, 566f), "Left corner position for the map players list (default: x=-268, y=566)");
btnSize = configFile.Bind<Vector2>("2 - Config", "Button exclamation Size", new Vector2(29f, 29f), "Width/Height of the button exclamation in the workstations (default: x=29, y=29)");
characterForNotcraftedItems = configFile.Bind<string>("2 - Config", "Character for Not Crafted Items", "!", "Character to show the item has never been crafted (default = '!')");
SetupWatcher();
}
private static void SetupWatcher()
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName);
fileSystemWatcher.Changed += ReadConfigValues;
fileSystemWatcher.Created += ReadConfigValues;
fileSystemWatcher.Renamed += ReadConfigValues;
fileSystemWatcher.IncludeSubdirectories = true;
fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
fileSystemWatcher.EnableRaisingEvents = true;
}
private static void ReadConfigValues(object sender, FileSystemEventArgs e)
{
if (!File.Exists(ConfigFileFullPath))
{
return;
}
try
{
Logger.Log("Attempting to reload configuration...");
configFile.Reload();
SettingsChanged(null, null);
}
catch
{
Logger.LogError("There was an issue loading " + ConfigFileName);
}
}
private static void SettingsChanged(object sender, EventArgs e)
{
if ((Object)(object)BtnExclamationPatch.btnExclamation != (Object)null)
{
((Component)BtnExclamationPatch.btnExclamation).gameObject.SetActive(modEnabled.Value == Toggle.On);
}
if (InventoryGui.IsVisible())
{
MethodInfo method = ((object)InventoryGui.instance).GetType().GetMethod("SetupCrafting", BindingFlags.Instance | BindingFlags.NonPublic);
if (method != null)
{
method.Invoke(InventoryGui.instance, new object[0]);
}
}
}
static ConfigurationFile()
{
string configPath = Paths.ConfigPath;
char directorySeparatorChar = Path.DirectorySeparatorChar;
ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName;
}
}
[BepInPlugin("Turbero.CraftMeOnce", "Craft Me Once", "1.0.1")]
public class CraftMeOnce : BaseUnityPlugin
{
public const string GUID = "Turbero.CraftMeOnce";
public const string NAME = "Craft Me Once";
public const string VERSION = "1.0.1";
private readonly Harmony harmony = new Harmony("Turbero.CraftMeOnce");
private void Awake()
{
ConfigurationFile.LoadConfig((BaseUnityPlugin)(object)this);
harmony.PatchAll();
}
private void onDestroy()
{
harmony.UnpatchSelf();
}
}
public class GameManager
{
private static readonly Dictionary<string, TMP_FontAsset> cachedFonts = new Dictionary<string, TMP_FontAsset>();
public static object GetPrivateValue(object obj, string name, BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic)
{
return obj.GetType().GetField(name, bindingAttr)?.GetValue(obj);
}
public static TMP_FontAsset getFontAsset(string name)
{
if (!cachedFonts.ContainsKey(name))
{
Logger.Log("Finding " + name + " font...");
TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
foreach (TMP_FontAsset val in array)
{
if (((Object)val).name == name)
{
Logger.Log(name + " font found.");
cachedFonts.Add(name, val);
return val;
}
}
Logger.Log(name + " font NOT found.");
return null;
}
return GeneralExtensions.GetValueSafe<string, TMP_FontAsset>(cachedFonts, name);
}
}
public static class Logger
{
private static readonly ManualLogSource logger = Logger.CreateLogSource("Craft Me Once");
internal static void Log(object s)
{
if (ConfigurationFile.debug.Value != 0)
{
logger.LogInfo((object)s?.ToString());
}
}
internal static void LogInfo(object s)
{
logger.LogInfo((object)s?.ToString());
}
internal static void LogWarning(object s)
{
string text = "Craft Me Once 1.0.1: " + ((s != null) ? s.ToString() : "null");
Debug.LogWarning((object)text);
}
internal static void LogError(object s)
{
string text = "Craft Me Once 1.0.1: " + ((s != null) ? s.ToString() : "null");
Debug.LogError((object)text);
}
}
[HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList")]
public static class UpdateCraftingPanelPatch
{
private static void Postfix(InventoryGui __instance, List<Recipe> recipes)
{
if (ConfigurationFile.modEnabled.Value == ConfigurationFile.Toggle.Off)
{
if ((Object)(object)BtnExclamationPatch.btnExclamation != (Object)null)
{
((Component)BtnExclamationPatch.btnExclamation).gameObject.SetActive(false);
}
}
else
{
if ((Object)(object)BtnExclamationPatch.btnExclamation == (Object)null || ConfigurationFile.showExclamation.Value == ConfigurationFile.Toggle.Off)
{
return;
}
((Component)BtnExclamationPatch.btnExclamation).gameObject.SetActive(true);
Logger.Log("UpdateCraftingPanelPatch - Postfix");
Player localPlayer = Player.m_localPlayer;
if ((Object)(object)localPlayer == (Object)null)
{
return;
}
Transform val = ((Component)__instance).transform.Find("root/Crafting/RecipeList/Recipes/ListRoot");
int childCount = val.childCount;
for (int i = 0; i < childCount; i++)
{
TextMeshProUGUI component = ((Component)val.GetChild(i).Find("name")).GetComponent<TextMeshProUGUI>();
if ((Object)(object)component == (Object)null)
{
continue;
}
Logger.Log("translatedText " + ((TMP_Text)component).text);
if (findTranslatedKey(component, out var recipeKey))
{
Logger.Log("found itemRecipeKeyValue: " + ((object)component)?.ToString() + " - " + recipeKey);
if (!localPlayer.IsKnownMaterial(recipeKey))
{
((TMP_Text)component).text = "<color=yellow>" + ConfigurationFile.characterForNotcraftedItems.Value + "</color> " + Localization.instance.Localize(((TMP_Text)component).text);
}
else
{
((TMP_Text)component).text = Localization.instance.Localize(((TMP_Text)component).text);
}
}
}
}
}
private static bool findTranslatedKey(TextMeshProUGUI translatedText, out string recipeKey)
{
if (Caching.itemDropTranslatedKeys.TryGetValue(((TMP_Text)translatedText).text, out recipeKey))
{
return true;
}
string text = RemoveAmountSuffix(((TMP_Text)translatedText).text, " x");
Logger.Log("translated quantity check: " + text);
if (Caching.itemDropTranslatedKeys.TryGetValue(text, out recipeKey))
{
return true;
}
string text2 = CleanItemName(((TMP_Text)translatedText).text);
Logger.Log("translated other mods check: " + text2);
return Caching.itemDropTranslatedKeys.TryGetValue(text2, out recipeKey);
}
private static string RemoveAmountSuffix(string text, string indicator)
{
if (string.IsNullOrWhiteSpace(text))
{
return text;
}
int num = text.LastIndexOf(indicator);
if (num > 0 && num + 2 < text.Length)
{
string s = text.Substring(num + 2);
if (int.TryParse(s, out var _))
{
return text.Substring(0, num).Trim();
}
}
return text;
}
private static string CleanItemName(string raw)
{
if (string.IsNullOrEmpty(raw))
{
return raw;
}
string input = raw;
input = Regex.Replace(input, "<size=.*?</size>", "", RegexOptions.IgnoreCase);
input = Regex.Replace(input, "<.*?>", "", RegexOptions.IgnoreCase);
input = Regex.Replace(input, "\\s+#\\d+$", "", RegexOptions.IgnoreCase);
return input.Trim();
}
}
[HarmonyPatch(typeof(InventoryGui), "SetupCrafting")]
public static class BtnExclamationPatch
{
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();
public static UnityAction <>9__3_0;
internal void <Postfix>b__3_0()
{
ConfigurationFile.showExclamation.Value = ((ConfigurationFile.showExclamation.Value == ConfigurationFile.Toggle.Off) ? ConfigurationFile.Toggle.On : ConfigurationFile.Toggle.Off);
}
}
private static GameObject btnExclamationGo;
public static Button btnExclamation;
private static TextMeshProUGUI buttonText;
private static void Postfix(InventoryGui __instance)
{
//IL_0097: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_0107: Unknown result type (might be due to invalid IL or missing references)
//IL_0111: Expected O, but got Unknown
//IL_0170: Unknown result type (might be due to invalid IL or missing references)
//IL_0169: Unknown result type (might be due to invalid IL or missing references)
//IL_0130: Unknown result type (might be due to invalid IL or missing references)
//IL_0135: Unknown result type (might be due to invalid IL or missing references)
//IL_013b: Expected O, but got Unknown
if ((Object)(object)btnExclamationGo == (Object)null || (Object)(object)btnExclamation == (Object)null || (Object)(object)buttonText == (Object)null)
{
Transform val = ((Component)__instance.m_skillsDialog).transform.Find("SkillsFrame/Closebutton");
Transform transform = ((Component)__instance.m_crafting).transform;
btnExclamationGo = Object.Instantiate<GameObject>(((Component)val).gameObject, transform);
((Object)btnExclamationGo).name = "BtnExclamation";
btnExclamationGo.transform.SetParent(transform, false);
RectTransform component = btnExclamationGo.GetComponent<RectTransform>();
component.anchoredPosition = ConfigurationFile.btnPosition.Value;
component.sizeDelta = ConfigurationFile.btnSize.Value;
buttonText = btnExclamationGo.GetComponentInChildren<TextMeshProUGUI>();
((TMP_Text)buttonText).font = GameManager.getFontAsset("Valheim-AveriaSerifLibre");
((TMP_Text)buttonText).fontStyle = (FontStyles)0;
((TMP_Text)buttonText).alignment = (TextAlignmentOptions)514;
btnExclamation = btnExclamationGo.GetComponent<Button>();
btnExclamation.onClick = new ButtonClickedEvent();
ButtonClickedEvent onClick = btnExclamation.onClick;
object obj = <>c.<>9__3_0;
if (obj == null)
{
UnityAction val2 = delegate
{
ConfigurationFile.showExclamation.Value = ((ConfigurationFile.showExclamation.Value == ConfigurationFile.Toggle.Off) ? ConfigurationFile.Toggle.On : ConfigurationFile.Toggle.Off);
};
<>c.<>9__3_0 = val2;
obj = (object)val2;
}
((UnityEvent)onClick).AddListener((UnityAction)obj);
}
((TMP_Text)buttonText).text = ConfigurationFile.characterForNotcraftedItems.Value;
((Graphic)buttonText).color = ((ConfigurationFile.showExclamation.Value == ConfigurationFile.Toggle.On) ? Color.yellow : Color.gray);
}
}