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 LuckyUpgradesFork v1.0.1
LuckyUpgradesFork.dll
Decompiled 18 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("mihmi125")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("Fork of R.E.P.O LuckyUpgrades")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+3220723b0df52f28ecf9f073b61fa83e0c9ba0f5")] [assembly: AssemblyProduct("LuckyUpgradesFork")] [assembly: AssemblyTitle("LuckyUpgradesFork")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 LuckyUpgrades { [BepInPlugin("LuckyUpgradesFork", "LuckyUpgradesFork", "1.0.0")] [BepInProcess("REPO.exe")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Logger; private static int _isApplyingSharedUpgrade = 0; private static readonly object _randomLock = new object(); private static readonly Random _random = new Random(); internal static readonly object SharedUpgradesLock = new object(); internal static readonly object ModdedUpgradeRegistryLock = new object(); internal static readonly object MySteamIDLock = new object(); internal static string _mySteamID = null; internal static readonly Dictionary<string, int> _sharedUpgrades = new Dictionary<string, int>(); internal static readonly Dictionary<string, (Action<string, int> apply, Func<int> getChance)> _moddedUpgradeRegistry = new Dictionary<string, (Action<string, int>, Func<int>)>(); private Harmony _harmony; private static bool? _repoLibTypeSearched = null; private static Type _repoLibItemUpgradeType = null; private static FieldInfo _repoLibUpgradeIdField = null; private static Type _repoLibUpgradesModuleType = null; private static MethodInfo _repoLibGetUpgradeMethod = null; public static Plugin Instance { get; private set; } public static UpgradeConfig UpgradeConfiguration { get; private set; } private void Awake() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Expected O, but got Unknown Instance = this; Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"Plugin LuckyUpgradesFork is loaded!"); UpgradeConfiguration = new UpgradeConfig(((BaseUnityPlugin)this).Config); _harmony = new Harmony("LuckyUpgradesFork"); HarmonyMethod val = new HarmonyMethod(typeof(Plugin), "ItemUpgrade_PlayUpgrade_Postfix", (Type[])null); bool flag = false; HashSet<string> hashSet = new HashSet<string> { "Start", "Awake", "Update", "FixedUpdate", "LateUpdate", "OnEnable", "OnDisable", "OnDestroy", "Reset", "ButtonToggle", "ButtonToggleLogic", "ButtonToggleRPC" }; string[] array = new string[8] { "PlayerUpgrade", "PlayUpgrade", "Use", "Upgrade", "Activate", "OnUse", "UseUpgrade", "Apply" }; foreach (string text in array) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ItemUpgrade), text, new Type[0], (Type[])null); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Logger.LogInfo((object)("[LuckyUpgrades] Patched ItemUpgrade." + text + " ✓")); flag = true; break; } } if (!flag) { MethodInfo[] methods = typeof(ItemUpgrade).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (methodInfo2.ReturnType == typeof(void) && methodInfo2.GetParameters().Length == 0 && !methodInfo2.IsAbstract && !hashSet.Contains(methodInfo2.Name)) { _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Logger.LogWarning((object)("[LuckyUpgrades] Fallback-patched ItemUpgrade." + methodInfo2.Name + " — verify this is the upgrade trigger!")); flag = true; break; } } } if (!flag) { Logger.LogError((object)"[LuckyUpgrades] Could not find any suitable method on ItemUpgrade to patch — sharing will not work! Check the method list above."); } GameObject val2 = new GameObject("LuckyUpgrades_UpdateRunner"); val2.AddComponent<UpgradeReapplyRunner>(); Object.DontDestroyOnLoad((Object)(object)val2); ((Object)val2).hideFlags = (HideFlags)61; PreBindREPOLibUpgrades(); Logger.LogInfo((object)"Harmony patches applied!"); } public static void RegisterModdedUpgrade(string upgradeId, Action<string, int> applyAction, int shareChance = 25) { if (string.IsNullOrEmpty(upgradeId)) { ManualLogSource logger = Logger; if (logger != null) { logger.LogError((object)"[LuckyUpgrades] RegisterModdedUpgrade: upgradeId cannot be null or empty."); } return; } if (applyAction == null) { ManualLogSource logger2 = Logger; if (logger2 != null) { logger2.LogError((object)("[LuckyUpgrades] RegisterModdedUpgrade: applyAction cannot be null (upgradeId: " + upgradeId + ").")); } return; } shareChance = Math.Max(0, Math.Min(100, shareChance)); lock (ModdedUpgradeRegistryLock) { if (_moddedUpgradeRegistry.ContainsKey(upgradeId)) { ManualLogSource logger3 = Logger; if (logger3 != null) { logger3.LogWarning((object)("[LuckyUpgrades] Upgrade '" + upgradeId + "' already registered — overwriting.")); } } ConfigEntry<int> configEntry = UpgradeConfiguration?.BindModdedUpgrade(upgradeId, shareChance); Func<int> func = ((configEntry != null) ? ((Func<int>)(() => configEntry.Value)) : ((Func<int>)(() => shareChance))); _moddedUpgradeRegistry[upgradeId] = (applyAction, func); ManualLogSource logger4 = Logger; if (logger4 != null) { logger4.LogInfo((object)$"[LuckyUpgrades] ✓ REGISTERED modded upgrade: '{upgradeId}' ({func()}% share chance)"); } } } public static void TriggerModdedUpgradeShare(string upgradeId, string sourceSteamID, int amount = 1) { Action<string, int> applyDelegate; int value2; lock (ModdedUpgradeRegistryLock) { if (!_moddedUpgradeRegistry.TryGetValue(upgradeId, out (Action<string, int>, Func<int>) value)) { ManualLogSource logger = Logger; if (logger != null) { logger.LogError((object)("[LuckyUpgrades] ✗ TriggerModdedUpgradeShare: '" + upgradeId + "' NOT REGISTERED! Did you call RegisterModdedUpgrade()?")); } return; } applyDelegate = value.Item1; value2 = value.Item2(); } ManualLogSource logger2 = Logger; if (logger2 != null) { logger2.LogInfo((object)("[LuckyUpgrades] → TriggerModdedUpgradeShare called: '" + upgradeId + "' from " + sourceSteamID)); } ApplySharedUpgradeToSelf(upgradeId, sourceSteamID, amount, delegate(int amt) { string mySteamID = GetMySteamID(); if (!string.IsNullOrEmpty(mySteamID)) { ManualLogSource logger3 = Logger; if (logger3 != null) { logger3.LogInfo((object)$"[LuckyUpgrades] → Applying modded upgrade '{upgradeId}' to player {mySteamID} (+{amt})"); } applyDelegate(mySteamID, amt); } else { ManualLogSource logger4 = Logger; if (logger4 != null) { logger4.LogWarning((object)("[LuckyUpgrades] ✗ Cannot apply '" + upgradeId + "': SteamID is null/empty")); } } }, value2); } internal static string GetMySteamID() { lock (MySteamIDLock) { if (string.IsNullOrEmpty(_mySteamID)) { PlayerAvatar val = SemiFunc.PlayerAvatarLocal(); if ((Object)(object)val != (Object)null) { _mySteamID = SemiFunc.PlayerGetSteamID(val); if (!string.IsNullOrEmpty(_mySteamID)) { ManualLogSource logger = Logger; if (logger != null) { logger.LogDebug((object)("[LuckyUpgrades] Player SteamID cached: " + _mySteamID)); } } else { ManualLogSource logger2 = Logger; if (logger2 != null) { logger2.LogWarning((object)"[LuckyUpgrades] Failed to get player SteamID from local player"); } } } else { ManualLogSource logger3 = Logger; if (logger3 != null) { logger3.LogDebug((object)"[LuckyUpgrades] Local player not found yet"); } } } return _mySteamID; } } internal static void ReapplySharedUpgrades() { Dictionary<string, int> dictionary; lock (SharedUpgradesLock) { if (_sharedUpgrades.Count == 0) { return; } dictionary = new Dictionary<string, int>(_sharedUpgrades); _sharedUpgrades.Clear(); } string mySteamID = GetMySteamID(); if (string.IsNullOrEmpty(mySteamID)) { return; } Logger.LogInfo((object)$"[LuckyUpgrades] Reapplying {dictionary.Count} upgrade type(s)..."); try { Interlocked.Exchange(ref _isApplyingSharedUpgrade, 1); foreach (KeyValuePair<string, int> item in dictionary) { string key = item.Key; int value = item.Value; if (value <= 0) { continue; } try { if (ReapplySingleUpgrade(mySteamID, key, value)) { Logger.LogInfo((object)$"[LuckyUpgrades] Reapplied: {key} +{value}"); } else { Logger.LogWarning((object)("[LuckyUpgrades] Unknown upgrade type during reapply — skipped: '" + key + "'")); } } catch (Exception ex) { Logger.LogError((object)("[LuckyUpgrades] Error reapplying '" + key + "': " + ex.Message)); } } } finally { Interlocked.Exchange(ref _isApplyingSharedUpgrade, 0); } } private static bool ReapplySingleUpgrade(string myID, string upgradeType, int amount) { switch (upgradeType) { case "Health": { for (int num6 = 0; num6 < amount; num6++) { PunManager.instance.UpgradePlayerHealth(myID, 1); } return true; } case "Energy": { for (int num = 0; num < amount; num++) { PunManager.instance.UpgradePlayerEnergy(myID, 1); } return true; } case "ExtraJump": { for (int num7 = 0; num7 < amount; num7++) { PunManager.instance.UpgradePlayerExtraJump(myID, 1); } return true; } case "GrabRange": { for (int l = 0; l < amount; l++) { PunManager.instance.UpgradePlayerGrabRange(myID, 1); } return true; } case "GrabStrength": { for (int num3 = 0; num3 < amount; num3++) { PunManager.instance.UpgradePlayerGrabStrength(myID, 1); } return true; } case "GrabThrow": { for (int k = 0; k < amount; k++) { PunManager.instance.UpgradePlayerThrowStrength(myID, 1); } return true; } case "SprintSpeed": { for (int num4 = 0; num4 < amount; num4++) { PunManager.instance.UpgradePlayerSprintSpeed(myID, 1); } return true; } case "TumbleLaunch": { for (int n = 0; n < amount; n++) { PunManager.instance.UpgradePlayerTumbleLaunch(myID, 1); } return true; } case "MapPlayerCount": { for (int i = 0; i < amount; i++) { PunManager.instance.UpgradeMapPlayerCount(myID, 1); } return true; } case "TumbleClimb": { for (int num5 = 0; num5 < amount; num5++) { PunManager.instance.UpgradePlayerTumbleClimb(myID, 1); } return true; } case "TumbleWings": { for (int num2 = 0; num2 < amount; num2++) { PunManager.instance.UpgradePlayerTumbleWings(myID, 1); } return true; } case "CrouchRest": { for (int m = 0; m < amount; m++) { PunManager.instance.UpgradePlayerCrouchRest(myID, 1); } return true; } case "DeathHeadBattery": { for (int j = 0; j < amount; j++) { PunManager.instance.UpgradeDeathHeadBattery(myID, 1); } return true; } default: lock (ModdedUpgradeRegistryLock) { if (_moddedUpgradeRegistry.TryGetValue(upgradeType, out (Action<string, int>, Func<int>) value)) { value.Item1(myID, amount); return true; } } return false; } } private static void TrackSharedUpgrade(string upgradeType, int amount) { lock (SharedUpgradesLock) { if (!_sharedUpgrades.TryGetValue(upgradeType, out var value)) { value = 0; } _sharedUpgrades[upgradeType] = value + amount; Logger.LogInfo((object)$"[LuckyUpgrades] Tracked: {upgradeType} (total: {_sharedUpgrades[upgradeType]})"); } } public static void ItemUpgrade_PlayUpgrade_Postfix(ItemUpgrade __instance) { try { if (Interlocked.CompareExchange(ref _isApplyingSharedUpgrade, 0, 0) == 1) { return; } string mySteamID = GetMySteamID(); string upgradeType = GetUpgradeType(__instance); if (string.IsNullOrEmpty(upgradeType)) { return; } string steamIDFromItem = GetSteamIDFromItem(__instance); if (string.IsNullOrEmpty(steamIDFromItem) || string.IsNullOrEmpty(mySteamID) || mySteamID == steamIDFromItem) { return; } int? chanceOverride = null; lock (ModdedUpgradeRegistryLock) { if (_moddedUpgradeRegistry.TryGetValue(upgradeType, out (Action<string, int>, Func<int>) value)) { chanceOverride = value.Item2(); } } ApplySharedUpgradeToSelf(upgradeType, steamIDFromItem, 1, delegate { string mySteamID2 = GetMySteamID(); if (!string.IsNullOrEmpty(mySteamID2)) { ApplyUpgradeByType(upgradeType, mySteamID2); } }, chanceOverride); } catch (Exception ex) { Logger.LogError((object)("[LuckyUpgrades] Error in ItemUpgrade_PlayUpgrade_Postfix: " + ex.Message + "\n" + ex.StackTrace)); } } private static string GetUpgradeType(ItemUpgrade item) { GameObject gameObject = ((Component)item).gameObject; if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerHealth>() != (Object)null) { return "Health"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerEnergy>() != (Object)null) { return "Energy"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerExtraJump>() != (Object)null) { return "ExtraJump"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerGrabRange>() != (Object)null) { return "GrabRange"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerGrabStrength>() != (Object)null) { return "GrabStrength"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerGrabThrow>() != (Object)null) { return "GrabThrow"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerSprintSpeed>() != (Object)null) { return "SprintSpeed"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerTumbleLaunch>() != (Object)null) { return "TumbleLaunch"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerTumbleClimb>() != (Object)null) { return "TumbleClimb"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerTumbleWings>() != (Object)null) { return "TumbleWings"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradePlayerCrouchRest>() != (Object)null) { return "CrouchRest"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradeDeathHeadBattery>() != (Object)null) { return "DeathHeadBattery"; } if ((Object)(object)gameObject.GetComponent<ItemUpgradeMapPlayerCount>() != (Object)null) { return "MapPlayerCount"; } return TryGetREPOLibUpgradeId(gameObject); } private static string TryGetREPOLibUpgradeId(GameObject go) { try { if (!_repoLibTypeSearched.GetValueOrDefault()) { _repoLibTypeSearched = false; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { Type[] types = assembly.GetTypes(); foreach (Type type in types) { if (type.Name == "REPOLibItemUpgrade") { _repoLibItemUpgradeType = type; break; } } } catch { } if (_repoLibItemUpgradeType != null) { break; } } if (_repoLibItemUpgradeType != null) { Logger.LogInfo((object)"[LuckyUpgrades] Found REPOLibItemUpgrade — resolving upgradeId field..."); _repoLibUpgradeIdField = _repoLibItemUpgradeType.GetField("upgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? _repoLibItemUpgradeType.GetField("_upgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? _repoLibItemUpgradeType.GetField("UpgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _repoLibTypeSearched = true; } } if (_repoLibItemUpgradeType == null) { return null; } Component component = go.GetComponent(_repoLibItemUpgradeType); if ((Object)(object)component == (Object)null) { return null; } if (_repoLibUpgradeIdField == null) { Logger.LogWarning((object)"[LuckyUpgrades] upgradeId field not resolved on REPOLibItemUpgrade — cannot share this upgrade type."); return null; } string text = _repoLibUpgradeIdField.GetValue(component) as string; if (string.IsNullOrEmpty(text)) { return null; } lock (ModdedUpgradeRegistryLock) { if (!_moddedUpgradeRegistry.ContainsKey(text)) { int defaultChance = UpgradeConfiguration?.DefaultModdedUpgradeChance.Value ?? 25; ConfigEntry<int> configEntry = UpgradeConfiguration?.BindModdedUpgrade(text, defaultChance); Func<int> func = ((configEntry != null) ? ((Func<int>)(() => configEntry.Value)) : ((Func<int>)(() => UpgradeConfiguration?.DefaultModdedUpgradeChance.Value ?? 25))); string capturedId = text; _moddedUpgradeRegistry[capturedId] = (delegate(string steamID, int amount) { ApplyREPOLibUpgrade(capturedId, steamID, amount); }, func); Logger.LogInfo((object)$"[LuckyUpgrades] Auto-registered REPOLib upgrade: '{capturedId}' ({func()}%)"); } } return text; } catch (Exception ex) { Logger.LogWarning((object)("[LuckyUpgrades] TryGetREPOLibUpgradeId failed: " + ex.Message)); return null; } } private static void PreBindREPOLibUpgrades() { try { Type type = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetType("REPOLib.Modules.Upgrades"); } catch { } if (type != null) { break; } } if (type == null) { Logger.LogInfo((object)"[LuckyUpgrades] REPOLib.Modules.Upgrades not found at startup — will bind on first encounter instead."); return; } List<object> list = new List<object>(); string[] array = new string[3] { "GetAll", "GetUpgrades", "GetRegistered" }; foreach (string name in array) { MethodInfo method = type.GetMethod(name, BindingFlags.Static | BindingFlags.Public); if (method == null) { continue; } object obj2 = method.Invoke(null, null); if (obj2 is IEnumerable enumerable) { foreach (object item in enumerable) { if (item != null) { list.Add(item); } } } if (list.Count > 0) { break; } } if (list.Count == 0) { FieldInfo[] fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { object value = fieldInfo.GetValue(null); if (!(value is IDictionary dictionary)) { continue; } foreach (object value2 in dictionary.Values) { if (value2 != null) { list.Add(value2); } } if (list.Count > 0) { break; } } } if (list.Count == 0) { Logger.LogInfo((object)"[LuckyUpgrades] Could not enumerate REPOLib upgrades at startup — will bind on first encounter."); return; } int defaultChance = UpgradeConfiguration?.DefaultModdedUpgradeChance.Value ?? 25; int num = 0; foreach (object item2 in list) { Type type2 = item2.GetType(); FieldInfo fieldInfo2 = type2.GetField("_upgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type2.GetField("upgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type2.GetField("UpgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); PropertyInfo propertyInfo = type2.GetProperty("UpgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type2.GetProperty("upgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); string text = (fieldInfo2?.GetValue(item2) ?? propertyInfo?.GetValue(item2)) as string; Logger.LogInfo((object)("[LuckyUpgrades] Pre-bind: found REPOLib upgrade id='" + (text ?? "NULL") + "'")); if (string.IsNullOrEmpty(text)) { continue; } lock (ModdedUpgradeRegistryLock) { if (!_moddedUpgradeRegistry.ContainsKey(text)) { ConfigEntry<int> configEntry = UpgradeConfiguration?.BindModdedUpgrade(text, defaultChance); Func<int> func = ((configEntry != null) ? ((Func<int>)(() => configEntry.Value)) : ((Func<int>)(() => UpgradeConfiguration?.DefaultModdedUpgradeChance.Value ?? 25))); string capturedId = text; _moddedUpgradeRegistry[capturedId] = (delegate(string steamID, int amount) { ApplyREPOLibUpgrade(capturedId, steamID, amount); }, func); num++; Logger.LogInfo((object)$"[LuckyUpgrades] Pre-bound '{capturedId}' ({func()}%) to config"); } } } Logger.LogInfo((object)$"[LuckyUpgrades] Pre-bound {num} REPOLib upgrade(s) to config ✓"); } catch (Exception ex) { Logger.LogWarning((object)("[LuckyUpgrades] PreBindREPOLibUpgrades failed: " + ex.Message)); } } private static void ApplyREPOLibUpgrade(string upgradeId, string steamID, int amount) { try { PlayerAvatar val = null; foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if (SemiFunc.PlayerGetSteamID(item) == steamID) { val = item; break; } } if ((Object)(object)val == (Object)null) { Logger.LogWarning((object)("[LuckyUpgrades] ApplyREPOLibUpgrade: no PlayerAvatar for steamID '" + steamID + "'")); return; } if (_repoLibUpgradesModuleType == null) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { _repoLibUpgradesModuleType = assembly.GetType("REPOLib.Modules.Upgrades"); } catch { } if (_repoLibUpgradesModuleType != null) { break; } } } if (_repoLibUpgradesModuleType == null) { Logger.LogError((object)"[LuckyUpgrades] Cannot find REPOLib.Modules.Upgrades"); return; } if (_repoLibGetUpgradeMethod == null) { _repoLibGetUpgradeMethod = _repoLibUpgradesModuleType.GetMethod("GetUpgrade", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(string) }, null); } if (_repoLibGetUpgradeMethod == null) { Logger.LogError((object)"[LuckyUpgrades] REPOLib.Modules.Upgrades.GetUpgrade(string) not found"); return; } MethodInfo repoLibGetUpgradeMethod = _repoLibGetUpgradeMethod; object obj2 = repoLibGetUpgradeMethod.Invoke(null, new object[1] { upgradeId }); if (obj2 == null) { Logger.LogWarning((object)("[LuckyUpgrades] REPOLib upgrade '" + upgradeId + "' not in registry")); return; } Type type = obj2.GetType(); MethodInfo method = type.GetMethod("AddLevel", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { typeof(PlayerAvatar), typeof(int) }, null); MethodInfo method2 = type.GetMethod("Upgrade", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(PlayerAvatar) }, null); MethodInfo method3 = type.GetMethod("AddLevel", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(PlayerAvatar) }, null); if (method != null) { method.Invoke(obj2, new object[2] { val, amount }); Logger.LogInfo((object)$"[LuckyUpgrades] Applied REPOLib upgrade '{upgradeId}' to {steamID} x{amount} via AddLevel(PlayerAvatar, int) ✓"); return; } if (method2 != null) { for (int j = 0; j < amount; j++) { method2.Invoke(obj2, new object[1] { val }); } Logger.LogInfo((object)$"[LuckyUpgrades] Applied REPOLib upgrade '{upgradeId}' to {steamID} x{amount} via Upgrade(PlayerAvatar) ✓"); return; } if (method3 != null) { for (int k = 0; k < amount; k++) { method3.Invoke(obj2, new object[1] { val }); } Logger.LogInfo((object)$"[LuckyUpgrades] Applied REPOLib upgrade '{upgradeId}' to {steamID} x{amount} via AddLevel(PlayerAvatar) ✓"); return; } Logger.LogWarning((object)("[LuckyUpgrades] No applicable upgrade method found on PlayerUpgrade for '" + upgradeId + "'. Methods available:")); MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { Logger.LogWarning((object)("[LuckyUpgrades] " + methodInfo.Name + "(" + string.Join(", ", Array.ConvertAll(methodInfo.GetParameters(), (ParameterInfo p) => p.ParameterType.Name)) + ")")); } } catch (Exception ex) { Logger.LogError((object)("[LuckyUpgrades] ApplyREPOLibUpgrade failed: " + ex.Message + "\n" + ex.StackTrace)); } } private static void ApplyUpgradeByType(string upgradeType, string steamID, int amount = 1) { switch (upgradeType) { case "Health": { for (int num4 = 0; num4 < amount; num4++) { PunManager.instance.UpgradePlayerHealth(steamID, 1); } return; } case "Energy": { for (int j = 0; j < amount; j++) { PunManager.instance.UpgradePlayerEnergy(steamID, 1); } return; } case "ExtraJump": { for (int n = 0; n < amount; n++) { PunManager.instance.UpgradePlayerExtraJump(steamID, 1); } return; } case "GrabRange": { for (int num6 = 0; num6 < amount; num6++) { PunManager.instance.UpgradePlayerGrabRange(steamID, 1); } return; } case "GrabStrength": { for (int num2 = 0; num2 < amount; num2++) { PunManager.instance.UpgradePlayerGrabStrength(steamID, 1); } return; } case "GrabThrow": { for (int l = 0; l < amount; l++) { PunManager.instance.UpgradePlayerThrowStrength(steamID, 1); } return; } case "SprintSpeed": { for (int num7 = 0; num7 < amount; num7++) { PunManager.instance.UpgradePlayerSprintSpeed(steamID, 1); } return; } case "TumbleLaunch": { for (int num5 = 0; num5 < amount; num5++) { PunManager.instance.UpgradePlayerTumbleLaunch(steamID, 1); } return; } case "MapPlayerCount": { for (int num3 = 0; num3 < amount; num3++) { PunManager.instance.UpgradeMapPlayerCount(steamID, 1); } return; } case "TumbleClimb": { for (int num = 0; num < amount; num++) { PunManager.instance.UpgradePlayerTumbleClimb(steamID, 1); } return; } case "TumbleWings": { for (int m = 0; m < amount; m++) { PunManager.instance.UpgradePlayerTumbleWings(steamID, 1); } return; } case "CrouchRest": { for (int k = 0; k < amount; k++) { PunManager.instance.UpgradePlayerCrouchRest(steamID, 1); } return; } case "DeathHeadBattery": { for (int i = 0; i < amount; i++) { PunManager.instance.UpgradeDeathHeadBattery(steamID, 1); } return; } } lock (ModdedUpgradeRegistryLock) { if (_moddedUpgradeRegistry.TryGetValue(upgradeType, out (Action<string, int>, Func<int>) value)) { value.Item1(steamID, amount); } else { Logger.LogWarning((object)("[LuckyUpgrades] ApplyUpgradeByType: no handler for '" + upgradeType + "'")); } } } private static string GetSteamIDFromItem(ItemUpgrade item) { if ((Object)(object)item == (Object)null) { return null; } try { FieldInfo field = ((object)item).GetType().GetField("playerAvatar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object? value = field.GetValue(item); PlayerAvatar val = (PlayerAvatar)((value is PlayerAvatar) ? value : null); if ((Object)(object)val != (Object)null) { return SemiFunc.PlayerGetSteamID(val); } } PhysGrabObject component = ((Component)item).GetComponent<PhysGrabObject>(); if ((Object)(object)component != (Object)null) { List<PlayerAvatar> list = SemiFunc.PhysGrabObjectGetPlayerAvatarsGrabbing(component); if (list != null && list.Count > 0) { return SemiFunc.PlayerGetSteamID(list[0]); } } } catch (Exception ex) { Logger.LogWarning((object)("[LuckyUpgrades] GetSteamIDFromItem failed: " + ex.Message)); } return null; } private static void ApplySharedUpgradeToSelf(string upgradeType, string sourceSteamID, int amount, Action<int> applyToSelf, int? chanceOverride = null) { try { int num = chanceOverride ?? UpgradeConfiguration.GetShareChance(upgradeType); if (num <= 0) { Logger.LogInfo((object)("[LuckyUpgrades] Shared upgrade skipped: " + upgradeType + " (0% chance) ✗")); return; } if (num >= 100) { bool flag = false; try { Interlocked.Exchange(ref _isApplyingSharedUpgrade, 1); applyToSelf(amount); flag = true; } finally { Interlocked.Exchange(ref _isApplyingSharedUpgrade, 0); } if (flag) { TrackSharedUpgrade(upgradeType, amount); Logger.LogInfo((object)$"[LuckyUpgrades] Shared upgrade applied: {upgradeType} +{amount} (100% guaranteed) ✓"); } return; } int num2; lock (_randomLock) { num2 = _random.Next(100); } if (num2 < num) { bool flag2 = false; try { Interlocked.Exchange(ref _isApplyingSharedUpgrade, 1); applyToSelf(amount); flag2 = true; } finally { Interlocked.Exchange(ref _isApplyingSharedUpgrade, 0); } if (flag2) { TrackSharedUpgrade(upgradeType, amount); Logger.LogInfo((object)$"[LuckyUpgrades] Shared upgrade applied: {upgradeType} +{amount} (rolled: {num2} | chance: {num}%) ✓"); } } else { Logger.LogInfo((object)$"[LuckyUpgrades] Shared upgrade missed: {upgradeType} (rolled: {num2} | chance: {num}%) ✗"); } } catch (Exception ex) { Logger.LogError((object)("[LuckyUpgrades] Error in ApplySharedUpgradeToSelf: " + ex.Message + "\n" + ex.StackTrace)); } } } public class UpgradeReapplyRunner : MonoBehaviour { private static readonly HashSet<string> SESSION_END_LEVELS = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Level - Main Menu", "Level - Lobby Menu", "Level - MainMenu", "Level - LobbyMenu", "Main Menu", "Lobby", "Lobby Menu" }; private string _lastLevelName = ""; private float _reapplyDelay = 0f; private bool _pendingReapply = false; private const float REAPPLY_DELAY_SECONDS = 3f; private static bool IsSessionEndLevel(string levelName) { if (SESSION_END_LEVELS.Contains(levelName)) { return true; } return levelName.IndexOf("menu", StringComparison.OrdinalIgnoreCase) >= 0 || levelName.IndexOf("lobby", StringComparison.OrdinalIgnoreCase) >= 0; } private void Update() { string text = ""; try { if ((Object)(object)RunManager.instance != (Object)null && (Object)(object)RunManager.instance.levelCurrent != (Object)null) { text = ((Object)RunManager.instance.levelCurrent).name; } } catch { } if (!string.IsNullOrEmpty(text) && text != _lastLevelName) { Plugin.Logger.LogInfo((object)("[LuckyUpgrades] Level changed: " + _lastLevelName + " -> " + text)); _lastLevelName = text; if (IsSessionEndLevel(text)) { lock (Plugin.SharedUpgradesLock) { Plugin._sharedUpgrades.Clear(); } lock (Plugin.MySteamIDLock) { Plugin._mySteamID = null; } Plugin.Logger.LogInfo((object)"[LuckyUpgrades] Session ended. All tracked data cleared."); return; } lock (Plugin.SharedUpgradesLock) { if (!PhotonNetwork.IsMasterClient && Plugin._sharedUpgrades.Count > 0) { _pendingReapply = true; _reapplyDelay = 3f; Plugin.Logger.LogInfo((object)$"[LuckyUpgrades] Scheduled reapply in {3f}s..."); } else if (PhotonNetwork.IsMasterClient && Plugin._sharedUpgrades.Count > 0) { Plugin.Logger.LogInfo((object)"[LuckyUpgrades] Host detected — skipping reapply (upgrades persist natively)."); } } } if (!_pendingReapply) { return; } _reapplyDelay -= Time.deltaTime; if (!(_reapplyDelay <= 0f)) { return; } PlayerAvatar val = SemiFunc.PlayerAvatarLocal(); if ((Object)(object)val == (Object)null) { _reapplyDelay = 0.5f; return; } string mySteamID = Plugin.GetMySteamID(); if (string.IsNullOrEmpty(mySteamID)) { _reapplyDelay = 0.5f; return; } _pendingReapply = false; Plugin.ReapplySharedUpgrades(); } } public class UpgradeConfig { private readonly ConfigFile _config; public ConfigEntry<int> ChanceToActivatePlayerHealth { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerEnergy { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerSprintSpeed { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerExtraJump { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerTumbleLaunch { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerGrabRange { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerGrabStrength { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerGrabThrow { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerTumbleClimb { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerTumbleWings { get; private set; } public ConfigEntry<int> ChanceToActivatePlayerCrouchRest { get; private set; } public ConfigEntry<int> ChanceToActivateDeathHeadBattery { get; private set; } public ConfigEntry<int> ChanceToActivateMapPlayerCount { get; private set; } public ConfigEntry<int> DefaultModdedUpgradeChance { get; private set; } public UpgradeConfig(ConfigFile config) { //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Expected O, but got Unknown _config = config; ChanceToActivatePlayerHealth = Bind("ChanceToActivatePlayerHealth", "% Chance to share the Health upgrade"); ChanceToActivatePlayerEnergy = Bind("ChanceToActivatePlayerEnergy", "% Chance to share the Energy (Stamina) upgrade"); ChanceToActivatePlayerSprintSpeed = Bind("ChanceToActivatePlayerSprintSpeed", "% Chance to share the Sprint Speed upgrade"); ChanceToActivatePlayerExtraJump = Bind("ChanceToActivatePlayerExtraJump", "% Chance to share the Extra Jump upgrade"); ChanceToActivatePlayerTumbleLaunch = Bind("ChanceToActivatePlayerTumbleLaunch", "% Chance to share the Tumble Launch upgrade"); ChanceToActivatePlayerGrabRange = Bind("ChanceToActivatePlayerGrabRange", "% Chance to share the Grab Range upgrade"); ChanceToActivatePlayerGrabStrength = Bind("ChanceToActivatePlayerGrabStrength", "% Chance to share the Grab Strength upgrade"); ChanceToActivatePlayerGrabThrow = Bind("ChanceToActivatePlayerGrabThrow", "% Chance to share the Grab Throw upgrade"); ChanceToActivatePlayerTumbleClimb = Bind("ChanceToActivatePlayerTumbleClimb", "% Chance to share the Tumble Climb upgrade"); ChanceToActivatePlayerTumbleWings = Bind("ChanceToActivatePlayerTumbleWings", "% Chance to share the Tumble Wings upgrade"); ChanceToActivatePlayerCrouchRest = Bind("ChanceToActivatePlayerCrouchRest", "% Chance to share the Crouch Rest upgrade"); ChanceToActivateDeathHeadBattery = Bind("ChanceToActivateDeathHeadBattery", "% Chance to share the Death Head Battery upgrade"); ChanceToActivateMapPlayerCount = Bind("ChanceToActivateMapPlayerCount", "% Chance to share the Map Player Count upgrade"); DefaultModdedUpgradeChance = config.Bind<int>("ModdedUpgrades", "DefaultModdedUpgradeChance", 25, new ConfigDescription("Default % chance used for modded upgrades that don't specify their own share chance. Also used as a fallback for any unrecognised upgrade type.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); } public int GetShareChance(string upgradeType) { switch (upgradeType) { case "Health": return ChanceToActivatePlayerHealth.Value; case "Energy": return ChanceToActivatePlayerEnergy.Value; case "SprintSpeed": return ChanceToActivatePlayerSprintSpeed.Value; case "ExtraJump": return ChanceToActivatePlayerExtraJump.Value; case "TumbleLaunch": return ChanceToActivatePlayerTumbleLaunch.Value; case "TumbleClimb": return ChanceToActivatePlayerTumbleClimb.Value; case "TumbleWings": return ChanceToActivatePlayerTumbleWings.Value; case "CrouchRest": return ChanceToActivatePlayerCrouchRest.Value; case "GrabRange": return ChanceToActivatePlayerGrabRange.Value; case "GrabStrength": return ChanceToActivatePlayerGrabStrength.Value; case "GrabThrow": return ChanceToActivatePlayerGrabThrow.Value; case "MapPlayerCount": return ChanceToActivateMapPlayerCount.Value; case "DeathHeadBattery": return ChanceToActivateDeathHeadBattery.Value; default: { ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogWarning((object)("[LuckyUpgrades] GetShareChance: unknown upgrade type '" + upgradeType + "'. " + $"Using DefaultModdedUpgradeChance ({DefaultModdedUpgradeChance.Value}%).")); } return DefaultModdedUpgradeChance.Value; } } } public ConfigEntry<int> BindModdedUpgrade(string upgradeId, int defaultChance = 25) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown defaultChance = Math.Max(0, Math.Min(100, defaultChance)); return _config.Bind<int>("ModdedUpgrades", upgradeId, defaultChance, new ConfigDescription("% Chance to share the '" + upgradeId + "' upgrade (added by another mod)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); } private ConfigEntry<int> Bind(string key, string description, int defaultValue = 25) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown return _config.Bind<int>("Upgrades", key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "LuckyUpgradesFork"; public const string PLUGIN_NAME = "LuckyUpgradesFork"; public const string PLUGIN_VERSION = "1.0.0"; } }