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 Automatics v1.6.0
plugins/Automatics.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using Automatics.AutomaticDoor; using Automatics.AutomaticMapping; using Automatics.AutomaticProcessing; using Automatics.ConsoleCommands; using Automatics.Valheim; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using LitJson; using ModUtils; using NDesk.Options; using Splatform; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Automatics")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Automatics")] [assembly: AssemblyCopyright("Copyright (c) 2022-2026 EideeHi")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("1821CB9A-56A9-4013-B268-86F0704773CF")] [assembly: AssemblyFileVersion("1.6.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = "")] [assembly: AssemblyVersion("1.6.0.0")] namespace ModUtils { public class Configuration { private sealed class LocalizedConfigEntry { public ConfigEntryBase Entry { get; set; } public L10N Localization { get; set; } public string Section { get; set; } public string Key { get; set; } public ConfigurationManagerAttributes Attributes { get; set; } public bool CategoryManaged { get; set; } public bool DispNameManaged { get; set; } public bool DescriptionManaged { get; set; } } private const int DefaultOrder = 4096; private static readonly object LocalizedEntriesLock; private static readonly Dictionary<ConfigEntryBase, LocalizedConfigEntry> LocalizedEntries; private static readonly string[] DescriptionFieldNames; private readonly ConfigFile _config; private readonly L10N _localization; private Logger _logger; private string Section { get; set; } = "general"; private int Order { get; set; } = 4096; static Configuration() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_0090: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Expected O, but got Unknown LocalizedEntriesLock = new object(); DescriptionFieldNames = new string[4] { "<Description>k__BackingField", "Description", "description", "_description" }; LocalizedEntries = new Dictionary<ConfigEntryBase, LocalizedConfigEntry>(); if (!TomlTypeConverter.CanConvert(typeof(StringList))) { TomlTypeConverter.AddConverter(typeof(StringList), new TypeConverter { ConvertToObject = (string str, Type type) => (!string.IsNullOrEmpty(str)) ? new StringList(Csv.ParseLine(str, trimUnquotedFields: true)) : new StringList(), ConvertToString = delegate(object obj, Type type) { StringList source = (StringList)obj; return string.Join(", ", source.Select(Csv.Escape)); } }); } if (!TomlTypeConverter.CanConvert(typeof(KeyboardShortcut))) { TomlTypeConverter.AddConverter(typeof(KeyboardShortcut), new TypeConverter { ConvertToObject = (string str, Type type) => KeyboardShortcut.Deserialize(str), ConvertToString = delegate(object obj, Type type) { //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) KeyboardShortcut val = (KeyboardShortcut)obj; return ((KeyboardShortcut)(ref val)).Serialize(); } }); } } public Configuration(ConfigFile config, L10N localization) { _config = config; _localization = localization; } internal static void RefreshAllLocalizedMetadata() { LocalizedConfigEntry[] array; lock (LocalizedEntriesLock) { array = LocalizedEntries.Values.ToArray(); } LocalizedConfigEntry[] array2 = array; for (int i = 0; i < array2.Length; i++) { RefreshLocalizedMetadata(array2[i]); } } private static void RegisterLocalizedEntry(ConfigEntryBase entry, L10N localization, string section, string key, ConfigurationManagerAttributes attributes, bool categoryManaged, bool dispNameManaged, bool descriptionManaged) { LocalizedConfigEntry localizedConfigEntry = new LocalizedConfigEntry { Entry = entry, Localization = localization, Section = section, Key = key, Attributes = attributes, CategoryManaged = categoryManaged, DispNameManaged = dispNameManaged, DescriptionManaged = descriptionManaged }; lock (LocalizedEntriesLock) { LocalizedEntries[entry] = localizedConfigEntry; } RefreshLocalizedMetadata(localizedConfigEntry); } private static void RefreshLocalizedMetadata(LocalizedConfigEntry entry) { //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown if (entry?.Entry == null || entry.Localization == null) { return; } try { ConfigurationManagerAttributes attributes = entry.Attributes; if (attributes != null) { if (entry.CategoryManaged) { attributes.Category = entry.Localization.Translate("@config_" + entry.Section + "_section"); } if (entry.DispNameManaged) { attributes.DispName = entry.Localization.Translate("@config_" + entry.Section + "_" + entry.Key + "_name"); } string text = (attributes.Description = (entry.DescriptionManaged ? entry.Localization.Translate("@config_" + entry.Section + "_" + entry.Key + "_description") : attributes.Description)); ConfigDescription description = entry.Entry.Description; AcceptableValueBase val = ((description != null) ? description.AcceptableValues : null); object[] array = ReplaceConfigurationManagerAttributes((description != null) ? description.Tags : null, attributes); ConfigDescription description2 = new ConfigDescription(text, val, array); if (!TrySetEntryDescription(entry.Entry, description2)) { Debug.LogError((object)("[ModUtils] Failed to replace ConfigDescription for [" + entry.Section + ":" + entry.Key + "].")); } } } catch (Exception arg) { Debug.LogError((object)$"[ModUtils] Failed to refresh localized config metadata for [{entry.Section}:{entry.Key}]: {arg}"); } } private static object[] ReplaceConfigurationManagerAttributes(object[] tags, ConfigurationManagerAttributes attributes) { if (tags == null || tags.Length == 0) { return new object[1] { attributes }; } bool flag = false; List<object> list = new List<object>(tags.Length); foreach (object obj in tags) { if (obj is ConfigurationManagerAttributes) { if (!flag) { list.Add(attributes); flag = true; } } else { list.Add(obj); } } if (!flag) { list.Add(attributes); } return list.ToArray(); } private static bool TrySetEntryDescription(ConfigEntryBase entry, ConfigDescription description) { MethodInfo methodInfo = AccessTools.PropertySetter(((object)entry).GetType(), "Description") ?? AccessTools.PropertySetter(typeof(ConfigEntryBase), "Description"); if (methodInfo != null) { methodInfo.Invoke(entry, new object[1] { description }); return true; } string[] descriptionFieldNames = DescriptionFieldNames; foreach (string text in descriptionFieldNames) { FieldInfo fieldInfo = AccessTools.Field(((object)entry).GetType(), text) ?? AccessTools.Field(typeof(ConfigEntryBase), text); if (!(fieldInfo == null) && typeof(ConfigDescription).IsAssignableFrom(fieldInfo.FieldType)) { fieldInfo.SetValue(entry, description); return true; } } return false; } private void LogSection(string section) { _logger?.Debug("[CONFIG] === " + GetSection(section) + " / [" + section + "]"); } private void LogConfigEntry<T>(ConfigEntry<T> entry, ConfigurationManagerAttributes attributes) { _logger?.Debug("[CONFIG] ==== " + attributes.DispName + " / [" + ((ConfigEntryBase)entry).Definition.Key + "]"); _logger?.Debug("[CONFIG] " + ((ConfigEntryBase)entry).Description.Description); _logger?.Debug("[CONFIG] "); Type type = typeof(T); object defaultValue = ((ConfigEntryBase)entry).DefaultValue; if (attributes.ObjToStr != null) { _logger?.Debug("[CONFIG] - Default value: " + attributes.ObjToStr(defaultValue)); } else if (TomlTypeConverter.CanConvert(type)) { _logger?.Debug("[CONFIG] - Default value: " + TomlTypeConverter.ConvertToString(defaultValue, type)); } else { _logger?.Debug($"[CONFIG] - Default value: {defaultValue}"); } AcceptableValueBase acceptableValues = ((ConfigEntryBase)entry).Description.AcceptableValues; if (acceptableValues != null) { string[] array = acceptableValues.ToDescriptionString().Split('\n'); foreach (string text in array) { _logger?.Debug("[CONFIG] - " + text); } } else if (type.IsEnum) { List<string> list = (from x in Enum.GetValues(type).OfType<T>() select Enum.GetName(type, x)).ToList(); _logger?.Debug("[CONFIG] - Acceptable values: " + string.Join(", ", list)); if (type.GetCustomAttributes(typeof(FlagsAttribute), inherit: false).Any()) { IEnumerable<string> values = list.Where((string x) => !string.Equals(x, "none", StringComparison.OrdinalIgnoreCase) && !string.Equals(x, "all", StringComparison.OrdinalIgnoreCase)).Take(2); _logger?.Debug("[CONFIG] - Multiple values can be set at the same time by separating them with , (e.g. " + string.Join(", ", values) + ")"); } } _logger?.Debug("[CONFIG] "); } public void SetDebugLogger(Logger logger) { _logger = logger; } public void ChangeSection(string section, int initialOrder = 4096) { Section = section; Order = initialOrder; if (_logger != null) { LogSection(Section); } } private ConfigEntry<T> Bind<T>(string section, int order, string key, T defaultValue, AcceptableValueBase acceptableValue = null, Action<ConfigurationManagerAttributes> initializer = null) { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown L10N.EnsurePatched(); string section2 = GetSection(section); string name = GetName(section, key); ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes { Category = section2, Order = order, DispName = name, CustomDrawer = ConfigurationCustomDrawer.Get(typeof(T), acceptableValue) }; initializer?.Invoke(configurationManagerAttributes); bool categoryManaged = string.Equals(configurationManagerAttributes.Category, section2, StringComparison.Ordinal); bool dispNameManaged = string.Equals(configurationManagerAttributes.DispName, name, StringComparison.Ordinal); bool flag = string.IsNullOrEmpty(configurationManagerAttributes.Description); string text = (configurationManagerAttributes.Description = (flag ? GetDescription(section, key) : configurationManagerAttributes.Description)); ConfigEntry<T> val = _config.Bind<T>(section, key, defaultValue, new ConfigDescription(text, acceptableValue, new object[1] { configurationManagerAttributes })); RegisterLocalizedEntry((ConfigEntryBase)(object)val, _localization, section, key, configurationManagerAttributes, categoryManaged, dispNameManaged, flag); if (_logger != null) { LogConfigEntry<T>(val, configurationManagerAttributes); } return val; } public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, AcceptableValueBase acceptableValue = null, Action<ConfigurationManagerAttributes> initializer = null) { return Bind(section, Order--, key, defaultValue, acceptableValue, initializer); } public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, (T, T) acceptableValue, Action<ConfigurationManagerAttributes> initializer = null) where T : IComparable { var (val, val2) = acceptableValue; return Bind(section, key, defaultValue, (AcceptableValueBase)(object)new AcceptableValueRange<T>(val, val2), initializer); } public ConfigEntry<T> Bind<T>(string key, T defaultValue, AcceptableValueBase acceptableValue = null, Action<ConfigurationManagerAttributes> initializer = null) { return Bind(Section, key, defaultValue, acceptableValue, initializer); } public ConfigEntry<T> Bind<T>(string key, T defaultValue, (T, T) acceptableValue, Action<ConfigurationManagerAttributes> initializer = null) where T : IComparable { return Bind(Section, key, defaultValue, acceptableValue, initializer); } private string GetSection(string section) { return _localization.Translate("@config_" + section + "_section"); } private string GetName(string section, string key) { return _localization.Translate("@config_" + section + "_" + key + "_name"); } private string GetDescription(string section, string key) { return _localization.Translate("@config_" + section + "_" + key + "_description"); } } public class StringList : ICollection<string>, IEnumerable<string>, IEnumerable { private readonly HashSet<string> _values; public int Count => _values.Count; public bool IsReadOnly => false; public StringList() { _values = new HashSet<string>(); } public StringList(IEnumerable<string> collection) { _values = new HashSet<string>(collection); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator<string> GetEnumerator() { return _values.GetEnumerator(); } public void Add(string item) { _values.Add(item); } public void Clear() { _values.Clear(); } public bool Contains(string item) { return _values.Contains(item); } public void CopyTo(string[] array, int arrayIndex) { _values.CopyTo(array, arrayIndex); } public bool Remove(string item) { return _values.Remove(item); } public bool TryAdd(string item) { return _values.Add(item); } } public class LocalizedDescriptionAttribute : DescriptionAttribute { private readonly string _prefix; private readonly string _key; public override string Description => L10N.Translate(_prefix, _key); public LocalizedDescriptionAttribute(string prefix, string key) : base(key) { _prefix = prefix; _key = key; } public LocalizedDescriptionAttribute(string key) : this("", key) { } } public class AcceptableValueEnum<T> : AcceptableValueBase where T : Enum { private readonly bool _isFlags; private readonly IList<T> _values; public AcceptableValueEnum(params T[] values) : base(typeof(T)) { _isFlags = ((AcceptableValueBase)this).ValueType.GetCustomAttributes(typeof(FlagsAttribute), inherit: false).Any(); _values = MakeValues(((AcceptableValueBase)this).ValueType, (IReadOnlyCollection<T>)(object)values, _isFlags); } private static IList<T> MakeValues(Type type, IReadOnlyCollection<T> values, bool isFlags) { object collection; if (values.Count != 0) { collection = values; } else { collection = Enum.GetValues(type).OfType<T>(); } List<T> list = new List<T>((IEnumerable<T>)collection); if (!isFlags) { return list; } HashSet<long> hashSet = new HashSet<long>(); foreach (long item in list.Select((T @enum) => Convert.ToInt64(@enum))) { long[] array = hashSet.ToArray(); foreach (long num in array) { hashSet.Add(num | item); } hashSet.Add(item); } return hashSet.Select((long x) => Enum.ToObject(type, x)).Cast<T>().ToList(); } public override object Clamp(object value) { if (!((AcceptableValueBase)this).IsValid(value)) { return _values[0]; } return value; } public override bool IsValid(object value) { if (value is T item) { return _values.Contains(item); } if (!(value is IConvertible)) { return false; } long @long = Convert.ToInt64(value); return _values.Any((T x) => Convert.ToInt64(x) == @long); } public override string ToDescriptionString() { StringBuilder stringBuilder = new StringBuilder(); Type type = typeof(T); List<string> list = (from x in _values where Enum.IsDefined(type, x) select Enum.GetName(type, x)).ToList(); stringBuilder.Append("# Acceptable values: ").Append(string.Join(", ", list)); if (!_isFlags) { return stringBuilder.ToString(); } List<string> list2 = list.Where((string x) => !string.Equals(x, "none", StringComparison.OrdinalIgnoreCase) && !string.Equals(x, "all", StringComparison.OrdinalIgnoreCase)).Take(2).ToList(); if (list2.Count == 2) { stringBuilder.Append('\n').Append("# Multiple values can be set at the same time by separating them with , (e.g. ").Append(string.Join(", ", list2)) .Append(")"); } return stringBuilder.ToString(); } } public static class ConfigurationCustomDrawer { public delegate bool IsMatchConfig(Type type, AcceptableValueBase acceptableValue); public delegate Action<ConfigEntryBase> CustomDrawerSupplier(); private const string L10NPrefix = "mod_utils"; private const string EnabledKey = "@config_button_enabled"; private const string DisabledKey = "@config_button_disabled"; private const string AddKey = "@config_button_add"; private const string RemoveKey = "@config_button_remove"; private static readonly Dictionary<IsMatchConfig, CustomDrawerSupplier> CustomDrawers; private static bool _defaultTranslationsInitialized; private static readonly Dictionary<string, Dictionary<string, string>> BuiltinDrawerTranslations; private static readonly Dictionary<string, Dictionary<string, string>> CustomDrawerTranslations; private static readonly Dictionary<string, string> OwnedDefaults; static ConfigurationCustomDrawer() { BuiltinDrawerTranslations = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase) { { "English", new Dictionary<string, string> { { "@config_button_enabled", "Enabled" }, { "@config_button_disabled", "Disabled" }, { "@config_button_add", "Add" }, { "@config_button_remove", "Remove" } } }, { "Japanese", new Dictionary<string, string> { { "@config_button_enabled", "有効" }, { "@config_button_disabled", "無効" }, { "@config_button_add", "追加" }, { "@config_button_remove", "削除" } } } }; CustomDrawerTranslations = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); OwnedDefaults = new Dictionary<string, string>(); CustomDrawers = new Dictionary<IsMatchConfig, CustomDrawerSupplier> { { IsBool, () => Bool }, { IsFloatWithRange, () => FloatSlider }, { IsStringList, StringList }, { IsFlagsEnum, () => Flags } }; } public static void RegisterDefaultDrawerTranslation(string language, string key, string value) { if (!CustomDrawerTranslations.TryGetValue(language, out var value2)) { value2 = new Dictionary<string, string>(); CustomDrawerTranslations[language] = value2; } value2[key] = value; if (_defaultTranslationsInitialized) { RefreshDefaultTranslations(); } } private static void EnsureDefaultTranslations() { if (!_defaultTranslationsInitialized) { _defaultTranslationsInitialized = TryAddDefaultTranslations(); } } internal static void RefreshDefaultTranslations() { _defaultTranslationsInitialized = false; _defaultTranslationsInitialized = TryAddDefaultTranslations(); if (!_defaultTranslationsInitialized) { Debug.LogWarning((object)"[ModUtils] Failed to refresh custom drawer fallback translations for the current language."); } } private static Dictionary<string, string> ResolveDrawerTranslations(string language) { Dictionary<string, string> dictionary = new Dictionary<string, string>(BuiltinDrawerTranslations["English"]); if (CustomDrawerTranslations.TryGetValue("English", out var value)) { foreach (KeyValuePair<string, string> item in value) { dictionary[item.Key] = item.Value; } } if (!string.Equals(language, "English", StringComparison.OrdinalIgnoreCase)) { if (BuiltinDrawerTranslations.TryGetValue(language, out var value2)) { foreach (KeyValuePair<string, string> item2 in value2) { dictionary[item2.Key] = item2.Value; } } if (CustomDrawerTranslations.TryGetValue(language, out var value3)) { foreach (KeyValuePair<string, string> item3 in value3) { dictionary[item3.Key] = item3.Value; } } } return dictionary; } private static bool TryAddDefaultTranslations() { Localization instance; try { instance = Localization.instance; } catch (Exception) { return false; } if (instance == null) { return false; } Dictionary<string, string> field = Reflections.GetField<Dictionary<string, string>>(instance, "m_translations"); if (field == null) { return false; } string text; try { text = instance.GetSelectedLanguage(); } catch (Exception) { return false; } if (string.IsNullOrEmpty(text)) { text = "English"; } foreach (KeyValuePair<string, string> item in ResolveDrawerTranslations(text)) { string translationKey = L10N.GetTranslationKey("mod_utils", item.Key); if (field.TryGetValue(translationKey, out var value)) { if (OwnedDefaults.TryGetValue(translationKey, out var value2) && value == value2) { field[translationKey] = item.Value; } } else { field[translationKey] = item.Value; } OwnedDefaults[translationKey] = item.Value; } return true; } private static bool IsBool(Type type, AcceptableValueBase acceptableValue) { return type == typeof(bool); } private static bool IsFloatWithRange(Type type, AcceptableValueBase acceptableValue) { if (type == typeof(float)) { return acceptableValue is AcceptableValueRange<float>; } return false; } private static bool IsStringList(Type type, AcceptableValueBase acceptableValue) { return type == typeof(StringList); } private static bool HasFlagsAttribute(Type type) { if (type.IsEnum) { return type.GetCustomAttributes(typeof(FlagsAttribute), inherit: false).Any(); } return false; } private static bool IsFlagsEnum(Type type, AcceptableValueBase acceptableValue) { return HasFlagsAttribute(type); } public static void Bool(ConfigEntryBase entry) { EnsureDefaultTranslations(); bool flag = (bool)entry.BoxedValue; string text = L10N.Translate("mod_utils", flag ? "@config_button_enabled" : "@config_button_disabled"); bool flag2 = GUILayout.Toggle(flag, text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (flag2 != flag) { entry.BoxedValue = flag2; } } public static void FloatSlider(ConfigEntryBase entry) { AcceptableValueRange<float> val = (AcceptableValueRange<float>)(object)entry.Description.AcceptableValues; float num = (float)entry.BoxedValue; float minValue = val.MinValue; float maxValue = val.MaxValue; float num2 = GUILayout.HorizontalSlider(num, minValue, maxValue, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); num2 = Mathf.Floor(num2 * 100f) / 100f; if (Math.Abs(num2 - num) > Mathf.Abs(maxValue - minValue) / 1000f) { entry.BoxedValue = num2; } string text = num.ToString("0.00", CultureInfo.InvariantCulture); string text2 = GUILayout.TextField(text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) }); if (text2 == text) { return; } try { num2 = (float)Convert.ToDouble(text2, CultureInfo.InvariantCulture); object boxedValue = Convert.ChangeType(((AcceptableValueBase)val).Clamp((object)num2), entry.SettingType, CultureInfo.InvariantCulture); entry.BoxedValue = boxedValue; } catch (FormatException) { } } private static Action<ConfigEntryBase> StringList() { string inputText = ""; return delegate(ConfigEntryBase entry) { //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected O, but got Unknown //IL_013f: Unknown result type (might be due to invalid IL or missing references) EnsureDefaultTranslations(); int num = Mathf.Min(Screen.width, 650); int num2 = num - Mathf.RoundToInt((float)num / 2.5f) - 115; string text = L10N.Translate("mod_utils", "@config_button_add"); string text2 = L10N.Translate("mod_utils", "@config_button_remove"); StringList stringList = new StringList((StringList)entry.BoxedValue); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MaxWidth((float)num2) }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); inputText = GUILayout.TextField(inputText, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (GUILayout.Button(text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }) && !string.IsNullOrEmpty(inputText)) { if (stringList.TryAdd(inputText)) { entry.BoxedValue = stringList; } inputText = ""; } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); double num3 = 0.0; foreach (string item in stringList.ToList()) { int num4 = Mathf.FloorToInt(GUI.skin.label.CalcSize(new GUIContent(item)).x) + Mathf.FloorToInt(GUI.skin.button.CalcSize(new GUIContent(text2)).x); num3 += (double)num4; if (num3 > (double)num2) { GUILayout.EndHorizontal(); num3 = num4; GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); } GUILayout.Label(item, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); if (GUILayout.Button(text2, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }) && stringList.Remove(item)) { entry.BoxedValue = stringList; } } GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUILayout.FlexibleSpace(); }; } public static void Flags(ConfigEntryBase entry) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00c2: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Min(Screen.width, 650); int num2 = num - Mathf.RoundToInt((float)num / 2.5f) - 115; Type settingType = entry.SettingType; long num3 = Convert.ToInt64(entry.BoxedValue); AcceptableValueBase acceptableValues = entry.Description.AcceptableValues; GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MaxWidth((float)num2) }); int num4 = 0; GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); foreach (object value2 in Enum.GetValues(settingType)) { if (acceptableValues != null && !acceptableValues.IsValid(value2)) { continue; } long num5 = Convert.ToInt64(value2); if (num5 != 0L) { string enumLabel = GetEnumLabel(settingType, value2); int num6 = Mathf.FloorToInt(GUI.skin.toggle.CalcSize(new GUIContent(enumLabel + "_")).x); num4 += num6; if (num4 > num2) { GUILayout.EndHorizontal(); num4 = num6; GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); } GUI.changed = false; bool flag = GUILayout.Toggle((num3 & num5) == num5, enumLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); if (GUI.changed) { long value = (flag ? (num3 | num5) : (num3 & ~num5)); entry.BoxedValue = Enum.ToObject(settingType, value); } } } GUILayout.EndHorizontal(); GUI.changed = false; GUILayout.EndVertical(); GUILayout.FlexibleSpace(); } public static Action<ConfigEntryBase> MultiSelect<T>(Func<IEnumerable<T>> allElementSupplier, Func<T, string> labelGenerator) where T : IComparable<T> { return delegate(ConfigEntryBase entry) { //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown //IL_0140: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Min(Screen.width, 650); int num2 = num - Mathf.RoundToInt((float)num / 2.5f) - 115; GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MaxWidth((float)num2) }); Type settingType = entry.SettingType; ConstructorInfo constructor = settingType.GetConstructor(new Type[1] { typeof(IEnumerable<T>) }); if ((object)constructor == null) { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label($"{settingType} does not define a constructor that takes IEnumerable<{typeof(T)}> as an argument.", Array.Empty<GUILayoutOption>()); GUILayout.EndHorizontal(); GUI.changed = false; GUILayout.EndVertical(); GUILayout.FlexibleSpace(); } else { ICollection<T> collection = (ICollection<T>)constructor.Invoke(new object[1] { entry.BoxedValue }); AcceptableValueBase acceptableValues = entry.Description.AcceptableValues; int num3 = 0; GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); foreach (T item in allElementSupplier()) { if (acceptableValues == null || acceptableValues.IsValid((object)item)) { string text = labelGenerator(item) ?? item.ToString(); int num4 = Mathf.FloorToInt(GUI.skin.toggle.CalcSize(new GUIContent(text + "_")).x); num3 += num4; if (num3 > num2) { GUILayout.EndHorizontal(); num3 = num4; GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); } GUI.changed = false; bool flag = GUILayout.Toggle(collection.Contains(item), text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); if (GUI.changed) { if (flag) { collection.Add(item); } else { collection.Remove(item); } entry.BoxedValue = collection; } } } GUILayout.EndHorizontal(); GUI.changed = false; GUILayout.EndVertical(); GUILayout.FlexibleSpace(); } }; } public static void Register(IsMatchConfig matcher, CustomDrawerSupplier supplier) { CustomDrawers[matcher] = supplier; } public static Action<ConfigEntryBase> Get(Type type, AcceptableValueBase acceptableValue) { return (from x in CustomDrawers where x.Key(type, acceptableValue) select x.Value()).FirstOrDefault(); } private static string GetEnumLabel(Type type, object @object) { return (type.GetMember(Enum.GetName(type, @object) ?? "").FirstOrDefault()?.GetCustomAttributes(typeof(DescriptionAttribute), inherit: false).OfType<DescriptionAttribute>().FirstOrDefault())?.Description ?? @object.ToString(); } } public sealed class ConfigurationManagerAttributes { public bool? ShowRangeAsPercent; public Action<ConfigEntryBase> CustomDrawer; public bool? Browsable; public string Category; public object DefaultValue; public bool? HideDefaultButton; public bool? HideSettingName; public string Description; public string DispName; public int? Order; public bool? ReadOnly; public bool? IsAdvanced; public Func<object, string> ObjToStr; public Func<string, object> StrToObj; } public static class Csv { public sealed class Parser { private readonly StringBuilder _fieldBuffer; private readonly List<string> _recordBuffer; private readonly string _source; private readonly bool _trimUnquotedFields; private bool _fieldQuoted; private bool _fieldStarted; private bool _inQuotes; private bool _lastTokenWasDelimiter; private bool _quotedFieldClosed; private int _offset; public Parser(string source, int offset = 0, bool trimUnquotedFields = false) { _source = source ?? ""; _offset = offset; _trimUnquotedFields = trimUnquotedFields; _recordBuffer = new List<string>(); _fieldBuffer = new StringBuilder(); } public List<List<string>> Parse() { List<List<string>> list = new List<List<string>>(); int num = -1; while (HasNext()) { List<string> list2 = ParseLine(); int count = list2.Count; if (count != 0) { if (num == -1) { num = count; } else if (num != count) { throw new Exception("Number of fields in a record is not uniform."); } list.Add(list2); } } return list; } public bool HasNext() { return _offset < _source.Length; } public List<string> ParseLine() { _recordBuffer.Clear(); _fieldBuffer.Clear(); _fieldQuoted = false; _fieldStarted = false; _inQuotes = false; _lastTokenWasDelimiter = false; _quotedFieldClosed = false; List<string> list = new List<string>(); bool recordHasContent = false; while (_offset < _source.Length) { char c = _source[_offset++]; if (!ParseChar(c, ref recordHasContent)) { break; } } if (recordHasContent || _fieldStarted || _lastTokenWasDelimiter) { FlushField(); } if (_recordBuffer.Count > 0) { list.AddRange(_recordBuffer); } return list; } private bool ParseChar(char c, ref bool recordHasContent) { switch (c) { case '"': recordHasContent = true; if (_inQuotes) { if (_offset < _source.Length && _source[_offset] == '"') { _fieldBuffer.Append('"'); _offset++; } else { _inQuotes = false; _quotedFieldClosed = true; } } else if (CanStartQuotedField()) { _fieldQuoted = true; _fieldStarted = true; _inQuotes = true; _quotedFieldClosed = false; _fieldBuffer.Clear(); } else { _fieldBuffer.Append(c); _fieldStarted = true; _quotedFieldClosed = false; } _lastTokenWasDelimiter = false; return true; case ',': if (!_inQuotes) { recordHasContent = true; FlushField(); _lastTokenWasDelimiter = true; return true; } break; } if ((c == '\r' || c == '\n') && !_inQuotes) { if (c == '\r' && _offset < _source.Length && _source[_offset] == '\n') { _offset++; } return false; } if (_trimUnquotedFields && _fieldQuoted && _quotedFieldClosed && char.IsWhiteSpace(c)) { _lastTokenWasDelimiter = false; return true; } recordHasContent = true; _fieldBuffer.Append(c); _fieldStarted = true; _quotedFieldClosed = false; _lastTokenWasDelimiter = false; return true; } private bool CanStartQuotedField() { if (_fieldStarted) { if (_trimUnquotedFields && !_fieldQuoted) { return FieldBufferIsWhitespace(); } return false; } return true; } private bool FieldBufferIsWhitespace() { for (int i = 0; i < _fieldBuffer.Length; i++) { if (!char.IsWhiteSpace(_fieldBuffer[i])) { return false; } } return true; } private void FlushField() { string text = _fieldBuffer.ToString(); _recordBuffer.Add((_trimUnquotedFields && !_fieldQuoted) ? text.Trim() : text); _fieldBuffer.Clear(); _fieldQuoted = false; _fieldStarted = false; _quotedFieldClosed = false; } } private static readonly char[] MustQuoteChars = new char[4] { '"', ',', '\r', '\n' }; public static string Escape(string field) { if (field == null) { return ""; } if (field.Length == 0 || field.IndexOfAny(MustQuoteChars) != -1 || (field.Length > 0 && (char.IsWhiteSpace(field[0]) || char.IsWhiteSpace(field[field.Length - 1])))) { return "\"" + field.Replace("\"", "\"\"") + "\""; } return field; } public static List<List<string>> Parse(string csv) { return Parse(csv, trimUnquotedFields: false); } public static List<List<string>> Parse(string csv, bool trimUnquotedFields) { return new Parser(csv, 0, trimUnquotedFields).Parse(); } public static List<string> ParseLine(string line) { return ParseLine(line, trimUnquotedFields: false); } public static List<string> ParseLine(string line, bool trimUnquotedFields) { return new Parser(line, 0, trimUnquotedFields).ParseLine(); } } public class InstanceCache<T> : MonoBehaviour { private static readonly HashSet<T> Cache; private T _instance; public static Action<T> OnCacheAdded { get; set; } public static Action<T> OnCacheRemoved { get; set; } static InstanceCache() { Cache = new HashSet<T>(); } private void Awake() { _instance = GetInstance(); if (_instance != null) { object obj = _instance; Object val = (Object)((obj is Object) ? obj : null); if (val == null || Object.op_Implicit(val)) { Cache.Add(_instance); OnCacheAdded?.Invoke(_instance); return; } } throw new NullReferenceException("Failed to acquire instance."); } private void OnDestroy() { Cache.Remove(_instance); OnCacheRemoved?.Invoke(_instance); _instance = default(T); } protected virtual T GetInstance() { return ((Component)this).GetComponent<T>(); } public static IEnumerable<T> GetAllInstance() { return Cache.ToList(); } public static void Fill(List<T> buffer) { if (buffer == null) { throw new ArgumentNullException("buffer"); } buffer.Clear(); buffer.AddRange(Cache); } } public enum WorldLevelMatchMode { Exact, CurrentOrHigher, Ignore } public static class Inventories { public static float CurrentWorldLevel { get { if (!((Object)(object)Game.instance != (Object)null)) { return 0f; } return Game.m_worldLevel; } } private static bool IsWorldLevelMatch(ItemData data, float worldLevel, WorldLevelMatchMode matchMode) { return matchMode switch { WorldLevelMatchMode.Exact => (float)data.m_worldLevel == worldLevel, WorldLevelMatchMode.CurrentOrHigher => (float)data.m_worldLevel >= CurrentWorldLevel, WorldLevelMatchMode.Ignore => true, _ => (float)data.m_worldLevel == worldLevel, }; } private static bool IsMatchedItem(ItemData data, string name, float worldLevel, int quality, bool isPrefabName, WorldLevelMatchMode matchMode) { if (data == null) { return false; } bool num; if (!isPrefabName) { num = data.m_shared.m_name == name; } else { if (!((Object)(object)data.m_dropPrefab != (Object)null)) { goto IL_0057; } num = ((Object)data.m_dropPrefab).name == name; } if (num && (quality < 0 || data.m_quality == quality)) { return IsWorldLevelMatch(data, worldLevel, matchMode); } goto IL_0057; IL_0057: return false; } private static void NotifyChanged(Inventory inventory) { Reflections.InvokeMethod(inventory, "Changed"); } private static int CountItems(Inventory inventory, string name, float worldLevel, int quality, bool isPrefabName = false, WorldLevelMatchMode matchMode = WorldLevelMatchMode.Exact) { return GetItems(inventory, name, worldLevel, matchMode, quality, isPrefabName).Sum((ItemData x) => x.m_stack); } private static int FindFreeStackSpace(Inventory inventory, string name, float worldLevel, int quality, bool isPrefabName) { return (from item in GetItems(inventory, name, worldLevel, WorldLevelMatchMode.Exact, quality, isPrefabName) where item.m_stack < item.m_shared.m_maxStackSize select item).Sum((ItemData item) => item.m_shared.m_maxStackSize - item.m_stack); } public static IEnumerable<ItemData> GetItems(Inventory inventory, string name, float worldLevel, WorldLevelMatchMode matchMode, int quality = -1, bool isPrefabName = false) { return (from data in inventory.GetAllItems() where IsMatchedItem(data, name, worldLevel, quality, isPrefabName, matchMode) select data).ToList(); } [Obsolete("Use overload with WorldLevelMatchMode parameter")] public static IEnumerable<ItemData> GetItems(Inventory inventory, string name, float worldLevel, int quality = -1, bool isPrefabName = false) { return GetItems(inventory, name, worldLevel, WorldLevelMatchMode.Exact, quality, isPrefabName); } public static int AddItem(Inventory inventory, GameObject prefab, int amount, int quality = -1) { if (amount <= 0) { return 0; } ItemData val = prefab.GetComponent<ItemDrop>().m_itemData.Clone(); val.m_dropPrefab = prefab; val.m_stack = Mathf.Min(amount, val.m_shared.m_maxStackSize); val.m_quality = Mathf.Clamp(quality, 1, val.m_shared.m_maxQuality); if ((Object)(object)Game.instance != (Object)null) { val.m_worldLevel = Game.m_worldLevel; } int num = CountItems(inventory, val.m_shared.m_name, val.m_worldLevel, val.m_quality); inventory.AddItem(val); return CountItems(inventory, val.m_shared.m_name, val.m_worldLevel, val.m_quality) - num; } public static int FillFreeStackSpace(Inventory from, Inventory to, string name, float worldLevel, int amount, int quality = -1, bool isPrefabName = false) { if (amount <= 0) { return 0; } int num = Mathf.Min(amount, FindFreeStackSpace(to, name, worldLevel, quality, isPrefabName)); if (num == 0) { return 0; } int num2 = 0; IEnumerable<ItemData> items = GetItems(to, name, worldLevel, WorldLevelMatchMode.Exact, quality, isPrefabName); foreach (ItemData item in GetItems(from, name, worldLevel, WorldLevelMatchMode.Exact, quality, isPrefabName)) { foreach (ItemData item2 in items) { if (item2.m_stack >= item2.m_shared.m_maxStackSize) { continue; } int stack = item.m_stack; int num3 = Mathf.Min(Mathf.Min(num, stack), item2.m_shared.m_maxStackSize - item2.m_stack); if (num3 != 0) { item2.m_stack += num3; num2 += num3; num -= num3; from.RemoveItem(item, num3); NotifyChanged(to); if (num == 0) { goto end_IL_00ff; } if (stack - num3 <= 0) { break; } } } continue; end_IL_00ff: break; } return num2; } public static int FillFreeStackSpace(Inventory inventory, string name, float worldLevel, int amount, int quality = -1, bool isPrefabName = false) { if (amount <= 0) { return 0; } int num = amount; int num2 = 0; foreach (ItemData item in GetItems(inventory, name, worldLevel, WorldLevelMatchMode.Exact, quality, isPrefabName)) { if (item.m_stack >= item.m_shared.m_maxStackSize) { continue; } int num3 = Mathf.Min(num, item.m_shared.m_maxStackSize - item.m_stack); if (num3 > 0) { item.m_stack += num3; num2 += num3; num -= num3; NotifyChanged(inventory); if (num == 0) { break; } } } return num2; } public static bool HaveItem(Inventory inventory, string name, float worldLevel, WorldLevelMatchMode matchMode, int amount, int quality = -1, bool isPrefabName = false) { if (amount <= 0) { return true; } int num = 0; foreach (ItemData item in GetItems(inventory, name, worldLevel, matchMode, quality, isPrefabName)) { num += item.m_stack; if (num >= amount) { return true; } } return false; } [Obsolete("Use overload with WorldLevelMatchMode parameter")] public static bool HaveItem(Inventory inventory, string name, float worldLevel, int amount, int quality = -1, bool isPrefabName = false) { return HaveItem(inventory, name, worldLevel, WorldLevelMatchMode.Exact, amount, quality, isPrefabName); } public static int RemoveItem(Inventory inventory, string name, float worldLevel, WorldLevelMatchMode matchMode, int amount, int quality = -1, bool isPrefabName = false) { if (amount <= 0) { return 0; } int num = 0; foreach (ItemData item in GetItems(inventory, name, worldLevel, matchMode, quality, isPrefabName)) { int num2 = Mathf.Min(amount, item.m_stack); inventory.RemoveItem(item, num2); num += num2; amount -= num2; if (amount == 0) { break; } } return num; } [Obsolete("Use overload with WorldLevelMatchMode parameter")] public static int RemoveItem(Inventory inventory, string name, float worldLevel, int amount, int quality = -1, bool isPrefabName = false) { return RemoveItem(inventory, name, worldLevel, WorldLevelMatchMode.Exact, amount, quality, isPrefabName); } } public static class Json { public static void AddImporter<TJson, TValue>(ImporterFunc<TJson, TValue> importer) { JsonMapper.RegisterImporter(importer); } public static void AddExporter<T>(ExporterFunc<T> exporter) { JsonMapper.RegisterExporter(exporter); } public static T Parse<T>(string jsonText, ReaderOption option) { return JsonMapper.ToObject<T>(new JsonReader(jsonText) { AllowComments = option.AllowComments, AllowSingleQuotedStrings = option.AllowSingleQuotedStrings, SkipNonMembers = option.SkipNonMembers }); } public static T Parse<T>(string jsonText) { return Parse<T>(jsonText, new ReaderOption { AllowComments = true, AllowSingleQuotedStrings = false, SkipNonMembers = true }); } public static string ToString(object obj, WriterOption option) { StringBuilder stringBuilder = new StringBuilder(); JsonMapper.ToJson(obj, new JsonWriter(stringBuilder) { IndentValue = option.IndentSize, PrettyPrint = option.Pretty }); return stringBuilder.ToString(); } public static string ToString(object obj) { return ToString(obj, new WriterOption { Pretty = false, IndentSize = 0 }); } } public struct ReaderOption { public bool AllowComments; public bool AllowSingleQuotedStrings; public bool SkipNonMembers; } public struct WriterOption { public bool Pretty; public int IndentSize; } public class L10N { private struct TranslationSource { public L10N Localization; public string Directory; } private static readonly Regex InternalNamePattern; private static readonly Dictionary<string, string> TranslationCache; private static readonly Dictionary<string, string> RuntimeWords; private static readonly Regex WordPattern; private const string HarmonyId = "net.eideehi.modutils.localization"; private static bool _patchesApplied; private static bool _initializePatchApplied; private static bool _setLanguagePatchApplied; private static bool _initializeMethodWarningLogged; private static bool _setLanguageMethodWarningLogged; private static bool _localizationCacheWarningLogged; private static string _currentLanguage; private static readonly List<TranslationSource> _translationSources; private readonly string _prefix; public static event Action<string> LanguageChanged; static L10N() { _translationSources = new List<TranslationSource>(); InternalNamePattern = new Regex("^(\\$|@)(\\w|\\d|[^\\s(){}[\\]+\\-!?/\\\\&%,.:=<>])+$", RegexOptions.Compiled); TranslationCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); RuntimeWords = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); WordPattern = new Regex("(\\$|@)((?:\\w|\\d|[^\\s(){}[\\]+\\-!?/\\\\&%,.:=<>])+)", RegexOptions.Compiled); } public L10N(string prefix) { _prefix = prefix; } internal static void EnsurePatched() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown if (_patchesApplied) { return; } try { Harmony val = new Harmony("net.eideehi.modutils.localization"); if (!_initializePatchApplied) { MethodInfo methodInfo = AccessTools.Method(typeof(Localization), "Initialize", (Type[])null, (Type[])null); if (methodInfo == null) { if (!_initializeMethodWarningLogged) { Debug.LogWarning((object)"[ModUtils] Localization.Initialize method not found. Auto-reload for initialization will not work until it becomes available."); _initializeMethodWarningLogged = true; } } else { val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(L10N), "OnLocalizationInitialized", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _initializePatchApplied = true; } } if (!_setLanguagePatchApplied) { MethodInfo methodInfo2 = AccessTools.Method(typeof(Localization), "SetLanguage", (Type[])null, (Type[])null); if (methodInfo2 == null) { if (!_setLanguageMethodWarningLogged) { Debug.LogWarning((object)"[ModUtils] Localization.SetLanguage method not found. Auto-reload for language change will not work until it becomes available."); _setLanguageMethodWarningLogged = true; } } else { val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(L10N), "OnLanguageSet", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _setLanguagePatchApplied = true; } } _patchesApplied = _initializePatchApplied && _setLanguagePatchApplied; } catch (Exception arg) { Debug.LogWarning((object)$"[ModUtils] Failed to patch Localization lifecycle methods. Auto-reload will stay disabled. {arg}"); } } private static void OnLocalizationInitialized() { try { Localization instance = Localization.instance; string text = ((instance != null) ? instance.GetSelectedLanguage() : null); if (!string.IsNullOrEmpty(text)) { HandleLanguageChange(text); } } catch (Exception arg) { Debug.LogError((object)$"[ModUtils] OnLocalizationInitialized error: {arg}"); } } private static void OnLanguageSet(string language) { try { if (!string.IsNullOrEmpty(language)) { HandleLanguageChange(language); } } catch (Exception arg) { Debug.LogError((object)$"[ModUtils] OnLanguageSet error: {arg}"); } } private static void HandleLanguageChange(string language) { ApplyLanguageChange(language, fireLanguageChanged: true); } public static string SyncCurrentLanguage() { EnsurePatched(); try { Localization instance = Localization.instance; if (instance == null) { return null; } string selectedLanguage = instance.GetSelectedLanguage(); if (string.IsNullOrEmpty(selectedLanguage)) { return null; } ApplyLanguageChange(selectedLanguage, fireLanguageChanged: false); return selectedLanguage; } catch (Exception arg) { Debug.LogError((object)$"[ModUtils] SyncCurrentLanguage failed: {arg}"); return null; } } private static void ApplyLanguageChange(string language, bool fireLanguageChanged) { if (!string.IsNullOrEmpty(language) && !string.Equals(language, _currentLanguage, StringComparison.OrdinalIgnoreCase)) { ReloadTranslations(language); _currentLanguage = language; RefreshConfigurationMetadata(); RefreshDefaultDrawerTranslations(); if (fireLanguageChanged) { FireLanguageChanged(language); } } } private static void RefreshConfigurationMetadata() { try { Configuration.RefreshAllLocalizedMetadata(); } catch (Exception arg) { Debug.LogError((object)$"[ModUtils] Failed to refresh localized config metadata: {arg}"); } } private static void RefreshDefaultDrawerTranslations() { try { ConfigurationCustomDrawer.RefreshDefaultTranslations(); } catch (Exception arg) { Debug.LogError((object)$"[ModUtils] Failed to refresh custom drawer translations: {arg}"); } } private static void ReloadTranslations(string language) { TranslationCache.Clear(); foreach (TranslationSource translationSource in _translationSources) { try { new TranslationsLoader(translationSource.Localization).LoadTranslations(translationSource.Directory, language); } catch (Exception arg) { Debug.LogError((object)$"[ModUtils] Failed to reload translations from {translationSource.Directory}: {arg}"); } } Localization localization = TryGetLocalization(); foreach (KeyValuePair<string, string> runtimeWord in RuntimeWords) { TranslationCache[runtimeWord.Key] = runtimeWord.Value; AddWordToLocalization(localization, runtimeWord.Key, runtimeWord.Value); } } private static void FireLanguageChanged(string language) { Action<string> languageChanged = L10N.LanguageChanged; if (languageChanged == null) { return; } Delegate[] invocationList = languageChanged.GetInvocationList(); foreach (Delegate @delegate in invocationList) { try { ((Action<string>)@delegate)(language); } catch (Exception arg) { Debug.LogError((object)$"[ModUtils] LanguageChanged handler error: {arg}"); } } } public void AddTranslationDirectory(string directory) { EnsurePatched(); _translationSources.Add(new TranslationSource { Localization = this, Directory = directory }); new TranslationsLoader(this).LoadTranslations(directory, DetectCurrentLanguage()); Localization localization = TryGetLocalization(); foreach (KeyValuePair<string, string> runtimeWord in RuntimeWords) { TranslationCache[runtimeWord.Key] = runtimeWord.Value; AddWordToLocalization(localization, runtimeWord.Key, runtimeWord.Value); } } private static string DetectCurrentLanguage() { if (_currentLanguage != null) { return _currentLanguage; } try { Localization instance = Localization.instance; string text = ((instance != null) ? instance.GetSelectedLanguage() : null); if (!string.IsNullOrEmpty(text)) { return text; } } catch (Exception) { } return "English"; } internal static Localization TryGetLocalization() { try { Localization instance = Localization.instance; if (instance == null) { return null; } return string.IsNullOrEmpty(instance.GetSelectedLanguage()) ? null : instance; } catch (Exception) { return null; } } private static string InvokeTranslate(string word) { Localization val = TryGetLocalization(); TranslationCache.TryGetValue(word, out var value); if (val == null) { return value ?? word; } string text = Reflections.InvokeMethod<string>(val, "Translate", new object[1] { word }); if (text == null || IsMissingTranslation(word, text)) { return value ?? word; } return text; } private static bool IsMissingTranslation(string word, string localized) { if (!string.Equals(localized, word, StringComparison.Ordinal)) { return string.Equals(localized, "[" + word + "]", StringComparison.Ordinal); } return true; } private static string InvokeInsertWordsFallback(string text, IReadOnlyList<string> words) { if (string.IsNullOrEmpty(text)) { return text; } string text2 = text; for (int i = 0; i < words.Count; i++) { text2 = text2.Replace($"${i + 1}", words[i] ?? ""); } return text2; } private static string InvokeInsertWords(string text, string[] words) { if (string.IsNullOrEmpty(text)) { return text; } Localization val = TryGetLocalization(); if (val == null) { return InvokeInsertWordsFallback(text, words); } return Reflections.InvokeMethod<string>(val, "InsertWords", new object[2] { text, words }); } private static void InvokeAddWord(string key, string word) { TranslationCache[key] = word; AddWordToLocalization(TryGetLocalization(), key, word); } private static void InvokeAddRuntimeWord(string key, string word) { RuntimeWords[key] = word; TranslationCache[key] = word; AddWordToLocalization(TryGetLocalization(), key, word); } private static void AddWordToLocalization(Localization localization, string key, string word) { if (localization != null) { Reflections.InvokeMethod(localization, "AddWord", key, word); EvictLocalizationCache(localization); } } private static void EvictLocalizationCache(Localization localization) { try { object field = Reflections.GetField<object>(localization, "m_cache"); if (field != null) { Reflections.InvokeMethod(field, "EvictAll"); } } catch (Exception arg) { if (!_localizationCacheWarningLogged) { Debug.LogWarning((object)$"[ModUtils] Failed to evict Localization cache after adding a word. Game-localized text may stay stale until the next language reload. {arg}"); _localizationCacheWarningLogged = true; } } } internal static string GetTranslationKey(string prefix, string internalName) { if (string.IsNullOrEmpty(internalName)) { return ""; } return internalName[0] switch { '$' => internalName.Substring(1), '@' => prefix + "_" + internalName.Substring(1), _ => internalName, }; } internal static string Translate(string prefix, string word) { return InvokeTranslate(GetTranslationKey(prefix, word)); } public static bool IsInternalName(string text) { if (!string.IsNullOrEmpty(text)) { return InternalNamePattern.IsMatch(text); } return false; } private string GetTranslationKey(string internalName) { return GetTranslationKey(_prefix, internalName); } public void AddWord(string key, string word) { InvokeAddRuntimeWord(GetTranslationKey(key), word); } internal void AddFileWord(string key, string word) { InvokeAddWord(GetTranslationKey(key), word); } public string Translate(string word) { return InvokeTranslate(GetTranslationKey(word)); } public string TranslateInternalName(string internalName) { if (IsInternalName(internalName)) { return InvokeTranslate(GetTranslationKey(internalName)); } return internalName; } public string Localize(string text) { if (string.IsNullOrEmpty(text)) { return text; } StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (Match item in WordPattern.Matches(text)) { GroupCollection groups = item.Groups; string word = ((groups[1].Value == "@") ? (_prefix + "_" + groups[2].Value) : groups[2].Value); stringBuilder.Append(text.Substring(num, groups[0].Index - num)); stringBuilder.Append(InvokeTranslate(word)); num = groups[0].Index + groups[0].Value.Length; } stringBuilder.Append(text.Substring(num)); return stringBuilder.ToString(); } public string Localize(string text, params object[] args) { object[] array = args ?? new object[0]; return InvokeInsertWords(Localize(text), Array.ConvertAll(array, (object arg) => (arg != null) ? ((!(arg is string internalName)) ? arg.ToString() : TranslateInternalName(internalName)) : "")); } public string LocalizeTextOnly(string text, params object[] args) { object[] array = args ?? new object[0]; return InvokeInsertWords(Localize(text), Array.ConvertAll(array, delegate(object arg) { object obj; if (arg != null) { obj = arg as string; if (obj == null) { return arg.ToString(); } } else { obj = ""; } return (string)obj; })); } } public class TranslationsLoader { private static readonly string DefaultLanguage; private static readonly string JsonFilePattern; private readonly L10N _localization; private Dictionary<string, TranslationsFile> _cache; private Logger _logger; static TranslationsLoader() { DefaultLanguage = "English"; JsonFilePattern = "*.json"; } public TranslationsLoader(L10N localization) { _localization = localization; } public void SetDebugLogger(Logger logger) { _logger = logger; } private bool LoadAllFile(string directory, string filePattern, string language, Func<string, string, bool> loading) { _logger?.Debug("Load translation files for " + language + " from directory: [directory: " + directory + ", file pattern: " + filePattern + "]"); return Directory.EnumerateFiles(directory, filePattern, SearchOption.AllDirectories).Count((string path) => loading(path, language)) > 0; } public void LoadTranslations(string languagesDir, string language) { _cache = new Dictionary<string, TranslationsFile>(); if (!Directory.Exists(languagesDir)) { _logger?.Error("Directory does not exist: " + languagesDir); return; } if (language != DefaultLanguage && !LoadAllFile(languagesDir, JsonFilePattern, DefaultLanguage, ReadJsonFile)) { _logger?.Warning("Directory does not contain a translation file for the default language: " + languagesDir); } if (!LoadAllFile(languagesDir, JsonFilePattern, language, ReadJsonFile)) { _logger?.Warning("Directory does not contain a translation file for the " + language + ": " + languagesDir); } _cache = null; } public void LoadTranslations(string languagesDir) { string text; try { Localization instance = Localization.instance; text = ((instance != null) ? instance.GetSelectedLanguage() : null); } catch (Exception) { text = null; } LoadTranslations(languagesDir, string.IsNullOrEmpty(text) ? DefaultLanguage : text); } private bool ReadJsonFile(string path, string language) { if (!_cache.TryGetValue(path, out var value)) { try { value = Json.Parse<TranslationsFile>(File.ReadAllText(path)); _cache.Add(path, value); } catch (Exception arg) { _logger?.Error($"Failed to read Json file\n{arg}"); _cache.Add(path, default(TranslationsFile)); return false; } } if (!string.Equals(value.language, language, StringComparison.OrdinalIgnoreCase)) { return false; } _logger?.Debug("Load translations: " + path); if (value.translations == null) { _logger?.Warning("Translation file does not contain translations: " + path); return true; } foreach (KeyValuePair<string, string> translation in value.translations) { _localization.AddFileWord(translation.Key, translation.Value); } return true; } } [Serializable] public struct TranslationsFile { public string language; public Dictionary<string, string> translations; } public class Logger { private readonly Func<LogLevel, bool> _isEnabled; private readonly ManualLogSource _logger; public Logger(ManualLogSource logger, Func<LogLevel, bool> isEnabled) { _logger = logger; _isEnabled = isEnabled; } private void Log(LogLevel level, string message) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (_isEnabled(level)) { _logger.Log(level, (object)message); } } private void Log(LogLevel level, Func<string> message) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (_isEnabled(level)) { _logger.Log(level, (object)message()); } } public void Fatal(Func<string> message) { Log((LogLevel)1, message); } public void Fatal(string message) { Log((LogLevel)1, message); } public void Error(Func<string> message) { Log((LogLevel)2, message); } public void Error(string message) { Log((LogLevel)2, message); } public void Warning(Func<string> message) { Log((LogLevel)4, message); } public void Warning(string message) { Log((LogLevel)4, message); } public void Info(Func<string> message) { Log((LogLevel)16, message); } public void Info(string message) { Log((LogLevel)16, message); } public void Message(Func<string> message) { Log((LogLevel)8, message); } public void Message(string message) { Log((LogLevel)8, message); } public void Debug(Func<string> message) { Log((LogLevel)32, message); } public void Debug(string message) { Log((LogLevel)32, message); } } public abstract class ObjectNode<TNode, TNetwork> : MonoBehaviour where TNode : ObjectNode<TNode, TNetwork> where TNetwork : ObjectNetwork<TNode, TNetwork> { protected static readonly HashSet<TNode> ObjectNodes; private TNode This => (TNode)this; public TNetwork Network { get; set; } static ObjectNode() { ObjectNodes = new HashSet<TNode>(); } protected virtual void Awake() { ObjectNodes.Add(This); ((MonoBehaviour)this).Invoke("NetworkConstruction", Random.Range(1f, 2f)); } protected virtual void OnDestroy() { ObjectNodes.Remove(This); Network?.RemoveNode(This); Network = null; } protected abstract TNetwork CreateNetwork(); protected abstract bool IsConnectable(TNode other); private void NetworkConstruction() { foreach (TNode item in ObjectNodes.Where(IsConnectable)) { if (Network == item.Network) { continue; } if (Network == null) { Network = item.Network; Network.AddNode(This); continue; } if (item.Network == null) { item.Network = Network; item.Network.AddNode(item); continue; } TNetwork obj = ((Network.NodeCount >= item.Network.NodeCount) ? item.Network : Network); TNetwork network = ((Network.NodeCount >= item.Network.NodeCount) ? Network : item.Network); foreach (TNode allNode in obj.GetAllNodes()) { allNode.Network.RemoveNode(allNode); allNode.Network = network; allNode.Network.AddNode(allNode); } } if (Network == null) { Network = CreateNetwork(); Network.AddNode(This); } } } public class ObjectNetwork<TNode, TNetwork> where TNode : ObjectNode<TNode, TNetwork> where TNetwork : ObjectNetwork<TNode, TNetwork> { private readonly HashSet<TNode> _nodes; protected Action<IEnumerable<TNode>> OnNodeChanged { get; set; } public bool IsDirty { get; private set; } public int NodeCount => _nodes.Count; protected ObjectNetwork() { _nodes = new HashSet<TNode>(); } public IEnumerable<TNode> EnumerateNodes() { return _nodes; } public IEnumerable<TNode> GetAllNodes() { return _nodes.ToList(); } public void AddNode(TNode node) { if (_nodes.Add(node)) { IsDirty = true; } } public void RemoveNode(TNode node) { if (_nodes.Remove(node)) { IsDirty = true; } } public void Update() { if (IsDirty) { OnNodeChanged?.Invoke(GetAllNodes()); IsDirty = false; } } } public static class Objects { private static readonly ConditionalWeakTable<Component, ZNetView> ZNetViewCache; private static readonly ConditionalWeakTable<Component, string> NameCache; private const string HoverableMarker = "\u0001HOVERABLE"; static Objects() { ZNetViewCache = new ConditionalWeakTable<Component, ZNetView>(); NameCache = new ConditionalWeakTable<Component, string>(); } public static string GetPrefabName(GameObject gameObject) { return Utils.GetPrefabName(gameObject); } public static string GetName(Component component) { if (!Object.op_Implicit((Object)(object)component)) { return ""; } if (NameCache.TryGetValue(component, out var value)) { if ((object)value == "\u0001HOVERABLE") { Hoverable component2 = component.GetComponent<Hoverable>(); string text = ((component2 != null) ? component2.GetHoverName() : null); if (string.IsNullOrEmpty(text)) { return Utils.GetPrefabName(component.gameObject); } return text; } return value; } string text2 = (from x in ((object)component).GetType().GetFields(AccessTools.all) where x.Name == "m_name" && x.FieldType == typeof(string) select x.GetValue(component) as string).FirstOrDefault(); if (!string.IsNullOrEmpty(text2)) { NameCache.Add(component, text2); return text2; } Hoverable component3 = component.GetComponent<Hoverable>(); if (component3 != null) { HoverText val = (HoverText)(object)((component3 is HoverText) ? component3 : null); if (val != null) { text2 = val.m_text; if (!string.IsNullOrEmpty(text2)) { NameCache.Add(component, text2); return text2; } } else { text2 = component3.GetHoverName(); if (!string.IsNullOrEmpty(text2)) { NameCache.Add(component, "\u0001HOVERABLE"); return text2; } } } text2 = Utils.GetPrefabName(component.gameObject); NameCache.Add(component, text2); return text2; } public static bool GetZNetView(Component component, out ZNetView zNetView) { if (!Object.op_Implicit((Object)(object)component)) { zNetView = null; return false; } if (ZNetViewCache.TryGetValue(component, out zNetView)) { return Object.op_Implicit((Object)(object)zNetView); } zNetView = (from x in ((object)component).GetType().GetFields(AccessTools.all) where x.Name == "m_nview" && x.FieldType == typeof(ZNetView) select x).Select(delegate(FieldInfo x) { object? value = x.GetValue(component); return (ZNetView)((value is ZNetView) ? value : null); }).FirstOrDefault() ?? component.GetComponent<ZNetView>(); if ((Object)(object)zNetView == (Object)null) { return false; } ZNetViewCache.Add(component, zNetView); return true; } public static bool GetZdoid(Component component, out ZDOID id) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) id = (GetZNetView(component, out var zNetView) ? zNetView.GetZDO() : null)?.m_uid ?? ZDOID.None; return id != ZDOID.None; } public static bool HasValidOwnership(ZNetView zNetView) { if ((Object)(object)zNetView != (Object)null && zNetView.GetZDO() != null && zNetView.IsValid()) { return zNetView.IsOwner(); } return false; } public static bool HasValidOwnership(Component component) { ZNetView zNetView; return HasValidOwnership(component, out zNetView); } public static bool HasValidOwnership(Component component, out ZNetView zNetView) { if (GetZNetView(component, out zNetView)) { return HasValidOwnership(zNetView); } return false; } public static float Distance(Component obj1, Component obj2) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) return Vector3.Distance(obj1.transform.position, obj2.transform.position); } public static List<(Collider collider, T obj, float distance)> GetInsideSphere<T>(Vector3 origin, float radius, Func<Collider, T> convertor, Collider[] buffer, int layerMask = -1) { //IL_0006: 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_0036: Unknown result type (might be due to invalid IL or missing references) List<(Collider, T, float)> list = new List<(Collider, T, float)>(); int num = Physics.OverlapSphereNonAlloc(origin, radius, buffer, layerMask); for (int i = 0; i < num; i++) { Collider val = buffer[i]; T val2 = convertor(val); if (val2 != null) { list.Add((val, val2, Vector3.Distance(origin, ((Component)val).transform.position))); } } return list; } public static List<(Collider collider, T obj, float distance)> GetInsideSphere<T>(Vector3 origin, float radius, Func<Collider, T> convertor, int bufferSize = 128, int layerMask = -1) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return GetInsideSphere(origin, radius, convertor, (Collider[])(object)new Collider[bufferSize], layerMask); } } public static class Reflections { public static TR InvokeStaticMethod<T, TR>(string methodName, params object[] args) { return Traverse.Create<T>().Method(methodName, args).GetValue<TR>(args); } public static void InvokeStaticMethod<T>(string methodName, params object[] args) { Traverse.Create<T>().Method(methodName, args).GetValue(args); } public static TR InvokeStaticMethod<T, TR>(string methodName) { return Traverse.Create<T>().Method(methodName, Array.Empty<object>()).GetValue<TR>(); } public static void InvokeStaticMethod<T>(string methodName) { Traverse.Create<T>().Method(methodName, Array.Empty<object>()).GetValue(); } public static T InvokeMethod<T>(object instance, string methodName, params object[] args) { return Traverse.Create(instance).Method(methodName, args).GetValue<T>(args); } public static void InvokeMethod(object instance, string methodName, params object[] args) { Traverse.Create(instance).Method(methodName, args).GetValue(args); } public static T InvokeMethod<T>(object instance, string methodName) { return Traverse.Create(instance).Method(methodName, Array.Empty<object>()).GetValue<T>(); } public static void InvokeMethod(object instance, string methodName) { Traverse.Create(instance).Method(methodName, Array.Empty<object>()).GetValue(); } public static TType GetStaticField<TClass, TType>(string fieldName) { return Traverse.Create<TClass>().Field<TType>(fieldName).Value; } public static TType SetStaticField<TClass, TType>(string fieldName, TType value) { return Traverse.Create<TClass>().Field<TType>(fieldName).Value = value; } public static T GetField<T>(object instance, string fieldName) { return Traverse.Create(instance).Field<T>(fieldName).Value; } public static void SetField<T>(object instance, string fieldName, T value) { Traverse.Create(instance).Field<T>(fieldName).Value = value; } } public class SpriteLoader { private static readonly Dictionary<string, Texture2D> TextureCache; private Logger _logger; static SpriteLoader() { TextureCache = new Dictionary<string, Texture2D>(); } private static Sprite CreateSprite(Texture2D texture, int width, int height) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) return Sprite.Create(texture, new Rect(0f, 0f, (float)width, (float)height), Vector2.zero); } public static string GetTextureFileName(Sprite sprite) { using (IEnumerator<KeyValuePair<string, Texture2D>> enumerator = TextureCache.Where((KeyValuePair<string, Texture2D> pair) => (Object)(object)pair.Value == (Object)(object)sprite.texture).GetEnumerator()) { if (enumerator.MoveNext()) { return Path.GetFileName(enumerator.Current.Key); } } return ""; } public void SetDebugLogger(Logger logger) { _logger = logger; } public Sprite Load(string texturePath, int width, int height) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown if (!File.Exists(texturePath)) { return null; } if (TextureCache.TryGetValue(texturePath, out var value)) { if (!((Object)(object)value != (Object)null)) { return null; } return CreateSprite(value, width, height); } try { _logger?.Info("Try to create sprite: " + texturePath); Texture2D val = new Texture2D(0, 0); ImageConversion.LoadImage(val, File.ReadAllBytes(texturePath)); TextureCache.Add(texturePath, val); return CreateSprite(val, width, height); } catch (Exception arg) { _logger?.Error($"Failed to create sprite: {texturePath}\n{arg}"); TextureCache.Add(texturePath, null); return null; } } } } namespace Automatics { [BepInPlugin("net.eidee.valheim.automatics", "Automatics", "1.6.0")] public class UnityPlugin : BaseUnityPlugin { private const string ModId = "net.eidee.valheim.automatics"; private const string ModName = "Automatics"; private const string ModVersion = "1.6.0"; private void Awake() { Automatics.Initialize((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger); } private void FixedUpdate() { if (!((Object)(object)Player.m_localPlayer != (Object)null) && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer()) { Hooks.OnDedicatedServerFixedUpdate?.Invoke(Time.fixedDeltaTime); } } } internal static class Automatics { public const string L10NPrefix = "automatics"; private static string _modLocation; private static string _guid; private static List<string> _allResourcesDirectory; private static bool _runtimeInitialized; public static BaseUnityPlugin Plugin { get; private set; } public static Logger Logger { get; private set; } public static ManualLogSource LogSource { get; private set; } public static L10N L10N { get; private set; } private static IEnumerable<string> GetAllResourcesDirectory() { if (_allResourcesDirectory != null) { return _allResourcesDirectory; } _allResourcesDirectory = new List<string> { _modLocation }; string pluginPath = Paths.PluginPath; if (!Directory.Exists(pluginPath)) { return _allResourcesDirectory; } string[] directories = Directory.GetDirectories(pluginPath); foreach (string text in directories) { if (File.Exists(Path.Combine(text, "automatics-child-mod"))) { _allResourcesDirectory.Add(text); } else if (File.Exists(Path.Combine(text, "automatics-resources-marker"))) { _allResourcesDirectory.Add(text); } } return _allResourcesDirectory; } private static void InitializeModules(Assembly assembly) { foreach (var item2 in from x in ((IEnumerable<Type>)AccessTools.GetTypesFromAssembly(assembly)).SelectMany((Func<Type, IEnumerable<MethodInfo>>)AccessTools.GetDeclaredMethods) select (x, x.GetCustomAttributes(typeof(AutomaticsInitializerAttribute), inherit: false).OfType<AutomaticsInitializerAttribute>().FirstOrDefault()) into x where x.Item2 != null orderby x.Item2.Order select x) { MethodInfo item = item2.Item1; try { item.Invoke(null, new object[0]); } catch (Exception arg) { Logger.Error($"Error while initializing {item.Name}\n{arg}"); } } } public static IEnumerable<string> GetAllResourcePath(string pathname) { return from x in GetAllResourcesDirectory() select Path.Combine(x, pathname); } public static string GetHarmonyId(string moduleName) { return _guid + "." + moduleName; } public static void Initialize(BaseUnityPlugin plugin, ManualLogSource logger) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown Plugin = plugin; LogSource = logger; Logger = new Logger(logger, Config.IsLogEnabled); _modLocation = Path.GetDirectoryName(Plugin.Info.Location) ?? ""; _guid = Plugin.Info.Metadata.GUID; Logger.Debug("Mod location: " + _modLocation); ConfigMigration.Migration(Plugin.Config); ValheimObject.Initialize(GetAllResourcePath("Data")); Harmony val = new Harmony(_guid); L10N = new L10N("automatics"); foreach (string item in GetAllResourcePath("Languages")) { L10N.AddTranslationDirectory(item); } L10N.LanguageChanged += InitializeRuntime; InitializeRuntime(L10N.SyncCurrentLanguage() ?? "English"); Hooks.OnInitTerminal = (Action)Delegate.Combine(Hooks.OnInitTerminal, new Action(Commands.Register)); val.PatchAll(typeof(Patches)); } public static void InitializeRuntime(string language) { if (_runtimeInitialized) { ConfigurationManagerBridge.Refresh(); return; } Config.Initialize(Plugin.Config); Plugin.Config.Save(); InitializeModules(Assembly.GetExecutingAssembly()); Plugin.Config.Save(); ValheimObject.PostInitialize(); _runtimeInitialized = true; ConfigurationManagerBridge.Refresh(); } } [AttributeUsage(AttributeTargets.Method)] [MeansImplicitUse] public class AutomaticsInitializerAttribute : Attribute { public int Order { get; } public AutomaticsInitializerAttribute(int order = 0) { Order = order; } } internal static class Commands { public static void Register() { new ShowCommands().Register(); new PrintNames().Register(); new PrintObjects().Register(); new RemoveMapPins().Register(); } } internal static class Config { private const int NexusID = 1700; private static ConfigEntry<bool> _logEnabled; private static ConfigEntry<LogLevel> _allowedLogLevel; public static bool LogEnabled => _logEnabled.Value; public static LogLevel AllowedLogLevel => _allowedLogLevel.Value; public static bool Initialized { get; private set; } public static Configuration Instance { get; private set; } public static bool IsLogEnabled(LogLevel level) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 if (Initialized) { if (LogEnabled) { return (AllowedLogLevel & level) > 0; } return false; } return true; } public static ConfigEntry<List<ObjectElement>> BindCustomValheimObject(this Configuration config, string key, ValheimObject obj) { ConfigEntry<List<ObjectElement>> entry = config.Bind(key, new List<ObjectElement>()); if (entry.Value.Any()) { obj.RegisterCustom(entry.Value); } entry.SettingChanged += delegate { obj.RegisterCustom(entry.Value); }; return entry; } public static ConfigEntry<StringList> BindValheimObjectList(this Configuration config, string key, ValheimObject obj, IEnumerable<string> defaults = null, IEnumerable<string> includes = null, IEnumerable<string> excludes = null) { List<string> list = new List<string>(); list.AddRange(defaults ?? (from x in obj.GetAllElements() select x.identifier)); if (includes != null) { list.RemoveAll((string x) => !includes.Contains(x)); } if (excludes != null) { list.RemoveAll(excludes.Contains<string>); } return config.Bind(key, new StringList(list), null, delegate(ConfigurationManagerAttributes x) { x.CustomDrawer = ConfigurationCustomDrawer.MultiSelect(() => from y in obj.GetAllElements() select y.identifier, (string identifier) => (!obj.GetName(identifier, out var name)) ? identifier : Automatics.L10N.TranslateInternalName(name)); }); } public static void Initialize(ConfigFile config) { if (!Initialized) { Instance = new Configuration(config, Automatics.L10N); Instance.ChangeSection("hidden"); Instance.Bind("NexusID", 1700, null, delegate(ConfigurationManagerAttributes x) { x.Browsable = false; x.ReadOnly = true; }); Instance.ChangeSection("system"); _logEnabled = Instance.Bind("enable_logging", defaultValue: false); _allowedLogLevel = Instance.Bind<LogLevel>("log_level_to_allow_logging", (LogLevel)15); Instance.ChangeSection("general"); Instance.BindCustomValheimObject("custom_animal", ValheimObject.Animal); Instance.BindCustomValheimObject("custom_dungeon", ValheimObject.Dungeon); Instance.BindCustomValheimObject("custom_flora", ValheimObject.Flora); Instance.BindCustomValheimObject("custom_mineral", ValheimObject.Mineral); Instance.BindCustomValheimObject("custom_monster", ValheimObject.Monster); Instance.BindCustomValheimObject("custom_spawner", ValheimObject.Spawner); Instance.BindCustomValheimObject("custom_spot", ValheimObject.Spot); Initialized = true; } } } public enum AutomaticsModule { [LocalizedDescription("automatics", "@message_module_enabled")] Enabled, [LocalizedDescription("automatics", "@message_module_disabled")] Disabled } public enum Message { [LocalizedDescription("automatics", "@message_none")] None, [LocalizedDescription("automatics", "@message_center")] Center, [LocalizedDescription("automatics", "@message_top_left")] TopLeft } internal static class ConfigMigration { private class Config { public int Offset; public string Category; public string Key; public string Value; } private delegate bool Operation(string category, List<string> lines, int begin, int end); private readonly struct Version : IComparable<Version> { private readonly int _major; private readonly int _minor; private readonly int _patch; public Version(int major, int minor, int patch) { _major = major; _minor = minor; _patch = patch; } public override string ToString() { return $"v{_major}.{_minor}.{_patch}"; } public int CompareTo(Version other) { if (_major != other._major) { return _major.CompareTo(other._major); } if (_minor == other._minor) { return _patch.CompareTo(other._patch); } return _minor.CompareTo(other._minor); } public static bool operator >(Version a, Version b) { return a.CompareTo(b) > 0; } public static bool operator <(Version a, Version b) { return a.CompareTo(b) < 0; } } private static readonly Regex VersionPattern; private static readonly char[] KeyValueSeparator; private static readonly Dictionary<string, List<Config>> ConfigCache; static ConfigMigration() { VersionPattern = new Regex("v(\\d+)\\.(\\d+)\\.(\\d+)$", RegexOptions.Compiled); KeyValueSeparator = new char[1] { '=' }; ConfigCache = new Dictionary<string, List<Config>>(); } private static Operation RenameCategory(string newCategory) { return delegate(string category, List<string> lines, int begin, int end) { string text = lines[begin]; lines[begin] = newCategory; Automatics.Logger.Message("Rename category: " + text + " => " + newCategory); return true; }; } private static Operation RemoveConfig(string key) { return delegate(string category, List<string> lines, int begin, int end) { foreach (Config item in FindConfig(category, key, lines, begin, end)) { string key2 = item.Key; item.Key = ""; UpdateConfig(item, lines); Automatics.Logger.Message("Remove config: " + category + " " + key2); } return true; }; } private static Operation RenameConfig(string key, string newKey) { return delegate(string category, List<string> lines, int begin, int end) { foreach (Config item in FindConfig(category, key, lines, begin, end)) { string key2 = item.Key; item.Key = (GetMatcher(key).IsRegex ? Regex.Replace(key, key, newKey) : newKey); UpdateConfig(item, lines); Automatics.Logger.Message("Rename config: " + category + " " + key2 + " => " + item.Key); } return true; }; } private static Operation ReplaceValue(string key, string value, string newValue) { return delegate(string category, List<string> lines, int begin, int end) { foreach (Config item in from x in FindConfig(category, key, lines, begin, end) where string.Equals(x.Value, value, StringComparison.OrdinalIgnoreCase) select x) { string value2 = item.Value; item.Value = newValue; UpdateConfig(item, lines); Automatics.Logger.Message("Replace value: " + category + " " + key + " " + value2 + " => " + newValue); } return true; }; } private static Operation AppendValues(string key, string[] valuesToAppend) { return delegate(string category, List<string> lines, int begin, int end) { List<Config> list = FindConfig(category, key, lines, begin, end).ToList(); if (!list.Any()) { return false; } foreach (Config item2 in list) { string text = item2.Value ?? ""; List<string> list2 = (string.IsNullOrWhiteSpace(text) ? new List<string>() : (from x in Csv.ParseLine(text) where !string.IsNullOrEmpty(x) select x).ToList()); HashSet<string> hashSet = new HashSet<string>(list2, StringComparer.Ordinal); List<string> list3 = new List<string>(); string[] array = valuesToAppend; foreach (string text2 in array) { if (!string.IsNullOrWhiteSpace(text2)) { string item = text2.Trim(); if (hashSet.Add(item)) { list2.Add(item); list3.Add(item); } } } if (list3.Count != 0) { item2.Value = string.Join(", ", list2.Select(Csv.Escape)); UpdateConfig(item2, lines); Automatics.Logger.Message("Append values: " + category + " " + key + " += [" + string.Join(", ", list3) + "]"); } } return true; }; } private static IEnumerable<Config> FindConfig(string category, string key, List<string> lines, int begin = 0, int end = int.MaxValue) { (bool IsRegex, string Pattern) matcher = GetMatcher(key); if (ConfigCache.TryGetValue(category, out var value)) { return value.Where((Config x) => x.Category == category && IsMatch(x.Key, matcher)); } end = Mathf.Min(end, lines.Count); begin = Mathf.Clamp(begin, 0, end); value = new List<Config>(0); string text = ""; int i; for (i = begin; i < end; i++) { string text2 = lines[i]; if (text2.StartsWith("#")) { continue; } if (Regex.IsMatch(text2, "^\\[[\\w\\d_]+\\]$")) { text = text2; if (!ConfigCache.TryGetValue(text, out value)) { value = new List<Config>(); ConfigCache[text] = value; } } else if (!value.Any((Config x) => x.Offset == i)) { string[] array = text2.Split(KeyValueSeparator, 2, StringSplitOptions.None); if (array.Length == 2) { Config item = new Config { Offset = i, Category = text, Key = array[0].Trim(), Value = array[1].Trim() }; value.Add(item); } } } if (ConfigCache.TryGetValue(category, out value)) { return value.Where((Config x) => x.Category == category && IsMatch(x.Key, matcher)); } Automatics.Logger.Warning("No config matching condition exists: " + category + " / " + key); return Array.Empty<Config>(); } private static void UpdateConfig(Config config, List<string> lines) { if (config.Offset >= 0 || config.Offset < lines.Count) { if (string.IsNullOrEmpty(config.Key)) { lines[config.Offset] = ""; } else { lines[config.Offset] = config.Key + " = " + config.Value; } } } private static (bool IsRegex, string Pattern) GetMatcher(string pattern) { if (string.IsNullOrEmpty(pattern)) { return (false, ""); } if (!pattern.StartsWith("r/")) { return (false, pattern); } return (true, pattern.Substring(2)); } private static bool IsMatch(string value, (bool IsRegex, string Pattern) matcher) { if (!matcher.IsRegex) { if (!string.IsNullOrEmpty(matcher.Pattern)) { return string.Equals(value, matcher.Pattern, StringComparison.OrdinalIgnoreCase); } return false; } return Regex.IsMatch(value, matcher.Pattern, RegexOptions.IgnoreCase); } public static void Migration(ConfigFile config) { string configFilePath = config.ConfigFilePath; if (!File.Exists(configFilePath)) { return; } List<string> list = File.ReadAllLines(configFilePath).ToList(); if (list.Any()) { bool flag = false; Version version = ParseVersion(list[0]); Version version2 = new Version(1, 3, 0); if (version < version2) { Automatics.Logger.Message($"Migrating config from {version} to {version2}"); flag = true; MigrationFor130(list); } version2 = new Version(1, 4, 0); if (version < version2) { Automatics.Logger.Message($"Migrating config from {version} to {version2}"); flag = true; MigrationFor140(list); } version2 = new Version(1, 4, 5); if (version < version2) { Automatics.Logger.Message($"Migrating config from {version} to {version2}"); flag = true; MigrationFor145(list); } version2 = new Version(1, 6, 0); if (version < version2) { Automatics.Logger.Message($"Migrating config from {version} to {version2}"); flag = true; MigrationFor160(list); } if (flag) { File.WriteAllText(configFilePath, string.Join(Environment.NewLine, list), Encoding.UTF8); config.Reload(); } } } private static void MigrationFor130(List<string> lines) { Migration(lines, new Dictionary<string, List<Operation>> { { "[logging]", new List<Operation> { RenameCategory("[system]"), RenameConfig("logging_enabled", "enable_logging"), RenameConfig("allowed_log_level", "log_level_to_allow_logging") } }, { "[automatic_door]", new List<Operation> { RenameConfig("automatic_door_enabled", "enable_automatic_door"), RenameConfig("player_search_radius_to_open", "distance_for_automatic_opening"), RenameConfig("player_search_radius_to_close", "distance_for_automatic_closing"), RenameConfig("toggle_automatic_door_enabled_key", "automatic_door_enable_disable_toggle") } }, { "[automatic_map_pinning]", new List<Operation> { RenameCategory("[automatic_mapping]"), RenameConfig("automatic_map_pinning_enabled", "enable_automatic_mapping"), RenameConfig("allow_pinning_vein", "allow_pinning_deposit"), RenameConfig("allow_pinning_vein_custom", "allow_pinning_deposit_custom"), RenameConfig("ignore_tamed_animals", "not_pinning_tamed_animals"), RenameConfig("in_ground_veins_need_wishbone", "need_to_equip_wishbone_for_underground_deposits") } }, { "[automatic_processing]", new List<Operation> { RenameConfig("automatic_processing_enabled", "enable_automatic_processing"), RenameConfig("r/^([\\w_]+)_allow_automatic_processing", "allow_processing_by_$1"), RenameConfig("r/^([\\w_]+)_container_search_range", "container_search_range_by_$1"), RenameConfig("r/^([\\w_]+)_material_count_that_suppress_automatic_process", "$1_material_count_of_suppress_processing"), RenameConfig("r/^([\\w_]+)_fuel_count_that_suppress_automatic_process", "$1_fuel_count_of_suppress_processing"), RenameConfig("r/^([\\w_]+)_product_count_that_suppress_automatic_store", "$1_product_count_of_suppress_processing") } }, { "[automatic_feeding]", new List<Operation> { RenameConfig("automatic_feeding_enabled", "enable_automatic_feeding"), RenameConfig("need_close_to_eat_the_feed", "need_get_close_to_eat_the_feed"), RenameConfig("automatic_repair_enabled", "enable_automatic_repair") } } }); } private static void MigrationFor140(List<string> lines) { Migration(lines, new Dictionary<string, List<Operation>> { { "[automatic_door]", new List<Operation> { RemoveConfig("allow_automatic_door_custom"), ReplaceValue("allow_automatic_door", "All", "WoodDoor, WoodGate, IronGate, DarkwoodGate, WoodShutter") } }, { "[automatic_mapping]", new List<Operation> { RenameConfig("dynamic_object_search_range", "dynamic_object_mapping_range"), RenameConfig("static_object_search_range", "static_object_mapping_range"), RenameConfig("location_search_range", "location_mapping_range"), RenameConfig("allow_pinning_deposit", "allow_pinning_mineral"), RemoveConfig("allow_pinning_ship"), RemoveConfig("r/^allow_pinning_[\\w]+_custom$"), RenameConfig("static_object_search_interval", "static_object_mapping_interval"), RenameConfig("need_to_equip_wishbone_for_underground_deposits", "need_to_equip_wishbone_for_underground_minerals"), RenameConfig("static_object_search_key", "static_object_mapping_key"), ReplaceValue("allow_pinning_animal", "All", "Boar, Piggy, Deer, Wolf, WolfCub, Lox, LoxCalf, Hen, Chicken, Hare, Bird, Fish"), ReplaceValue("allow_pinning_monster", "All", "Greyling, Neck, Ghost, Greydwarf, GreydwarfBrute, GreydwarfShaman, RancidRemains, Skeleton, Troll, Abomination, Blob, Draugr, DraugrElite, Leech, Oozer, Surtling, Wraith, Drake, Fenring, StoneGolem, Deathsquito, Fuling, FulingBerserker, FulingShaman, Growth, Serpent, Bat, FenringCultist, Ulv, DvergrRogue, DvergrMage, Tick, Seeker, SeekerBrood, Gjall, SeekerSoldier"), ReplaceValue("allow_pinning_flora", "All", "Dandelion, Mushroom, Raspberries, Blueberries, Carrot, CarrotSeeds, YellowMushroom, Thistle, Turnip, TurnipSeeds, Onion, OnionSeeds, Barley, Cloudberries, Flex, JotunPuffs, Magecap"), ReplaceValue("allow_pinning_mineral", "All", "CopperDeposit, TinDeposit, MudPile, ObsidianDeposit, SilverVein, PetrifiedBone, SoftTissue"), ReplaceValue("allow_pinning_spawner", "All", "GreydwarfNest, EvilBonePile, BodyPile"), ReplaceValue("allow_pinning_other", "All", "Vegvisir, Runestone, WildBeehive"), ReplaceValue("allow_pinning_dungeon", "All", "BurialChambers, TrollCave, SunkenCrypts, MountainCave, InfestedMine"), ReplaceValue("allow_pinning_spot", "All", "InfestedTree, FireHole, DrakeNest, GoblinCamp, TarPit, DvergrExcavation, DvergrGuardTower, DvergrHarbour, DvergrLighthouse, PetrifiedBone") } }, { "[automatic_processing]", new List<Operation> { RemoveConfig("r/^([\\w_]+)_product_count_of_suppress_processing") } } }); } private static void MigrationFor145(List<string> lines) { Migration(lines, new Dictionary<string, List<Operation>> { { "[automatic_processing]", new List<Operation> { ReplaceValue("r/^allow_processing_by", "All", "Craft, Refuel, Store") } } }); } private static void MigrationFor160(List<string> lines) { Migration(lines, new Dictionary<string, List<Operation>> { { "[automatic_mapping]", new List<Operation> { AppendValues("allow_pinning_monster", new string[15] { "CharredMelee", "CharredArcher", "CharredMage", "CharredTwitcher", "CharredGrunt", "Morgen", "Volture", "BonemawSerpent", "FallenValkyrie", "Fader", "SkeletonFire", "FrostBlob", "Bear", "Vile", "LavaBlob" }), AppendValues("allow_pinning_animal", new string[2] { "Asksvin", "AsksvinHatchling" }), AppendValues("allow_pinning_flora", new string[4] { "Fiddlehead", "SmokePuff", "Vineberry", "VineberrySeeds" }), AppendValues("allow_pinning_mineral", new string[1] { "FlametalDeposit" }), AppendValues("allow_pinning_vehicle", new string[1] { "Drakkar" }), AppendValues("allow_pinning_spot", new string[3] { "CharredFortress", "LeviathanLava", "BogWitchCamp" }), AppendValues("allow_pinning_dungeon", new string[1] { "MorgenHole" }) } }, { "[automatic_mining]", new List<Operation> { AppendValues("allow_mining_mineral", new string[1] { "FlametalDeposit" }) } }, { "[automatic_door]", new List<Operation> { AppendValues("allow_automatic_door", new string[3] { "AshwoodDoor", "FlametalGate", "DvergrDoor" }) } }, { "[automatic_processing]", new List<Operation> { AppendValues("allow_container", new string[1] { "PieceChestCharred" }) } } }); } private static void Migration(List<string> lines, Dictionary<string, List<Operation>> operations) { string category = ""; int begin = -1; int i; List<Operation> value; for (i = 0; i < lines.Count; i++) { string text = lines[i]; if (text.StartsWith("#") || string.I