Decompiled source of GoldXpMultiplier v2.6.2
Mods/GoldXpMultiplier.dll
Decompiled 3 weeks 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.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using GoldXpMultiplier; using HarmonyLib; using Il2Cpp; using Il2CppInterop.Common; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Reflection; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using SpellBrigade.ModMenu; using SpellBrigade.Shared; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.Localization; using UnityEngine.Localization.Components; using UnityEngine.Localization.Settings; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(GoldXpMod), "Gold & XP Multiplier", "2.6.2", "Relsev", null)] [assembly: MelonGame("BoltBlasterGames", "TheSpellBrigade")] [assembly: MelonOptionalDependencies(new string[] { "ModMenu" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("GoldXpMultiplier")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.6.2.0")] [assembly: AssemblyInformationalVersion("2.6.2+1856b18f4bf94ea33a9b586b39e9ee6f647e5453")] [assembly: AssemblyProduct("GoldXpMultiplier")] [assembly: AssemblyTitle("GoldXpMultiplier")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.6.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SpellBrigade.Shared { internal static class Hooks { public static bool Patch(Harmony harmony, Instance log, Type target, string method, Type patchClass, string prefix = null, string postfix = null) { return Patch(harmony, log, AccessTools.Method(target, method, (Type[])null, (Type[])null), patchClass, prefix, postfix, target.Name + "." + method); } public static bool Patch(Harmony harmony, Instance log, MethodBase original, Type patchClass, string prefix = null, string postfix = null, string name = null) { //IL_011a: 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) if (name == null) { name = ((original != null) ? (original.DeclaringType?.Name + "." + original.Name) : ((prefix ?? postfix) + " target")); } try { if (original == null) { throw new MissingMethodException(name + " not found"); } List<string> list = SharedCodeGuard.FindMethodsSharingCode(original); if (list.Count > 0) { log.Warning($"skipped {name}: its native code is shared with {list.Count} other method(s), patching would break them (e.g. {string.Join(", ", list.GetRange(0, Math.Min(5, list.Count)))})"); return false; } harmony.Patch(original, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.Method(patchClass, prefix, (Type[])null, (Type[])null)), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.Method(patchClass, postfix, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception ex) { log.Error($"failed to hook {name} — this part is disabled. {ex.GetType().Name}: {ex.Message}"); return false; } } } internal sealed class Localizer { private readonly string[] _keys; private readonly Dictionary<string, string[]> _table; private readonly Func<Instance> _log; private readonly Dictionary<string, bool> _fontSupport = new Dictionary<string, bool>(); private readonly List<(TMP_Text text, Func<string> value)> _bound = new List<(TMP_Text, Func<string>)>(); private readonly List<Action> _listeners = new List<Action>(); private string[] _current; private string _code; private TMP_Text _fontSample; private bool _subscribed; public Localizer(string[] keys, Dictionary<string, string[]> table, Func<Instance> log) { _keys = keys; _table = table; _log = log; } public string Get(string key) { if (_current == null || _code != LocaleCode()) { _current = Resolve(_fontSample); } int num = Array.IndexOf(_keys, key); if (num < 0 || num >= _current.Length) { return key; } return _current[num]; } public void Bind(TMP_Text text, Func<string> value, TMP_Text fontSample = null) { if (!((Object)(object)text == (Object)null)) { EnsureSubscribed(); if (_current == null) { _current = Resolve(fontSample ?? text); } text.text = value(); _bound.Add((text, value)); } } public void Bind(TMP_Text text, string key, TMP_Text fontSample = null) { Bind(text, () => Get(key), fontSample); } public void OnLanguageChanged(Action listener) { EnsureSubscribed(); _listeners.Add(listener); } public void UseFont(TMP_Text sample) { _fontSample = sample; _current = null; EnsureSubscribed(); } private void EnsureSubscribed() { if (_subscribed) { return; } _subscribed = true; try { LocalizationSettings.SelectedLocaleChanged += Action<Locale>.op_Implicit((Action<Locale>)delegate { Refresh(); }); } catch (Exception ex) { Instance obj = _log(); if (obj != null) { obj.Warning("[lang] can't follow language changes: " + ex.Message); } } } private void Refresh() { _bound.RemoveAll(((TMP_Text text, Func<string> value) b) => (Object)(object)b.text == (Object)null); _current = Resolve((_bound.Count > 0) ? _bound[0].text : _fontSample); foreach (var (val, func) in _bound) { val.text = func(); } foreach (Action listener in _listeners) { try { listener(); } catch (Exception ex) { Instance obj = _log(); if (obj != null) { obj.Warning("[lang] " + ex.Message); } } } } private static string LocaleCode() { try { Locale selectedLocale = LocalizationSettings.SelectedLocale; return ((selectedLocale != null) ? selectedLocale.Identifier.Code : null) ?? "en"; } catch { return "en"; } } private string[] Resolve(TMP_Text fontSample) { string text = (_code = LocaleCode()); string text2 = TableKey(text); if (text2 != "en" && (Object)(object)fontSample != (Object)null && !FontHasAll(fontSample, text2)) { Instance obj = _log(); if (obj != null) { obj.Warning("[lang] font lacks glyphs for '" + text + "', using English"); } text2 = "en"; } return _table[text2]; } private string TableKey(string code) { code = code.ToLowerInvariant().Replace('_', '-'); string text = (code.StartsWith("zh") ? ((code.Contains("hant") || code.Contains("tw") || code.Contains("hk")) ? "zh-hant" : "zh-hans") : (code.StartsWith("pt") ? (code.Contains("br") ? "pt-br" : "pt") : ((!code.StartsWith("es")) ? code.Split('-')[0] : ((code == "es" || code.Contains("es-es")) ? "es" : "es-mx")))); if (_table.ContainsKey(text)) { return text; } string text2 = text.Split('-')[0]; if (!_table.ContainsKey(text2)) { return "en"; } return text2; } private bool FontHasAll(TMP_Text sample, string key) { if (_fontSupport.TryGetValue(key, out var value)) { return value; } value = true; try { TMP_FontAsset font = sample.font; if ((Object)(object)font != (Object)null) { string[] array = _table[key]; foreach (string text in array) { foreach (char c in text) { if (c > '\u007f' && !char.IsWhiteSpace(c) && !font.HasCharacter(c, true, true)) { value = false; break; } } if (!value) { break; } } } } catch { value = true; } _fontSupport[key] = value; return value; } } internal static class MatchmakingGuard { private const string BlockersKey = "SpellBrigade.Matchmaking.Blockers"; private const string OwnerKey = "SpellBrigade.Matchmaking.Owner"; private const string PanelName = "SpellBrigade_MatchmakingLock"; private static readonly Color Gold = new Color(0.96f, 0.78f, 0.35f); private static bool _owner; private static bool _wasBlocked; private static Instance _log; private static MatchmakingPreferencesModal _modal; private static TMP_Text _title; private static TMP_Text _body; private static float _pulse; private static float _nextCheck; private const string Title = "title"; private const string Intro = "intro"; private const string Footer = "footer"; private static readonly Localizer Text = new Localizer(new string[3] { "title", "intro", "footer" }, new Dictionary<string, string[]> { ["en"] = new string[3] { "Matchmaking is locked by mods", "These mods change the game for everyone in the lobby, so players without them can't be matched with you:", "Play with friends: invite them or share the lobby code. Matchmaking comes back as soon as nothing above applies." }, ["ru"] = new string[3] { "Подбор игроков заблокирован модами", "Эти моды меняют игру для всех в лобби, поэтому игроков без них к вам не подберёт:", "Играйте с друзьями: пригласите их или дайте код лобби. Подбор вернётся сам, как только ничего из списка не будет мешать." }, ["uk"] = new string[3] { "Підбір гравців заблоковано модами", "Ці моди змінюють гру для всіх у лобі, тому гравців без них до вас не підбере:", "Грайте з друзями: запросіть їх або дайте код лобі. Підбір повернеться сам, щойно нічого зі списку не заважатиме." }, ["de"] = new string[3] { "Matchmaking ist durch Mods gesperrt", "Diese Mods verändern das Spiel für alle in der Lobby, daher kannst du nicht mit Spielern ohne sie zusammengebracht werden:", "Spiele mit Freunden: Lade sie ein oder teile den Lobby-Code. Matchmaking kommt zurück, sobald nichts davon mehr zutrifft." }, ["fr"] = new string[3] { "Matchmaking bloqué par des mods", "Ces mods modifient la partie pour tout le salon, vous ne pouvez donc pas être associé à des joueurs qui ne les ont pas :", "Jouez avec des amis : invitez-les ou partagez le code du salon. Le matchmaking revient dès que plus rien ci-dessus ne s'applique." }, ["it"] = new string[3] { "Matchmaking bloccato dalle mod", "Queste mod cambiano la partita per tutta la lobby, quindi non puoi essere abbinato a chi non le ha:", "Gioca con gli amici: invitali o condividi il codice della lobby. Il matchmaking torna appena nulla di tutto ciò si applica." }, ["nl"] = new string[3] { "Matchmaking geblokkeerd door mods", "Deze mods veranderen het spel voor iedereen in de lobby, dus je wordt niet gekoppeld aan spelers zonder deze mods:", "Speel met vrienden: nodig ze uit of deel de lobbycode. Matchmaking komt terug zodra niets hiervan meer geldt." }, ["pl"] = new string[3] { "Dobieranie graczy zablokowane przez mody", "Te mody zmieniają grę wszystkim w lobby, więc gracze bez nich nie zostaną do ciebie dobrani:", "Graj ze znajomymi: zaproś ich lub podaj kod lobby. Dobieranie wróci samo, gdy nic z listy nie będzie przeszkadzać." }, ["pt-br"] = new string[3] { "Matchmaking bloqueado por mods", "Estes mods mudam o jogo para todos no lobby, então jogadores sem eles não serão pareados com você:", "Jogue com amigos: convide-os ou compartilhe o código do lobby. O matchmaking volta assim que nada acima se aplicar." }, ["pt"] = new string[3] { "Matchmaking bloqueado por mods", "Estes mods mudam o jogo para todos no lobby, por isso jogadores sem eles não serão emparelhados contigo:", "Joga com amigos: convida-os ou partilha o código do lobby. O matchmaking volta assim que nada acima se aplicar." }, ["es"] = new string[3] { "Emparejamiento bloqueado por mods", "Estos mods cambian la partida para toda la sala, así que no se te emparejará con jugadores que no los tengan:", "Juega con amigos: invítalos o comparte el código de sala. El emparejamiento vuelve en cuanto nada de lo anterior aplique." }, ["es-mx"] = new string[3] { "Emparejamiento bloqueado por mods", "Estos mods cambian la partida para toda la sala, así que no se te emparejará con jugadores que no los tengan:", "Juega con amigos: invítalos o comparte el código de sala. El emparejamiento regresa en cuanto nada de lo anterior aplique." }, ["ja"] = new string[3] { "MODによりマッチングがロックされています", "これらのMODはロビー全員のゲームを変えるため、MODのないプレイヤーとはマッチングしません:", "フレンドと遊びましょう:招待するかロビーコードを共有してください。上記が当てはまらなくなればマッチングは戻ります。" }, ["ko"] = new string[3] { "모드로 인해 매칭이 잠겼습니다", "이 모드들은 로비 전체의 게임을 바꾸므로, 모드가 없는 플레이어와는 매칭되지 않습니다:", "친구와 플레이하세요: 초대하거나 로비 코드를 공유하세요. 위 항목이 모두 해당되지 않으면 매칭이 다시 열립니다." }, ["zh-hans"] = new string[3] { "匹配已被模组锁定", "这些模组会改变大厅里所有人的游戏,因此不会为你匹配没有这些模组的玩家:", "和好友一起玩:邀请他们或分享大厅代码。只要上面各项都不再适用,匹配就会恢复。" }, ["zh-hant"] = new string[3] { "配對已被模組鎖定", "這些模組會改變大廳裡所有人的遊戲,因此不會為你配對沒有這些模組的玩家:", "和好友一起玩:邀請他們或分享大廳代碼。只要上面各項都不再適用,配對就會恢復。" }, ["th"] = new string[3] { "การจ\u0e31บค\u0e39\u0e48ถ\u0e39กล\u0e47อกโดยม\u0e47อด", "ม\u0e47อดเหล\u0e48าน\u0e35\u0e49เปล\u0e35\u0e48ยนเกมของท\u0e38กคนในล\u0e47อบบ\u0e35\u0e49 จ\u0e36งจะไม\u0e48จ\u0e31บค\u0e39\u0e48ค\u0e38ณก\u0e31บผ\u0e39\u0e49เล\u0e48นท\u0e35\u0e48ไม\u0e48ม\u0e35ม\u0e47อด:", "เล\u0e48นก\u0e31บเพ\u0e37\u0e48อน: เช\u0e34ญเพ\u0e37\u0e48อนหร\u0e37อแชร\u0e4cรห\u0e31สล\u0e47อบบ\u0e35\u0e49 การจ\u0e31บค\u0e39\u0e48จะกล\u0e31บมาเม\u0e37\u0e48อไม\u0e48ม\u0e35ข\u0e49อใดด\u0e49านบนเข\u0e49าเง\u0e37\u0e48อนไข" } }, () => _log); private static bool _prepareRestore; public static bool Blocked => Reasons().Count > 0; private static MatchmakingPreferencesManager Prefs => Singleton<MatchmakingPreferencesManager>.Instance; public static void Register(Harmony harmony, Instance log, string modName, Func<string> reason) { AppDomain currentDomain = AppDomain.CurrentDomain; List<Func<string>> list = currentDomain.GetData("SpellBrigade.Matchmaking.Blockers") as List<Func<string>>; if (list == null) { currentDomain.SetData("SpellBrigade.Matchmaking.Blockers", list = new List<Func<string>>()); } list.Add(delegate { string text = reason(); return (text == null) ? null : ("<b>" + modName + "</b> — " + text); }); object data = currentDomain.GetData("SpellBrigade.Matchmaking.Owner"); if (!(data is bool) || !(bool)data) { currentDomain.SetData("SpellBrigade.Matchmaking.Owner", true); _owner = true; _log = log; int num = 0; num += Hook(harmony, typeof(RunPreparationManager), "Prepare", "PreparePrefix", "PreparePostfix"); num += Hook(harmony, typeof(RunPreparationManager), "CanUseMatchmaking", "CanUsePrefix"); num += Hook(harmony, typeof(MatchmakingToggle), "Toggle", "TogglePrefix"); num += Hook(harmony, typeof(MatchmakingPreferencesModal), "OnIsMatchmakingEnabledChanged", "EnabledChangedPrefix"); num += Hook(harmony, typeof(MatchmakingPreferencesModal), "RefreshPanel", null, "RefreshPanelPostfix"); log.Msg($"matchmaking guard: {num}/5 hooks"); } } private static int Hook(Harmony harmony, Type type, string method, string prefix, string postfix = null) { return Hooks.Patch(harmony, _log, type, method, typeof(MatchmakingGuard), prefix, postfix) ? 1 : 0; } public static List<string> Reasons() { List<string> list = new List<string>(); if (!(AppDomain.CurrentDomain.GetData("SpellBrigade.Matchmaking.Blockers") is List<Func<string>> list2)) { return list; } foreach (Func<string> item in list2) { try { string text = item(); if (text != null) { list.Add(text); } } catch { } } return list; } private static void PreparePrefix() { List<string> list = Reasons(); MatchmakingPreferencesManager prefs = Prefs; if (list.Count != 0 && !((Object)(object)prefs == (Object)null) && prefs.IsMatchmakingEnabled) { prefs.IsMatchmakingEnabled = false; _prepareRestore = true; Instance log = _log; if (log != null) { log.Msg("run starts without matchmaking — " + StripTags(string.Join("; ", list))); } } } private static void PreparePostfix() { if (_prepareRestore) { _prepareRestore = false; MatchmakingPreferencesManager prefs = Prefs; if ((Object)(object)prefs != (Object)null) { prefs.IsMatchmakingEnabled = true; } } } private static bool CanUsePrefix(ref bool __result) { if (!Blocked) { return true; } __result = false; return false; } private static bool TogglePrefix(MatchmakingToggle __instance) { if (!Blocked || !IsMatchmakingToggle(__instance)) { return true; } _pulse = 1f; return false; } private static bool EnabledChangedPrefix(MatchmakingPreferencesModal __instance, bool isOn) { if (!isOn || !Blocked) { return true; } RefreshPanelPostfix(__instance); return false; } private static bool IsMatchmakingToggle(MatchmakingToggle toggle) { if ((Object)(object)_modal != (Object)null && (Object)(object)_modal.isMatchmakingEnabledToggle != (Object)null) { return ((Il2CppObjectBase)_modal.isMatchmakingEnabledToggle).Pointer == ((Il2CppObjectBase)toggle).Pointer; } return false; } private static void RefreshPanelPostfix(MatchmakingPreferencesModal __instance) { try { _modal = __instance; Apply(__instance, Reasons()); } catch (Exception ex) { Instance log = _log; if (log != null) { log.Warning("matchmaking window: " + ex.Message); } } } private static void Apply(MatchmakingPreferencesModal modal, List<string> reasons) { bool flag = reasons.Count > 0; MatchmakingToggle isMatchmakingEnabledToggle = modal.isMatchmakingEnabledToggle; if ((Object)(object)isMatchmakingEnabledToggle != (Object)null) { if (flag) { isMatchmakingEnabledToggle.isOn = false; isMatchmakingEnabledToggle.RefreshUI(); } (((Component)isMatchmakingEnabledToggle).GetComponent<CanvasGroup>() ?? ((Component)isMatchmakingEnabledToggle).gameObject.AddComponent<CanvasGroup>()).alpha = (flag ? 0.4f : 1f); } if (flag && (Object)(object)modal.matchmakingPreferencesObject != (Object)null) { modal.matchmakingPreferencesObject.SetActive(false); } Transform val = ((Component)modal).transform.Find("SpellBrigade_MatchmakingLock"); if ((Object)(object)val == (Object)null && flag) { val = BuildPanel(modal); } if ((Object)(object)val == (Object)null) { return; } ((Component)val).gameObject.SetActive(flag); if (!flag) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(Text.Get("intro")).Append('\n'); foreach (string reason in reasons) { stringBuilder.Append("\n• ").Append(reason); } stringBuilder.Append("\n\n<alpha=#B3>").Append(Text.Get("footer")); _title.text = Text.Get("title"); _body.text = stringBuilder.ToString(); } private static Transform BuildPanel(MatchmakingPreferencesModal modal) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) Transform obj = ((Component)modal).transform.Find("Matchmaking_Description"); TMP_Text val = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null) ?? ((Component)modal).GetComponentInChildren<TMP_Text>(true); if ((Object)(object)val == (Object)null) { return null; } RectTransform val2 = new GameObject("SpellBrigade_MatchmakingLock").AddComponent<RectTransform>(); ((Transform)val2).SetParent(((Component)modal).transform, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0f, 1f); val2.pivot = val3; Vector2 anchorMin = (val2.anchorMax = val3); val2.anchorMin = anchorMin; val2.anchoredPosition = new Vector2(250f, -1060f); val2.sizeDelta = new Vector2(2250f, 760f); float num = (val.enableAutoSizing ? val.fontSizeMax : val.fontSize); _title = CloneText(val, (Transform)(object)val2, "Title", num * 1.25f, Gold, (FontStyles)1); RectTransform rectTransform = _title.rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.pivot = new Vector2(0f, 1f); rectTransform.sizeDelta = new Vector2(0f, num * 1.8f); rectTransform.anchoredPosition = Vector2.zero; _body = CloneText(val, (Transform)(object)val2, "Body", num, Color.white, (FontStyles)0); RectTransform rectTransform2 = _body.rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; rectTransform2.pivot = new Vector2(0f, 1f); rectTransform2.offsetMin = Vector2.zero; rectTransform2.offsetMax = new Vector2(0f, (0f - num) * 2f); _body.textWrappingMode = (TextWrappingModes)1; _body.enableAutoSizing = true; _body.fontSizeMin = num * 0.6f; _body.fontSizeMax = num; return (Transform)(object)val2; } private static TMP_Text CloneText(TMP_Text template, Transform parent, string name, float size, Color color, FontStyles style) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) TMP_Text component = Object.Instantiate<GameObject>(((Component)template).gameObject, parent).GetComponent<TMP_Text>(); foreach (LocalizeStringEvent component2 in ((Component)component).GetComponents<LocalizeStringEvent>()) { Object.Destroy((Object)(object)component2); } foreach (MonoBehaviour component3 in ((Component)component).GetComponents<MonoBehaviour>()) { if ((Object)(object)component3 != (Object)null && (Object)(object)((Il2CppObjectBase)component3).TryCast<TMP_Text>() == (Object)null) { Object.Destroy((Object)(object)component3); } } ((Object)component).name = name; component.enableAutoSizing = false; component.fontSize = size; ((Graphic)component).color = color; component.fontStyle = style; component.alignment = (TextAlignmentOptions)257; component.textWrappingMode = (TextWrappingModes)0; component.overflowMode = (TextOverflowModes)0; ((Graphic)component).raycastTarget = false; return component; } private static string StripTags(string s) { return Regex.Replace(s, "<.*?>", ""); } public static void Tick() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (!_owner) { return; } if (_pulse > 0f && (Object)(object)_title != (Object)null) { _pulse = Mathf.Max(0f, _pulse - Time.unscaledDeltaTime * 2.5f); _title.transform.localScale = Vector3.one * (1f + 0.08f * _pulse); ((Graphic)_title).color = Color.Lerp(Gold, new Color(1f, 0.45f, 0.35f), _pulse); } if (Time.unscaledTime < _nextCheck) { return; } _nextCheck = Time.unscaledTime + 0.25f; List<string> list = Reasons(); bool flag = list.Count > 0; if (flag != _wasBlocked) { _wasBlocked = flag; Instance log = _log; if (log != null) { log.Msg(flag ? ("matchmaking locked — " + StripTags(string.Join("; ", list))) : "matchmaking unlocked"); } if ((Object)(object)_modal != (Object)null && ((Component)_modal).gameObject.activeInHierarchy) { try { _modal.RefreshPanel(); } catch { } } } else if (flag && (Object)(object)_modal != (Object)null && ((Component)_modal).gameObject.activeInHierarchy && (Object)(object)_body != (Object)null) { Apply(_modal, list); } } } internal static class SharedCodeGuard { private static Dictionary<IntPtr, List<IntPtr>> _methodsByCode; public static List<string> FindMethodsSharingCode(MethodBase generatedMethod) { List<string> list = new List<string>(); if (!(Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(generatedMethod)?.GetValue(null) is IntPtr intPtr) || intPtr == IntPtr.Zero) { return list; } IntPtr intPtr2 = Marshal.ReadIntPtr(intPtr); if (intPtr2 == IntPtr.Zero) { return list; } if (_methodsByCode == null) { _methodsByCode = BuildIndex(); } if (!_methodsByCode.TryGetValue(intPtr2, out var value)) { return list; } foreach (IntPtr item in value) { if (item != intPtr) { list.Add(Describe(item)); } } return list; } private static string Describe(IntPtr method) { IntPtr intPtr = IL2CPP.il2cpp_method_get_class(method); string text = IL2CPP.il2cpp_class_get_namespace_(intPtr); return (string.IsNullOrEmpty(text) ? IL2CPP.il2cpp_class_get_name_(intPtr) : (text + "." + IL2CPP.il2cpp_class_get_name_(intPtr))) + "::" + IL2CPP.il2cpp_method_get_name_(method); } private unsafe static Dictionary<IntPtr, List<IntPtr>> BuildIndex() { Dictionary<IntPtr, List<IntPtr>> dictionary = new Dictionary<IntPtr, List<IntPtr>>(); uint num = 0u; IntPtr* ptr = IL2CPP.il2cpp_domain_get_assemblies(IL2CPP.il2cpp_domain_get(), ref num); for (uint num2 = 0u; num2 < num; num2++) { IntPtr intPtr = IL2CPP.il2cpp_assembly_get_image(ptr[num2]); uint num3 = IL2CPP.il2cpp_image_get_class_count(intPtr); for (uint num4 = 0u; num4 < num3; num4++) { IntPtr intPtr2 = IL2CPP.il2cpp_image_get_class(intPtr, num4); if (intPtr2 == IntPtr.Zero) { continue; } IntPtr zero = IntPtr.Zero; IntPtr intPtr3; while ((intPtr3 = IL2CPP.il2cpp_class_get_methods(intPtr2, ref zero)) != IntPtr.Zero) { IntPtr intPtr4 = Marshal.ReadIntPtr(intPtr3); if (!(intPtr4 == IntPtr.Zero)) { if (!dictionary.TryGetValue(intPtr4, out var value)) { value = (dictionary[intPtr4] = new List<IntPtr>(1)); } value.Add(intPtr3); } } } } return dictionary; } } internal static class WheelSelect { private sealed class State { public ScrollRect Blocked; public float BlockedSensitivity; public Vector2 LastMouse = new Vector2(-1f, -1f); } private const string FrameKey = "SpellBrigade.WheelSelect.Frame"; private const string CustomKey = "SpellBrigade.WheelSelect.Custom"; private static readonly State Own = new State(); private static List<(GameObject target, Action<int> step)> Custom { get { AppDomain currentDomain = AppDomain.CurrentDomain; if (currentDomain.GetData("SpellBrigade.WheelSelect.Custom") is List<(GameObject, Action<int>)> result) { return result; } List<(GameObject, Action<int>)> list = new List<(GameObject, Action<int>)>(); currentDomain.SetData("SpellBrigade.WheelSelect.Custom", list); return list; } } public static void Register(GameObject target, Action<int> step) { if ((Object)(object)target != (Object)null && step != null) { Custom.Add((target, step)); } } public static void Tick() { try { TickCore(); } catch { } } private static void TickCore() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) int frameCount = Time.frameCount; AppDomain currentDomain = AppDomain.CurrentDomain; if (currentDomain.GetData("SpellBrigade.WheelSelect.Frame") is int num && num == frameCount) { return; } currentDomain.SetData("SpellBrigade.WheelSelect.Frame", frameCount); object data = currentDomain.GetData("SpellBrigade.Modal"); if (data is bool && (bool)data) { return; } Mouse current = Mouse.current; if (current == null) { return; } State own = Own; Vector2 val = ((InputControl<Vector2>)(object)((Pointer)current).position).ReadValue(); float y = ((InputControl<Vector2>)(object)current.scroll).ReadValue().y; Vector2 val2 = val - own.LastMouse; if (!(((Vector2)(ref val2)).sqrMagnitude > 0.25f) && Mathf.Abs(y) < 0.01f) { return; } own.LastMouse = val; var (val3, action, val4) = Hovered(val); BlockScroll(own, ((Object)(object)val4 != (Object)null) ? ScrollRectAbove(val4) : null); if (Mathf.Abs(y) < 0.01f || (Object)(object)val4 == (Object)null) { return; } int num2 = ((y > 0f) ? 1 : (-1)); try { if ((Object)(object)val3 != (Object)null) { if (num2 > 0) { val3.SelectNext(); } else { val3.SelectPrevious(); } } else { action?.Invoke(num2); } } catch { } } private static (Selector selector, Action<int> custom, RectTransform rect) Hovered(Vector2 position) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) List<(GameObject target, Action<int> step)> custom = Custom; custom.RemoveAll(((GameObject target, Action<int> step) c) => (Object)(object)c.target == (Object)null); foreach (var (val, item) in custom) { if (val.activeInHierarchy && Contains(val.GetComponent<RectTransform>(), position)) { return (selector: null, custom: item, rect: val.GetComponent<RectTransform>()); } } foreach (Selector item2 in Object.FindObjectsOfType<Selector>()) { if (!((Object)(object)item2 == (Object)null) && ((Selectable)item2).IsInteractable()) { RectTransform component = ((Component)item2).GetComponent<RectTransform>(); if (Contains(component, position)) { return (selector: item2, custom: null, rect: component); } } } return (selector: null, custom: null, rect: null); } private static bool Contains(RectTransform rect, Vector2 position) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)rect == (Object)null) { return false; } Canvas val = null; Transform val2 = ((Component)rect).transform; while ((Object)(object)val2 != (Object)null) { CanvasGroup component = ((Component)val2).GetComponent<CanvasGroup>(); if ((Object)(object)component != (Object)null && (component.alpha < 0.01f || !component.blocksRaycasts)) { return false; } Canvas component2 = ((Component)val2).GetComponent<Canvas>(); if ((Object)(object)component2 != (Object)null) { val = component2; } val2 = val2.parent; } if ((Object)(object)val == (Object)null) { return false; } Camera val3 = (((int)val.renderMode == 0) ? null : val.worldCamera); if (!RectTransformUtility.RectangleContainsScreenPoint(rect, position, val3)) { return false; } Transform parent = ((Transform)rect).parent; while ((Object)(object)parent != (Object)null) { if ((Object)(object)((Component)parent).GetComponent<RectMask2D>() != (Object)null && !RectTransformUtility.RectangleContainsScreenPoint(((Component)parent).GetComponent<RectTransform>(), position, val3)) { return false; } parent = parent.parent; } return true; } private static ScrollRect ScrollRectAbove(RectTransform rect) { Transform parent = ((Transform)rect).parent; while ((Object)(object)parent != (Object)null) { ScrollRect component = ((Component)parent).GetComponent<ScrollRect>(); if ((Object)(object)component != (Object)null) { return component; } parent = parent.parent; } return null; } private static void BlockScroll(State state, ScrollRect scroll) { if (!((Object)(object)state.Blocked != (Object)null) || !((Object)(object)scroll != (Object)null) || !(((Il2CppObjectBase)state.Blocked).Pointer == ((Il2CppObjectBase)scroll).Pointer)) { if ((Object)(object)state.Blocked != (Object)null) { state.Blocked.scrollSensitivity = state.BlockedSensitivity; } state.Blocked = scroll; if (!((Object)(object)scroll == (Object)null)) { state.BlockedSensitivity = scroll.scrollSensitivity; scroll.scrollSensitivity = 0f; } } } } } namespace GoldXpMultiplier { internal static class GoldPatch { public static void Postfix(EarnedGold __result) { if (!GoldXpMod.Enabled || __result == null) { return; } float mult = GoldXpMod.GoldMultiplier; if (mult != 1f) { int totalGold = __result.TotalGold; int[] array = Read(__result); Write(__result, Array.ConvertAll(array, (int v) => Scale(v, mult))); RunSummaryUi.RememberGold(__result, array, mult, totalGold); } } public static int[] Read(EarnedGold g) { return new int[6] { g._BaseGold_k__BackingField, g._BonusGoldFromFinalUpgrade_k__BackingField, g._BonusGoldFromGoldGainStat_k__BackingField, g._BonusGoldFromCurses_k__BackingField, g._BonusGoldFromDifficulty_k__BackingField, g._BonusGoldFromUnusedCharacter_k__BackingField }; } private static void Write(EarnedGold g, int[] v) { g._BaseGold_k__BackingField = v[0]; g._BonusGoldFromFinalUpgrade_k__BackingField = v[1]; g._BonusGoldFromGoldGainStat_k__BackingField = v[2]; g._BonusGoldFromCurses_k__BackingField = v[3]; g._BonusGoldFromDifficulty_k__BackingField = v[4]; g._BonusGoldFromUnusedCharacter_k__BackingField = v[5]; } public static EarnedGold Create(int[] values, EarnedGold percentagesFrom) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_000c: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown EarnedGold val = new EarnedGold(); Write(val, values); val._BonusGoldPercentageFromCurses_k__BackingField = percentagesFrom._BonusGoldPercentageFromCurses_k__BackingField; val._BonusGoldPercentageFromDifficulty_k__BackingField = percentagesFrom._BonusGoldPercentageFromDifficulty_k__BackingField; val._BonusGoldPercentageFromUnusedCharacter_k__BackingField = percentagesFrom._BonusGoldPercentageFromUnusedCharacter_k__BackingField; return val; } private static int Scale(int value, float mult) { if (value <= 0) { return value; } double num = Math.Round((double)value * (double)mult); if (!(num >= 2147483647.0)) { return (int)num; } return int.MaxValue; } } internal static class XpPatch { public static void Prefix(PartyLevelManager __instance, ref float experience) { if (!(experience <= 0f)) { float num = (GoldXpMod.Enabled ? GoldXpMod.XpMultiplier : 1f); float vanilla = experience; if (num != 1f) { experience *= num; } RunSummaryUi.TrackXp(__instance, vanilla, experience); } } } internal static class ImpactFx { private sealed class Spark { public RectTransform Rect; public Image Image; public Vector2 Velocity; public float Spin; } private const float FallDuration = 0.42f; private const float ImpactDuration = 0.55f; private static readonly Color SparkColor = new Color(1f, 0.84f, 0.3f, 1f); private static Sprite _round; public static IEnumerator Slam(TMP_Text target, TMP_Text style, string label, string newText, Transform shakeRoot, Action onImpact) { if ((Object)(object)target == (Object)null) { yield break; } object obj; if (!((Object)(object)((Graphic)target).canvas != (Object)null)) { obj = null; } else { Canvas rootCanvas = ((Graphic)target).canvas.rootCanvas; obj = ((rootCanvas != null) ? ((Component)rootCanvas).GetComponent<RectTransform>() : null); } RectTransform root = (RectTransform)obj; if ((Object)(object)root == (Object)null) { target.text = newText; onImpact?.Invoke(); yield break; } target.ForceMeshUpdate(false, false); Transform transform = target.transform; Bounds textBounds = target.textBounds; Vector3 end = ((Transform)root).InverseTransformPoint(transform.TransformPoint(((Bounds)(ref textBounds)).center)); Rect rect = root.rect; Vector3 start = end + new Vector3(0f, ((Rect)(ref rect)).height * 0.6f, 0f); TMP_Text slam = CreateLabel(root, style ?? target, label); RectTransform slamRect = slam.rectTransform; float t0 = Time.unscaledTime; while (true) { if ((Object)(object)target == (Object)null || (Object)(object)slam == (Object)null) { Cleanup(slam, null); yield break; } float num = Mathf.Clamp01((Time.unscaledTime - t0) / 0.42f); float num2 = num * num * num; ((Transform)slamRect).localPosition = Vector3.LerpUnclamped(start, end, num2); float num3 = Mathf.Lerp(2.4f, 1f, num2); ((Transform)slamRect).localScale = new Vector3(num3 * 0.85f, num3 * 1.15f, 1f); ((Transform)slamRect).localRotation = Quaternion.Euler(0f, 0f, Mathf.Lerp(-16f, 0f, num2)); slam.alpha = Mathf.Clamp01(num * 5f); if (num >= 1f) { break; } yield return null; } target.text = newText; try { onImpact?.Invoke(); } catch (Exception ex) { GoldXpMod.Log.Warning("[fx] " + ex.Message); } List<Spark> sparks = SpawnSparks(root, end, 26); Image flash = SpawnFlash(root, end); Vector3 targetScale = target.transform.localScale; Vector3 shakeBase = (((Object)(object)shakeRoot != (Object)null) ? shakeRoot.localPosition : Vector3.zero); t0 = Time.unscaledTime; float last = t0; while (true) { float unscaledTime = Time.unscaledTime; float num4 = unscaledTime - last; last = unscaledTime; float num5 = Mathf.Clamp01((unscaledTime - t0) / 0.55f); if ((Object)(object)target != (Object)null) { target.transform.localScale = targetScale * (1f + 0.55f * Punch(num5)); } if ((Object)(object)shakeRoot != (Object)null) { float num6 = 22f * (1f - num5) * (1f - num5); shakeRoot.localPosition = shakeBase + new Vector3(Random.Range(0f - num6, num6), Random.Range(0f - num6, num6), 0f); } if ((Object)(object)slam != (Object)null) { float num7 = Mathf.Exp(-9f * num5); ((Transform)slamRect).localScale = new Vector3(1f + 0.6f * num7 + 0.25f * num5, 1f - 0.45f * num7 + 0.25f * num5, 1f); ((Transform)slamRect).localPosition = end + new Vector3(0f, 70f * Mathf.Sin(num5 * (float)Math.PI * 0.5f), 0f); slam.alpha = 1f - num5 * num5; } if ((Object)(object)flash != (Object)null) { float num8 = 1f - Mathf.Pow(1f - num5, 3f); ((Transform)((Graphic)flash).rectTransform).localScale = Vector3.one * Mathf.Lerp(0.2f, 3.2f, num8); Color color = ((Graphic)flash).color; color.a = 0.8f * (1f - num8); ((Graphic)flash).color = color; } foreach (Spark item in sparks) { if (!((Object)(object)item.Rect == (Object)null)) { item.Velocity += new Vector2(0f, -1800f) * num4; RectTransform rect2 = item.Rect; ((Transform)rect2).localPosition = ((Transform)rect2).localPosition + Vector2.op_Implicit(item.Velocity * num4); ((Transform)item.Rect).localRotation = Quaternion.Euler(0f, 0f, ((Transform)item.Rect).localEulerAngles.z + item.Spin * num4); Color color2 = ((Graphic)item.Image).color; color2.a = 1f - num5; ((Graphic)item.Image).color = color2; ((Transform)item.Rect).localScale = Vector3.one * (1f - 0.6f * num5); } } if (num5 >= 1f) { break; } yield return null; } if ((Object)(object)target != (Object)null) { target.transform.localScale = targetScale; } if ((Object)(object)shakeRoot != (Object)null) { shakeRoot.localPosition = shakeBase; } Cleanup(slam, sparks); if ((Object)(object)flash != (Object)null) { Object.Destroy((Object)(object)((Component)flash).gameObject); } } private static float Punch(float k) { return Mathf.Exp(-6f * k) * Mathf.Sin(k * (float)Math.PI * 3.5f + 0.35f) * ((k < 0.02f) ? (k / 0.02f) : 1f); } private static TMP_Text CreateLabel(RectTransform root, TMP_Text style, string label) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("SBMod_Slam"); val.AddComponent<RectTransform>(); val.transform.SetParent((Transform)(object)root, false); val.transform.SetAsLastSibling(); TextMeshProUGUI obj = val.AddComponent<TextMeshProUGUI>(); ((TMP_Text)obj).font = style.font; ((TMP_Text)obj).fontSharedMaterial = style.fontSharedMaterial; ((TMP_Text)obj).fontStyle = style.fontStyle; ((Graphic)obj).color = ((Graphic)style).color; ((TMP_Text)obj).enableVertexGradient = style.enableVertexGradient; ((TMP_Text)obj).colorGradient = style.colorGradient; ((TMP_Text)obj).fontSize = style.fontSize * 1.5f; ((TMP_Text)obj).alignment = (TextAlignmentOptions)514; ((TMP_Text)obj).textWrappingMode = (TextWrappingModes)0; ((Graphic)obj).raycastTarget = false; ((TMP_Text)obj).text = label; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(800f, 400f); return (TMP_Text)(object)obj; } private static List<Spark> SpawnSparks(RectTransform root, Vector3 at, int count) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) List<Spark> list = new List<Spark>(); for (int i = 0; i < count; i++) { GameObject val = new GameObject("SBMod_Spark"); RectTransform val2 = val.AddComponent<RectTransform>(); val.transform.SetParent((Transform)(object)root, false); val.transform.SetAsLastSibling(); Image val3 = val.AddComponent<Image>(); val3.sprite = RoundSprite(); ((Graphic)val3).color = ((i % 3 == 0) ? Color.white : SparkColor); ((Graphic)val3).raycastTarget = false; float num = Random.Range(22f, 48f); val2.sizeDelta = new Vector2(num, num); ((Transform)val2).localPosition = at; ((Transform)val2).localRotation = Quaternion.Euler(0f, 0f, 45f); float num2 = Random.Range(10f, 170f) * ((float)Math.PI / 180f); float num3 = Random.Range(500f, 1100f); list.Add(new Spark { Rect = val2, Image = val3, Velocity = new Vector2(Mathf.Cos(num2), Mathf.Sin(num2)) * num3, Spin = Random.Range(-720f, 720f) }); } return list; } private static Image SpawnFlash(RectTransform root, Vector3 at) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) Sprite val = RoundSprite(); if ((Object)(object)val == (Object)null) { return null; } GameObject val2 = new GameObject("SBMod_Flash"); RectTransform val3 = val2.AddComponent<RectTransform>(); val2.transform.SetParent((Transform)(object)root, false); val2.transform.SetAsLastSibling(); Image obj = val2.AddComponent<Image>(); obj.sprite = val; ((Graphic)obj).color = new Color(1f, 0.9f, 0.55f, 0.8f); ((Graphic)obj).raycastTarget = false; val3.sizeDelta = new Vector2(320f, 320f); ((Transform)val3).localPosition = at; return obj; } private static Sprite RoundSprite() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_round != (Object)null) { return _round; } try { Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false); Il2CppStructArray<Color32> val2 = new Il2CppStructArray<Color32>(4096L); float num = 31.5f; for (int i = 0; i < 64; i++) { for (int j = 0; j < 64; j++) { float num2 = Mathf.Sqrt(((float)j - num) * ((float)j - num) + ((float)i - num) * ((float)i - num)) / num; float num3 = Mathf.Clamp01(1f - num2); num3 = num3 * num3 * (3f - 2f * num3); ((Il2CppArrayBase<Color32>)(object)val2)[i * 64 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(num3 * 255f)); } } val.SetPixels32(val2); val.Apply(false, true); ((Object)val).hideFlags = (HideFlags)61; _round = Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f)); ((Object)_round).hideFlags = (HideFlags)61; } catch (Exception ex) { GoldXpMod.Log.Warning("[fx] no round sprite: " + ex.Message); } return _round; } private static void Cleanup(TMP_Text slam, List<Spark> sparks) { if ((Object)(object)slam != (Object)null) { Object.Destroy((Object)(object)((Component)slam).gameObject); } if (sparks == null) { return; } foreach (Spark spark in sparks) { if ((Object)(object)spark.Rect != (Object)null) { Object.Destroy((Object)(object)((Component)spark.Rect).gameObject); } } } } internal static class MenuPage { [MethodImpl(MethodImplOptions.NoInlining)] public static void Register() { Menu.AddPage("GoldMultiplier", "Gold & XP Multiplier").Toggle((Func<string>)(() => Strings.Get("enabled")), (Func<bool>)(() => GoldXpMod._enabledEntry.Value), (Action<bool>)delegate(bool v) { Store(GoldXpMod._enabledEntry, v); }, (bool?)GoldXpMod._enabledEntry.DefaultValue).Number((Func<string>)(() => Strings.Get("gold")), (IEnumerable<float>)SettingsTab.Presets, (Func<float>)(() => GoldXpMod._goldEntry.Value), (Action<float>)delegate(float v) { Store(GoldXpMod._goldEntry, v); }, (Func<float, string>)Format, (float?)GoldXpMod._goldEntry.DefaultValue) .Number((Func<string>)(() => Strings.Get("xp")), (IEnumerable<float>)SettingsTab.Presets, (Func<float>)(() => GoldXpMod._xpEntry.Value), (Action<float>)delegate(float v) { Store(GoldXpMod._xpEntry, v); }, (Func<float, string>)Format, (float?)GoldXpMod._xpEntry.DefaultValue) .Number((Func<string>)(() => Strings.Get("rank")), (IEnumerable<float>)SettingsTab.Presets, (Func<float>)(() => GoldXpMod._rankXpEntry.Value), (Action<float>)delegate(float v) { Store(GoldXpMod._rankXpEntry, v); }, (Func<float, string>)Format, (float?)GoldXpMod._rankXpEntry.DefaultValue) .Toggle((Func<string>)(() => Strings.Get("reveal")), (Func<bool>)(() => GoldXpMod._revealEntry.Value), (Action<bool>)delegate(bool v) { Store(GoldXpMod._revealEntry, v); }, (bool?)GoldXpMod._revealEntry.DefaultValue) .Note((Func<string>)(() => Strings.Get("hint"))); } private static string Format(float v) { return "x" + v.ToString("0.##", CultureInfo.InvariantCulture); } private static void Store<T>(MelonPreferences_Entry<T> entry, T value) { if (!object.Equals(entry.Value, value)) { entry.Value = value; GoldXpMod.SaveSettings(); } } } public class GoldXpMod : MelonMod { public static Instance Log; private static MelonPreferences_Category _category; internal static MelonPreferences_Entry<bool> _enabledEntry; internal static MelonPreferences_Entry<float> _goldEntry; internal static MelonPreferences_Entry<float> _xpEntry; internal static MelonPreferences_Entry<float> _rankXpEntry; internal static MelonPreferences_Entry<bool> _revealEntry; private int _hooksInstalled; private int _hooksTotal; public static bool Enabled => _enabledEntry?.Value ?? true; public static float GoldMultiplier => Sanitize(_goldEntry?.Value); public static float XpMultiplier => Sanitize(_xpEntry?.Value); public static float RankXpMultiplier => Sanitize(_rankXpEntry?.Value); public static bool Reveal => _revealEntry?.Value ?? true; public override void OnInitializeMelon() { Log = ((MelonBase)this).LoggerInstance; ValueRange<float> val = new ValueRange<float>(0f, 1000f); _category = MelonPreferences.CreateCategory("GoldMultiplier", "Gold & XP Multiplier"); _enabledEntry = _category.CreateEntry<bool>("Enabled", true, "Enabled", "Включён ли мод", false, false, (ValueValidator)null, (string)null); _goldEntry = _category.CreateEntry<float>("Multiplier", 2f, "Gold Multiplier", "Множитель золота за забег (1.0 = ваниль). Умножается и база, и все бонусы", false, false, (ValueValidator)(object)val, (string)null); _xpEntry = _category.CreateEntry<float>("XpMultiplier", 2f, "Run XP Multiplier", "Множитель опыта в забеге (1.0 = ваниль). В коопе работает, если мод стоит у хоста", false, false, (ValueValidator)(object)val, (string)null); _rankXpEntry = _category.CreateEntry<float>("RankXpMultiplier", 1f, "Wizard Rank XP Multiplier", "Множитель опыта ранга мага за забег (1.0 = ваниль)", false, false, (ValueValidator)(object)val, (string)null); _revealEntry = _category.CreateEntry<bool>("Reveal", true, "End-of-run x2 Reveal", "Экран итогов: сначала ванильные числа, потом удар множителя. false — сразу итоговые числа", false, false, (ValueValidator)null, (string)null); _category.SaveToFile(false); WarnAboutOldVersion(); ApplyPatches(); MatchmakingGuard.Register(((MelonBase)this).HarmonyInstance, Log, "Gold & XP Multiplier", () => (!Enabled || XpMultiplier == 1f) ? null : string.Format(Strings.Get("mm"), "x" + XpMultiplier.ToString("0.##", CultureInfo.InvariantCulture))); Log.Msg($"{_hooksInstalled}/{_hooksTotal} hooks — gold x{GoldMultiplier}, run xp x{XpMultiplier}, rank xp x{RankXpMultiplier}{(Enabled ? "" : " (disabled)")}. Settings: Options → Multipliers"); } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { if (sceneName == "StartMenu") { MelonCoroutines.Start(RepairSaveLater()); } } private static IEnumerator RepairSaveLater() { yield return (object)new WaitForSeconds(2f); try { SaveDoctor.RepairCorruptedRanks(); } catch (Exception value) { Log.Error($"rank repair failed: {value}"); } } internal static void SaveSettings() { _category.SaveToFile(false); } public override void OnUpdate() { WheelSelect.Tick(); MatchmakingGuard.Tick(); } private static float Sanitize(float? value) { float num = value ?? 1f; if (!float.IsNaN(num) && !float.IsInfinity(num) && !(num < 0f)) { return num; } return 1f; } private static void WarnAboutOldVersion() { if (MelonBase.RegisteredMelons.Any((MelonBase m) => m.Info.Name == "SpellBrigadeGoldMultiplier")) { Log.Warning("Old version found: delete Mods\\SpellBrigadeGoldMultiplier.dll, otherwise rewards are multiplied twice"); } } private void ApplyPatches() { Patch(typeof(RunGoldCalculator), "Calculate", typeof(GoldPatch), null, "Postfix"); Patch(typeof(PartyLevelManager), "TryAddExperience", typeof(XpPatch), "Prefix"); Patch(typeof(CharacterRankProgression), "SetRankProgression", typeof(RankXp), "SetRankProgressionPrefix"); Patch(typeof(RunRecordFactory), "CalculateRankProgression", typeof(RankXp), null, "CalculateRankProgressionPostfix"); Patch(typeof(GameOverMissionStatsPanel), "SetValues", typeof(RunSummaryUi), null, "SetValuesPostfix"); Patch(typeof(GameOverMissionStatsPanel), "SetXPEarned", typeof(RunSummaryUi), "SetXpEarnedPrefix"); bool flag = false; if (MelonBase.FindMelon("Mod Menu", "Relsev") != null) { try { MenuPage.Register(); flag = true; } catch (Exception ex) { Log.Warning("Mod Menu page: " + ex.Message + " — using own settings tab"); } } if (!flag) { Patch(typeof(SettingsPanel), "SetupSubPanels", typeof(SettingsTab), "InjectPrefix", "LinkTabButtonsPostfix"); } } private void Patch(Type target, string method, Type patchClass, string prefix = null, string postfix = null) { _hooksTotal++; string text = target.Name + "." + method; MethodInfo methodInfo = AccessTools.Method(target, method, (Type[])null, (Type[])null); if (methodInfo != null && ReturnsStruct(methodInfo)) { Log.Warning($"skipped {text}: it returns a struct ({methodInfo.ReturnType.Name}), hooking it corrupts the result"); } else if (Hooks.Patch(((MelonBase)this).HarmonyInstance, Log, methodInfo, patchClass, prefix, postfix, text)) { _hooksInstalled++; } } private static bool ReturnsStruct(MethodInfo method) { Type returnType = method.ReturnType; if (returnType == typeof(void) || returnType.IsPrimitive || returnType.IsEnum) { return false; } if (!returnType.IsValueType) { return typeof(ValueType).IsAssignableFrom(returnType); } return true; } } internal static class RankXp { [ThreadStatic] public static bool Suspended; private static (int r0, float p0, int r, float p) _lastScaled = (r0: -1, p0: 0f, r: -1, p: 0f); public static void SetRankProgressionPrefix(CharacterRankProgression __instance, CharacterId characterId, ref int rank, ref float rankProgress) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) try { if (Suspended || !GoldXpMod.Enabled) { return; } float rankXpMultiplier = GoldXpMod.RankXpMultiplier; if (rankXpMultiplier == 1f) { return; } CharacterRankProgress orCreateProgressForCharacter = __instance.GetOrCreateProgressForCharacter(characterId); if (orCreateProgressForCharacter != null) { int currentRank = orCreateProgressForCharacter.CurrentRank; float progressTowardsNextRank = orCreateProgressForCharacter.ProgressTowardsNextRank; if ((_lastScaled.r0 != currentRank || _lastScaled.p0 != progressTowardsNextRank || _lastScaled.r != rank || _lastScaled.p != rankProgress) && TryScale(__instance.rankCalculator, orCreateProgressForCharacter.Prestige, currentRank, progressTowardsNextRank, rank, rankProgress, rankXpMultiplier, out var rank2, out var progress)) { rank = rank2; rankProgress = progress; } } } catch (Exception value) { GoldXpMod.Log.Error($"[rank xp] {value}"); } } public static void CalculateRankProgressionPostfix(RankProgression __result, int prestige) { try { if (__result == (RankProgression)null || !GoldXpMod.Enabled) { return; } float rankXpMultiplier = GoldXpMod.RankXpMultiplier; if (rankXpMultiplier != 1f) { LocalPlayer instance = SingletonPersistent<LocalPlayer>.Instance; object calc; if (instance == null) { calc = null; } else { CharacterRankProgression characterRankManager = instance.GetCharacterRankManager(); calc = ((characterRankManager != null) ? characterRankManager.rankCalculator : null); } int startRank = __result.StartRank; int endRank = __result.EndRank; float startRankProgress = __result.StartRankProgress; float endRankProgress = __result.EndRankProgress; if (TryScale((CharacterRankCalculator)calc, prestige, startRank, startRankProgress, endRank, endRankProgress, rankXpMultiplier, out var rank, out var progress)) { __result._EndRank_k__BackingField = rank; __result._EndRankProgress_k__BackingField = progress; _lastScaled = (r0: startRank, p0: startRankProgress, r: rank, p: progress); } } } catch (Exception value) { GoldXpMod.Log.Error($"[rank xp] {value}"); } } private static bool TryScale(CharacterRankCalculator calc, int prestige, int r0, float p0, int r1, float p1, float mult, out int rank, out float progress) { rank = r1; progress = p1; if (calc == null || mult == 1f) { return false; } if (!Sane(r0, p0) || !Sane(r1, p1)) { return false; } if (r1 < r0 || (r1 == r0 && p1 <= p0)) { return false; } if (r1 - r0 > 1000 || calc.IsMaxRank(r0) || calc.IsMaxRank(r1)) { return false; } double num; if (r1 == r0) { num = (double)(p1 - p0) * Minutes(calc, r0, prestige); } else { num = (double)(1f - p0) * Minutes(calc, r0, prestige); for (int i = r0 + 1; i < r1; i++) { num += Minutes(calc, i, prestige); } num += (double)p1 * Minutes(calc, r1, prestige); } if (!(num > 0.0) || double.IsInfinity(num)) { return false; } double num2 = num * (double)mult; int num3 = r0; double num4 = p0; while (true) { if (calc.IsMaxRank(num3)) { num4 = 0.0; break; } double num5 = Minutes(calc, num3, prestige); if (!(num5 > 0.0) || double.IsInfinity(num5)) { return false; } double num6 = (1.0 - num4) * num5; if (num2 < num6) { num4 += num2 / num5; break; } num2 -= num6; num3++; num4 = 0.0; if (num3 - r0 > 10000) { return false; } } rank = num3; progress = (float)Math.Min(num4, 0.9999); return Sane(rank, progress); } private static double Minutes(CharacterRankCalculator calc, int rank, int prestige) { return calc.CalculateMinutesRequiredForNextRank(rank, prestige); } private static bool Sane(int rank, float progress) { if (rank >= 0 && rank <= 1000 && !float.IsNaN(progress) && progress >= 0f) { return progress <= 1f; } return false; } } internal static class RunSummaryUi { private sealed class GoldSnapshot { public int[] Vanilla; public int[] Modded; public float Mult; public int VanillaTotal; public int ModdedTotal; } private sealed class Pending { public EarnedGoldUI GoldUi; public EarnedGold ModdedGold; public GoldSnapshot Gold; public TMP_Text XpText; public float XpVanilla; public float XpModded; public float XpMult; public Transform ShakeRoot; } private const string BonusPanelName = "SBMod_GoldBonus"; private static readonly Dictionary<IntPtr, GoldSnapshot> Snapshots = new Dictionary<IntPtr, GoldSnapshot>(); private static readonly List<GoldSnapshot> RecentSnapshots = new List<GoldSnapshot>(); private static IntPtr _xpRunId; private static double _xpVanilla; private static double _xpModded; private static Pending _pending; private static bool _sequenceQueued; public static void RememberGold(EarnedGold modded, int[] vanilla, float mult, int vanillaTotal) { GoldSnapshot goldSnapshot = new GoldSnapshot { Vanilla = vanilla, Modded = GoldPatch.Read(modded), Mult = mult, VanillaTotal = vanillaTotal, ModdedTotal = modded.TotalGold }; if (Snapshots.Count > 64) { Snapshots.Clear(); } Snapshots[((Il2CppObjectBase)modded).Pointer] = goldSnapshot; RecentSnapshots.Add(goldSnapshot); if (RecentSnapshots.Count > 16) { RecentSnapshots.RemoveAt(0); } } private static GoldSnapshot FindSnapshot(EarnedGold gold) { if (Snapshots.TryGetValue(((Il2CppObjectBase)gold).Pointer, out var value)) { return value; } int[] b = GoldPatch.Read(gold); for (int num = RecentSnapshots.Count - 1; num >= 0; num--) { if (Same(RecentSnapshots[num].Modded, b)) { return RecentSnapshots[num]; } } return null; } private static bool Same(int[] a, int[] b) { for (int i = 0; i < a.Length; i++) { if (a[i] != b[i]) { return false; } } return true; } public static void TrackXp(PartyLevelManager manager, float vanilla, float modded) { if (!((Object)(object)manager == (Object)null)) { if (((Il2CppObjectBase)manager).Pointer != _xpRunId) { _xpRunId = ((Il2CppObjectBase)manager).Pointer; _xpVanilla = 0.0; _xpModded = 0.0; } _xpVanilla += vanilla; _xpModded += modded; } } public static void SetValuesPostfix(GameOverMissionStatsPanel __instance) { try { if (!GoldXpMod.Reveal) { return; } EarnedGoldUI earnedGoldUI = __instance.earnedGoldUI; EarnedGold val = ((earnedGoldUI != null) ? earnedGoldUI.earnedGold : null); if (val != null) { GoldSnapshot goldSnapshot = FindSnapshot(val); if (goldSnapshot != null && goldSnapshot.Mult != 1f && goldSnapshot.VanillaTotal != goldSnapshot.ModdedTotal) { Queue().GoldUi = earnedGoldUI; _pending.ShakeRoot = ((Component)__instance).transform; _pending.ModdedGold = val; _pending.Gold = goldSnapshot; earnedGoldUI.earnedGold = GoldPatch.Create(goldSnapshot.Vanilla, val); } } } catch (Exception value) { GoldXpMod.Log.Error($"[summary] gold: {value}"); } } public static void SetXpEarnedPrefix(GameOverMissionStatsPanel __instance, ref float xpEarned) { try { if (GoldXpMod.Reveal && _xpModded > 0.0 && _xpVanilla > 0.0 && !(Math.Abs(_xpModded - _xpVanilla) < 0.5)) { float num = (float)((double)xpEarned * (_xpVanilla / _xpModded)); if (Math.Round(num) != Math.Round(xpEarned)) { Queue().XpText = __instance.xpEarnedValue; _pending.ShakeRoot = ((Component)__instance).transform; _pending.XpMult = GoldXpMod.XpMultiplier; _pending.XpVanilla = num; _pending.XpModded = xpEarned; xpEarned = num; } } } catch (Exception value) { GoldXpMod.Log.Error($"[summary] xp: {value}"); } } private static Pending Queue() { if (_pending == null) { _pending = new Pending(); } if (!_sequenceQueued) { _sequenceQueued = true; MelonCoroutines.Start(Sequence()); } return _pending; } private static IEnumerator Sequence() { yield return null; Pending job = _pending; _pending = null; _sequenceQueued = false; if (job == null) { yield break; } TMP_Text goldText = (((Object)(object)job.GoldUi != (Object)null) ? job.GoldUi.totalGoldEarnedText : null); if ((Object)(object)goldText != (Object)null && job.Gold != null) { yield return WaitForValue(goldText, job.Gold.VanillaTotal); EarnedGoldUI ui = job.GoldUi; yield return ImpactFx.Slam(goldText, goldText, FormatMult(job.Gold.Mult), FormatLike(goldText.text, job.Gold.ModdedTotal), job.ShakeRoot, delegate { ShowBonusPanel(ui, job.Gold.Mult); PlayBonusSound(ui); }); if ((Object)(object)ui != (Object)null && job.ModdedGold != null) { ui.earnedGold = job.ModdedGold; } } if ((Object)(object)job.XpText != (Object)null) { yield return WaitVisible(job.XpText); string label = FormatMult(job.XpMult); yield return ImpactFx.Slam(job.XpText, goldText ?? job.XpText, label, FormatLike(job.XpText.text, Mathf.RoundToInt(job.XpModded)) + Suffix(job.XpMult), job.ShakeRoot, delegate { PlayBonusSound(job.GoldUi); }); } } private static void ShowBonusPanel(EarnedGoldUI ui, float mult) { if ((Object)(object)ui == (Object)null) { return; } TMP_Text valueText; GameObject orCreateBonusPanel = GetOrCreateBonusPanel(ui, out valueText); if (!((Object)(object)orCreateBonusPanel == (Object)null)) { orCreateBonusPanel.SetActive(true); if ((Object)(object)valueText != (Object)null) { valueText.text = FormatMult(mult); } } } private static void PlayBonusSound(EarnedGoldUI ui) { try { if ((Object)(object)ui != (Object)null) { OneShotSoundPlayer bonusGoldSoundPlayer = ui.bonusGoldSoundPlayer; if (bonusGoldSoundPlayer != null) { bonusGoldSoundPlayer.Play(); } } } catch { } } private static IEnumerator WaitForValue(TMP_Text text, int value) { float timeout = Time.unscaledTime + 120f; float stableSince = -1f; while ((Object)(object)text != (Object)null && Time.unscaledTime < timeout) { if (((Behaviour)text).isActiveAndEnabled && ParseInt(text.text) == value) { if (stableSince < 0f) { stableSince = Time.unscaledTime; } else if (Time.unscaledTime - stableSince > 0.35f) { break; } } else { stableSince = -1f; } yield return null; } } private static IEnumerator WaitVisible(TMP_Text text) { float timeout = Time.unscaledTime + 120f; while ((Object)(object)text != (Object)null && !((Behaviour)text).isActiveAndEnabled && Time.unscaledTime < timeout) { yield return null; } float until = Time.unscaledTime + 0.3f; while (Time.unscaledTime < until) { yield return null; } } private static GameObject GetOrCreateBonusPanel(EarnedGoldUI ui, out TMP_Text valueText) { valueText = null; try { BonusGoldUI difficultyGoldBonus = ui.difficultyGoldBonus; GameObject val = ((difficultyGoldBonus != null) ? difficultyGoldBonus.Panel : null); if ((Object)(object)val == (Object)null) { return null; } Transform parent = val.transform.parent; Transform val2 = parent.Find("SBMod_GoldBonus"); GameObject val3; if ((Object)(object)val2 != (Object)null) { val3 = ((Component)val2).gameObject; } else { val3 = Object.Instantiate<GameObject>(val, parent); ((Object)val3).name = "SBMod_GoldBonus"; Transform transform = val.transform; GameObject[] array = new GameObject[2]; BonusGoldUI cursesGoldBonus = ui.cursesGoldBonus; array[0] = ((cursesGoldBonus != null) ? cursesGoldBonus.Panel : null); BonusGoldUI unusedCharacterGoldBonus = ui.unusedCharacterGoldBonus; array[1] = ((unusedCharacterGoldBonus != null) ? unusedCharacterGoldBonus.Panel : null); GameObject[] array2 = (GameObject[])(object)array; foreach (GameObject val4 in array2) { if ((Object)(object)val4 != (Object)null && (Object)(object)val4.transform.parent == (Object)(object)parent && val4.transform.GetSiblingIndex() > transform.GetSiblingIndex()) { transform = val4.transform; } } val3.transform.SetSiblingIndex(transform.GetSiblingIndex() + 1); } string text = (((Object)(object)difficultyGoldBonus.Text != (Object)null) ? RelativePath(val.transform, difficultyGoldBonus.Text.transform) : null); foreach (TMP_Text componentsInChild in val3.GetComponentsInChildren<TMP_Text>(true)) { if (text != null && RelativePath(val3.transform, componentsInChild.transform) == text) { foreach (LocalizeStringEvent component in ((Component)componentsInChild).GetComponents<LocalizeStringEvent>()) { Object.DestroyImmediate((Object)(object)component); } valueText = componentsInChild; } else if ((Object)(object)val2 == (Object)null) { SettingsTab.BindText(componentsInChild, "bonus"); } } val3.SetActive(false); return val3; } catch (Exception ex) { GoldXpMod.Log.Warning("[summary] bonus panel: " + ex.Message); return null; } } private static string RelativePath(Transform root, Transform t) { if ((Object)(object)t == (Object)(object)root) { return ""; } StringBuilder stringBuilder = new StringBuilder(((Object)t).name); while ((Object)(object)t.parent != (Object)null && (Object)(object)t.parent != (Object)(object)root) { t = t.parent; stringBuilder.Insert(0, ((Object)t).name + "/"); } return stringBuilder.ToString(); } private static string FormatMult(float mult) { return "x" + mult.ToString("0.##", CultureInfo.InvariantCulture); } private static string Suffix(float mult) { return " <color=#FFD54A><size=75%>" + FormatMult(mult) + "</size></color>"; } private static int ParseInt(string s) { if (string.IsNullOrEmpty(s)) { return -1; } long num = 0L; bool flag = false; foreach (char c in s) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': num = num * 10 + (c - 48); flag = true; if (num <= int.MaxValue) { continue; } return -1; default: continue; case '<': break; } break; } if (!flag) { return -1; } return (int)num; } private static string FormatLike(string sample, int value) { char? c = null; if (!string.IsNullOrEmpty(sample)) { for (int i = 1; i < sample.Length - 1; i++) { if (!char.IsDigit(sample[i]) && char.IsDigit(sample[i - 1]) && char.IsDigit(sample[i + 1])) { c = sample[i]; break; } } } string text = Math.Abs(value).ToString(); if (!c.HasValue || text.Length <= 3) { return ((value < 0) ? "-" : "") + text; } StringBuilder stringBuilder = new StringBuilder(); for (int j = 0; j < text.Length; j++) { if (j > 0 && (text.Length - j) % 3 == 0) { stringBuilder.Append(c.Value); } stringBuilder.Append(text[j]); } return ((value < 0) ? "-" : "") + stringBuilder; } } internal static class SaveDoctor { private const int MaxSaneRank = 1000; public static void RepairCorruptedRanks() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: 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_0148: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) Instance log = GoldXpMod.Log; SaveManager instance = SingletonPersistent<SaveManager>.Instance; LocalPlayer instance2 = SingletonPersistent<LocalPlayer>.Instance; CharacterRankProgression val = ((instance2 != null) ? instance2.GetCharacterRankManager() : null); Dictionary<CharacterId, CharacterRankProgress> val2 = ((val != null) ? val.RankProgressPerCharacter : null); if ((Object)(object)instance == (Object)null || val2 == null) { return; } List<CharacterId> list = new List<CharacterId>(); Enumerator<CharacterId, CharacterRankProgress> enumerator = val2.GetEnumerator(); while (enumerator.MoveNext()) { KeyValuePair<CharacterId, CharacterRankProgress> current = enumerator.Current; if (!IsSane(current.Value)) { list.Add(current.Key); } } if (list.Count == 0) { return; } List<(int, PlayerSaveData)> list2 = LoadAllSlots(instance); foreach (CharacterId item3 in list) { string value = $"rank {val2[item3].CurrentRank} / progress {val2[item3].ProgressTowardsNextRank}"; int num = 1; float num2 = 0f; string value2 = "reset to rank 1 (no healthy save slot)"; foreach (var item4 in list2) { int item = item4.Item1; PlayerSaveData item2 = item4.Item2; Dictionary<CharacterId, CharacterRankProgress> rankProgressPerCharacter = item2.RankProgressPerCharacter; if (rankProgressPerCharacter != null && rankProgressPerCharacter.ContainsKey(item3) && IsSane(rankProgressPerCharacter[item3])) { num = rankProgressPerCharacter[item3].CurrentRank; num2 = rankProgressPerCharacter[item3].ProgressTowardsNextRank; value2 = $"restored from save slot {item} ({item2.SaveTimestamp})"; break; } } RankXp.Suspended = true; try { val.SetRankProgression(item3, num, num2); } finally { RankXp.Suspended = false; } log.Warning($"[save] repaired {item3}: {value} → rank {num} / progress {num2} — {value2}"); } instance.SavePlayerData(); log.Msg($"[save] repaired {list.Count} character rank(s) and saved"); } private static bool IsSane(CharacterRankProgress p) { if (p != null && p.CurrentRank >= 0 && p.CurrentRank <= 1000 && p.Prestige >= 0 && p.Prestige <= 1000 && !float.IsNaN(p.ProgressTowardsNextRank) && !float.IsInfinity(p.ProgressTowardsNextRank) && p.ProgressTowardsNextRank >= 0f && p.ProgressTowardsNextRank <= 1f) { if (p.ProgressTowardsNextRank != 0f) { return p.ProgressTowardsNextRank >= 1E-06f; } return true; } return false; } private static List<(int slot, PlayerSaveData data)> LoadAllSlots(SaveManager saveManager) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) List<(int, PlayerSaveData)> list = new List<(int, PlayerSaveData)>(); for (int i = 0; i < SaveManager.SlotCount; i++) { try { IExternalSaveManager val = saveManager.CreateManagerForSlot(new RollingSaveSlot(i)); if (val != null && val.SaveExists()) { PlayerSaveData val2 = val.Load<PlayerSaveData>(SaveManager.SaveKey); if (val2 != null) { list.Add((i, val2)); } } } catch (Exception ex) { GoldXpMod.Log.Warning($"[save] can't read slot {i}: {ex.GetType().Name}: {ex.Message}"); } } list.Sort(delegate((int, PlayerSaveData) a, (int, PlayerSaveData) b) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) DateTime saveTimestamp = b.Item2.SaveTimestamp; long ticks = ((DateTime)(ref saveTimestamp)).Ticks; saveTimestamp = a.Item2.SaveTimestamp; return ticks.CompareTo(((DateTime)(ref saveTimestamp)).Ticks); }); return list; } } internal static class SettingsTab { private sealed class Row { public Selector Selector; public Func<List<string>> Options; public Func<string> Current; public Action<string> Apply; } private const string ButtonName = "SBMod_Button"; private const string PanelName = "SBMod_Panel"; internal static readonly float[] Presets = new float[16] { 0.5f, 1f, 1.25f, 1.5f, 2f, 2.5f, 3f, 4f, 5f, 7.5f, 10f, 15f, 20f, 25f, 50f, 100f }; private static readonly List<Row> Rows = new List<Row>(); private static bool _followsLanguage; public static void InjectPrefix(SettingsPanel __instance) { try { Inject(__instance); } catch (Exception value) { GoldXpMod.Log.Error($"[settings tab] inject failed: {value}"); } } private static void Inject(SettingsPanel settings) { //IL_040a: Unknown result type (might be due to invalid IL or missing references) //IL_040f: Unknown result type (might be due to invalid IL or missing references) //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_0421: Expected O, but got Unknown List<SubPanel> subPanels = settings.subPanels; if (subPanels == null || subPanels.Count == 0) { return; } for (int i = 0; i < subPanels.Count; i++) { if ((Object)(object)subPanels[i].Panel != (Object)null && ((Object)subPanels[i].Panel).name == "SBMod_Panel") { return; } } SubPanel val = null; SubPanel val2 = null; int num = subPanels.Count; for (int j = 0; j < subPanels.Count; j++) { SubPanel val3 = subPanels[j]; if (!((Object)(object)val3.Button == (Object)null) && !((Object)(object)val3.Panel == (Object)null)) { if (((Object)val3.Panel).name.StartsWith("General")) { val = val3; } if (((Object)val3.Button).name.StartsWith("Controls")) { val2 = val3; } if (((Object)val3.Button).name.StartsWith("Credits")) { num = j; } } } if (val == null) { val = subPanels[0]; } if (val2 == null) { val2 = subPanels[0]; } if (!_followsLanguage) { _followsLanguage = true; Strings.OnLanguageChanged(delegate { MelonCoroutines.Start(RefreshNextFrame(onlyVisible: false)); }); } GameObject val4 = Object.Instantiate<GameObject>(((Component)val2.Button).gameObject, ((Component)val2.Button).transform.parent); ((Object)val4).name = "SBMod_Button"; if (num < subPanels.Count) { val4.transform.SetSiblingIndex(((Component)subPanels[num].Button).transform.GetSiblingIndex()); } BindText(val4.GetComponentInChildren<TMP_Text>(true), "tab"); MuteSerializedClicks(val4.GetComponent<Button>()); SelectableButton component = val4.GetComponent<SelectableButton>(); GameObject obj = Object.Instantiate<GameObject>(((Component)val.Panel).gameObject, ((Component)val.Panel).transform.parent); ((Object)obj).name = "SBMod_Panel"; obj.SetActive(false); NavigateablePanel component2 = obj.GetComponent<NavigateablePanel>(); VerticalLayoutGroup componentInChildren = obj.GetComponentInChildren<VerticalLayoutGroup>(true); Transform val5 = ((componentInChildren != null) ? ((Component)componentInChildren).transform : null) ?? throw new InvalidOperationException("row container not found"); Transform val6 = null; for (int num2 = 0; num2 < val5.childCount; num2++) { if (!((Object)(object)val6 == (Object)null)) { break; } Transform child = val5.GetChild(num2); if (((Object)child).name.StartsWith("DamageNumbers")) { val6 = child; } } for (int num3 = 0; num3 < val5.childCount; num3++) { if (!((Object)(object)val6 == (Object)null)) { break; } Transform child2 = val5.GetChild(num3); if ((Object)(object)((Component)child2).GetComponentInChildren<Selector>(true) != (Object)null) { val6 = child2; } } if ((Object)(object)val6 == (Object)null) { throw new InvalidOperationException("selector row template not found"); } val6.SetParent((Transform)null, false); for (int num4 = val5.childCount - 1; num4 >= 0; num4--) { Object.DestroyImmediate((Object)(object)((Component)val5.GetChild(num4)).gameObject); } StripGameLogic(((Component)val6).gameObject); List<Selectable> list = new List<Selectable> { AddToggleRow(val5, val6, "enabled", GoldXpMod._enabledEntry), AddMultiplierRow(val5, val6, "gold", GoldXpMod._goldEntry), AddMultiplierRow(val5, val6, "xp", GoldXpMod._xpEntry), AddMultiplierRow(val5, val6, "rank", GoldXpMod._rankXpEntry), AddToggleRow(val5, val6, "reveal", GoldXpMod._revealEntry) }; for (int num5 = 0; num5 < list.Count; num5++) { SetExplicit(list[num5], (num5 > 0) ? list[num5 - 1] : null, (num5 < list.Count - 1) ? list[num5 + 1] : null, null, null); } AddHint(val5, val6); Object.DestroyImmediate((Object)(object)((Component)val6).gameObject); component2.firstSelected = ((Component)list[0]).gameObject; RegisterInputReceivers(settings, ((Component)val.Panel).GetComponentInChildren<Selector>(true), list); SubPanel val7 = new SubPanel { Button = component, Panel = (Panel)(object)component2 }; subPanels.Insert(num, val7); ((ButtonInteractionHook)component).OnClicked += Action.op_Implicit((Action)delegate { MelonCoroutines.Start(RefreshNextFrame()); }); } public static void LinkTabButtonsPostfix(SettingsPanel __instance) { try { List<SubPanel> subPanels = __instance.subPanels; if (subPanels == null) { return; } List<Button> list = new List<Button>(); Selectable val = null; for (int i = 0; i < subPanels.Count; i++) { Button val2 = (((Object)(object)subPanels[i].Button != (Object)null) ? ((Component)subPanels[i].Button).GetComponent<Button>() : null); if ((Object)(object)val2 != (Object)null) { list.Add(val2); } if ((Object)(object)subPanels[i].Panel != (Object)null && ((Object)subPanels[i].Panel).name == "SBMod_Panel") { val = (Selectable)(object)((Component)subPanels[i].Panel).GetComponentInChildren<Selector>(true); } } if (list.Count < 2) { return; } list.Sort((Button a, Button b) => ((Component)a).transform.GetSiblingIndex().CompareTo(((Component)b).transform.GetSiblingIndex())); Selectable selectOnUp = ((Selectable)list[0]).navigation.selectOnUp; Selectable selectOnDown = ((Selectable)list[list.Count - 1]).navigation.selectOnDown; Selectable val3 = null; foreach (Button item in list) { if (((Object)item).name != "SBMod_Button" && (Object)(object)((Selectable)item).navigation.selectOnRight != (Object)null) { val3 = ((Selectable)item).navigation.selectOnRight; break; } } for (int num = 0; num < list.Count; num++) { Navigation navigation = ((Selectable)list[num]).navigation; bool flag = ((Object)list[num]).name == "SBMod_Button"; SetExplicit((Selectable)(object)list[num], (Selectable)((num > 0) ? ((object)list[num - 1]) : ((object)selectOnUp)), (Selectable)((num < list.Count - 1) ? ((object)list[num + 1]) : ((object)selectOnDown)), flag ? null : navigation.selectOnLeft, flag ? (((Object)(object)val3 != (Object)null) ? val : null) : navigation.selectOnRight); } } catch (Exception value) { GoldXpMod.Log.Error($"[settings tab] navigation: {value}"); } } private static void SetExplicit(Selectable s, Selectable up, Selectable down, Selectable left, Selectable right) { if (!((Object)(object)s == (Object)null)) { Navigation navigation = s.navigation; navigation.mode = (Mode)4; navigation.selectOnUp = up; navigation.selectOnDown = down; navigation.selectOnLeft = left; navigation.selectOnRight = right; s.navigation = navigation; } } private static void RegisterInputReceivers(SettingsPanel settings, Selector nativeSelector, List<Selectable> selectors) { //IL_01e9: Unknown result type (might be due to invalid IL or missing references) List<UIPanel> list = new List<UIPanel>(); foreach (UIPanel item in ((Component)settings).GetComponentsInParent<UIPanel>(true)) { list.Add(item); } foreach (UIPanel componentsInChild in ((Component)settings).GetComponentsInChildren<UIPanel>(true)) { list.Add(componentsInChild); } UIPanel val = null; if ((Object)(object)nativeSelector != (Object)null) { foreach (UIPanel item2 in list) { List<IUIInputReceiver> inputReceivers = item2.inputReceivers; if (inputReceivers == null) { continue; } for (int i = 0; i < inputReceivers.Count; i++) { if (!((Object)(object)val == (Object)null)) { break; } if (inputReceivers[i] != null && ((Il2CppObjectBase)inputReceivers[i]).Pointer == ((Il2CppObjectBase)nativeSelector).Pointer) { val = item2; } } if ((Object)(object)val != (Object)null) { break; } } } if (val == null) { val = ((list.Count > 0) ? list[0] : null); } if ((Object)(object)val == (Object)null) { GoldXpMod.Log.Warning("[settings tab] no UIPanel — arrow keys won't change values"); return; } UIPanel val2 = val; if (val2.inputReceivers == null) { List<IUIInputReceiver> val3 = (val2.inputReceivers = new List<IUIInputReceiver>()); } UIInputRouter instance = SingletonPersistent<UIInputRouter>.Instance; bool flag = false; if (((instance != null) ? instance.currentReceivers : null) != null && (Object)(object)nativeSelector != (Object)null) { for (int j = 0; j < instance.currentReceivers.Count; j++) { if (instance.currentReceivers[j] != null && ((Il2CppObjectBase)instance.currentReceivers[j]).Pointer == ((Il2CppObjectBase)nativeSelector).Pointer) { flag = true; } } } foreach (Selectable selector in selectors) { IUIInputReceiver val5 = (IUIInputReceiver)(((object)((Il2CppObjectBase)selector).TryCast<IUIInputReceiver>()) ?? ((object)new IUIInputReceiver(((Il2CppObjectBase)selector).Pointer))); val.inputReceivers.Add(val5); if (flag) { instance.Register(val5); } } } private static Selectable AddToggleRow(Transform container, Transform template, string labelKey, MelonPreferences_Entry<bool> entry) { return AddRow(container, template, labelKey, new Row { Options = () => new List<string> { Strings.Get("on"), Strings.Get("off") }, Current = () => Strings.Get(entry.Value ? "on" : "off"), Apply = delegate(string option) { Store(entry, option == Strings.Get("on")); } }); } private static Selectable AddMultiplierRow(Transform container, Transform template, string labelKey, MelonPreferences_Entry<float> entry) { return AddRow(container, template, labelKey, new Row { Options = delegate { List<float> list = new List<float>(Presets); if (!list.Contains(entry.Value)) { list.Add(entry.Value); list.Sort(); } return list.ConvertAll<string>(Format); }, Current = () => Format(entry.Value), Apply = delegate(string option) { if (float.TryParse(option.TrimStart('x'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { Store(entry, result); } } }); } private static Selectable AddRow(Transform container, Transform template, string labelKey, Row row) { GameObject obj = Object.Instantiate<GameObject>(((Component)template).gameObject, container); ((Object)obj).name = "SBMod_" + labelKey; obj.SetActive(true); Selector componentInChildren = obj.GetComponentInChildren<Selector>(true); foreach (TMP_Text componentsInChild in obj.GetComponentsInChildren<TMP_Text>(true)) { if (!componentsInChild.transform.IsChildOf(((Component)componentInChildren).transform)) { BindText(componentsInChild, labelKey); } } componentInChildren.localizationMode = (LocalizationMode)2; if ((Object)(object)componentInChildren.valueLocalizeString != (Object)null) { ((Behaviour)componentInChildren.valueLocalizeString).enabled = false; } row.Selector = componentInChildren; componentInChildren.OnOptionSelected += Action<string>.op_Implicit((Action<string>)delegate(string option) { try { row.Apply(option); } catch (Exception value) { GoldXpMod.Log.Error($"[settings tab] {value}"); } }); Rows.Add(row); Show(row); return (Selectable)(object)componentInChildren; } private static void AddHint(Transform container, Transform template) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) GameObject obj = Object.Instantiate<GameObject>(((Component)template).gameObject, container); ((Object)obj).name = "SBMod_Hint"; obj.SetActive(true); Object.DestroyImmediate((Object)(object)((Component)obj.GetComponentInChildren<Selector>(true)).gameObject); TMP_Text componentInChildren = obj.GetComponentInChildren<TMP_Text>(true); BindText(componentInChildren, "hint"); componentInChildren.fontSize *= 0.7f; componentInChildren.alignment = (TextAlignmentOptions)257; RectTransform rectTransform = componentInChildren.rectTransform; rectTransform.sizeDelta = new Vector2(0f, 140f); rectTransform.anchoredPosition = new Vector2(0f, -40f); } private static IEnumerator RefreshNextFrame(bool onlyVisible = true) { yield return null; Rows.RemoveAll((Row r) => (Object)(object)r.Selector == (Object)null); foreach (Row row in Rows) { if (!onlyVisible || ((Behaviour)row.Selector).isActiveAndEnabled) { Show(row); } } } private static void Show(Row row) { List<string> val = new List<string>(); foreach (string item in row.Options()) { val.Add(item); } string text = row.Current(); row.Selector.SetOptions(val); row.Selector.SetSelectedOption(text); row.Selector.SetNonLocalizedValueText(text); } private static void Store<T>(MelonPreferences_Entry<T> entry, T value) { if (!object.Equals(entry.Value, value)) { entry.Value = value; GoldXpMod.SaveSettings(); } } private static string Format(float v) { return "x" + v.ToString("0.##", CultureInfo.InvariantCulture); } internal static void BindText(TMP_Text text, string key) { if ((Object)(object)text == (Object)null) { return; } foreach (LocalizeStringEvent component in ((Component)text).GetComponents<LocalizeStringEvent>()) { Object.DestroyImmediate((Object)(object)component); } Strings.Bind(text, key); } private static void StripGameLogic(GameObject row) { foreach (MonoBehaviour component in row.GetComponents<MonoBehaviour>()) { if (((MemberInfo)((Object)component).GetIl2CppType()).Name != "Image") { Object.DestroyImmediate((Object)(object)component); } } foreach (TextReplacer componentsInChild in row.GetComponentsInChildren<TextReplacer>(true)) { Object.DestroyImmediate((Object)(object)componentsInChild); } } private static void MuteSerializedClicks(Button button) { if (!((Object)(object)button == (Object)null)) { for (int i = 0; i < ((UnityEventBase)button.onClick).GetPersistentEventCount(); i++) { ((UnityEventBase)button.onClick).SetPersistentListenerState(i, (UnityEventCallState)0); } } } } internal static class Strings { public const string Tab = "tab"; public const string Enabled = "enabled"; public const string On = "on"; public const string Off = "off"; public const string Gold = "gold"; public const string Xp = "xp"; public const string RankXp = "rank"; public const string Hint = "hint"; public const string Bonus = "bonus"; public const string Reveal = "reveal"; public const string Matchmaking = "mm"; private static readonly string[] Keys = new string[11] { "tab", "enabled", "on", "off", "gold", "xp", "rank", "hint", "bonus", "reveal", "mm" }; private static readonly Dictionary<string, string[]> Table = new Dictionary<string, string[]> { ["en"] = new string[11] { "Multipliers", "Mod Enabled", "On", "Off", "Gold per Run", "Run XP", "Wizard Rank XP", "Changes apply immediately. x1 = vanilla.\nIn co-op, run XP works when the host has the mod.", "Mod multiplier", "x2 reveal at the end of a run", "run XP multiplier is {0}: as host you change XP for the whole team. Set it to x1 to use matchmaking." }, ["ru"] = new string[11] { "Множители", "Мод включён", "Вкл", "Выкл", "Золото за забег", "Опыт в забеге", "Опыт мага (ранг)", "Изменения применяются сразу. x1 — как в оригинале.\nОпыт в забеге в коопе работает, если мод стоит у хоста.", "Множитель мода", "Анимация x2 в итогах забега", "множитель опыта в забеге {0}: у хоста он меняет опыт всей команде. Поставьте x1, чтобы включить подбор." }, ["uk"] = new string[11] { "Множники", "Мод увімкнено", "Увімк", "Вимк", "Золото за забіг", "Досвід у забігу", "Досвід мага (ранг)", "Зміни застосовуються одразу. x1 — як в оригіналі.\nДосвід у забігу в кооперативі працює, якщо мод встановлено в хоста.", "Множник мода", "Анімація x2 у підсумках забігу", "множник досвіду в забігу {0}: у хоста він змінює досвід усій команді. Поставте x1, щоб увімкнути підбір." }, ["de"] = new string[11] { "Multiplikatoren", "Mod aktiviert", "An", "Aus", "Gold pro Durchlauf", "EP im Durchlauf", "Magier-Rang-EP", "Änderungen gelten sofort. x1 = Original.\nIm Koop wirken die EP im Durchlauf, wenn der Host die Mod hat.", "Mod-Multiplikator", "x2-Animation am Ende des Laufs", "Lauf-EP-Multiplikator ist {0}: Als Host änderst du die EP des ganzen Teams. Stelle ihn auf x1, um Matchmaking zu nutzen." }, ["fr"] = new string[11] { "Multiplicateurs", "Mod activé", "Activé", "Désactivé", "Or par partie", "XP de partie", "XP de rang du mage", "Les changements s'appliquent immédiatement. x1 = jeu d'origine.\nEn coop, l'XP de partie fonctionne si l'hôte a le mod.", "Multiplicateur du mod", "Animation x2 en fin de partie", "le multiplicateur d'XP de partie est {0} : en tant qu'hôte, vous modifiez l'XP de toute l'équipe. Mettez-le à x1 pour utiliser le matchmaking." }, ["it"] = new string[11] { "Moltiplicatori", "Mod attiva", "Attivo", "Disattivo", "Oro per partita", "PE della partita", "PE rango mago", "Le modifiche si applicano subito. x1 = originale.\nIn co-op, i PE della partita funzionano se l'host ha la mod.", "Moltiplicatore mod", "Animazione x2 a fine partita", "il moltiplicatore PE della partita è {0}: come host cambi i PE di tutta la squadra. Impostalo a x1 per usare il matchmaking." }, ["nl"] = new string[11] { "Vermenigvuldigers", "Mod ingeschakeld", "Aan", "Uit", "Goud per run", "Run-XP", "Tovenaarsrang-XP", "Wijzigingen gelden direct. x1 = origineel.\nIn co-op werkt run-XP als de host de mod heeft.", "Mod-vermenigvuldiger", "x2-animatie aan het einde van de run", "run-XP-vermenigvuldiger is {0}: als host verander je de XP van het hele team. Zet hem op x1 om matchmaking te gebruiken." }, ["pl"] = new string[11] { "Mnożniki", "Mod włączony", "Wł.", "Wył.", "Złoto za wyprawę", "PD w wyprawie", "PD rangi maga", "Zmiany działają od razu. x1 = oryginał.\nW trybie co-op PD w wyprawie działa, jeśli host ma moda.", "Mnożnik moda", "Animacja x2 w podsumowaniu wyprawy", "mnożnik PD w wyprawie to {0}: jako host zmieniasz PD całej drużynie. Ustaw x1, aby używać dobierania." }, ["pt-br"] = new string[11] { "Multiplicadores", "Mod ativado", "Ligado", "Desligado", "Ouro por partida", "XP da partida", "XP de nível do mago", "As mudanças valem na hora. x1 = original.\nNo co-op, o XP da partida funciona se o anfitrião tiver o mod.", "Multiplicador do mod", "Animação x2 no fim da partida", "o multiplicador de XP da partida é {0}: como anfitrião, você muda o XP de toda a equipe. Coloque x1 para usar o matchmaking." }, ["pt"] = new string[11] { "Multiplicadores", "Mod ativado", "Ligado", "Desligado", "Ouro por partida", "XP da partida", "XP de nível do mago", "As alterações aplicam-se de imediato.