using 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 _sharedUpgrades = new Dictionary(); internal static readonly Dictionary apply, Func getChance)> _moddedUpgradeRegistry = new Dictionary, Func)>(); 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 hashSet = new HashSet { "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(); Object.DontDestroyOnLoad((Object)(object)val2); ((Object)val2).hideFlags = (HideFlags)61; PreBindREPOLibUpgrades(); Logger.LogInfo((object)"Harmony patches applied!"); } public static void RegisterModdedUpgrade(string upgradeId, Action 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 configEntry = UpgradeConfiguration?.BindModdedUpgrade(upgradeId, shareChance); Func func = ((configEntry != null) ? ((Func)(() => configEntry.Value)) : ((Func)(() => 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 applyDelegate; int value2; lock (ModdedUpgradeRegistryLock) { if (!_moddedUpgradeRegistry.TryGetValue(upgradeId, out (Action, Func) 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 dictionary; lock (SharedUpgradesLock) { if (_sharedUpgrades.Count == 0) { return; } dictionary = new Dictionary(_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 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, Func) 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, Func) 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() != (Object)null) { return "Health"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "Energy"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "ExtraJump"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "GrabRange"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "GrabStrength"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "GrabThrow"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "SprintSpeed"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "TumbleLaunch"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "TumbleClimb"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "TumbleWings"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "CrouchRest"; } if ((Object)(object)gameObject.GetComponent() != (Object)null) { return "DeathHeadBattery"; } if ((Object)(object)gameObject.GetComponent() != (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 configEntry = UpgradeConfiguration?.BindModdedUpgrade(text, defaultChance); Func func = ((configEntry != null) ? ((Func)(() => configEntry.Value)) : ((Func)(() => 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 list = new List(); 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 configEntry = UpgradeConfiguration?.BindModdedUpgrade(text, defaultChance); Func func = ((configEntry != null) ? ((Func)(() => configEntry.Value)) : ((Func)(() => 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, Func) 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(); if ((Object)(object)component != (Object)null) { List 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 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 SESSION_END_LEVELS = new HashSet(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 ChanceToActivatePlayerHealth { get; private set; } public ConfigEntry ChanceToActivatePlayerEnergy { get; private set; } public ConfigEntry ChanceToActivatePlayerSprintSpeed { get; private set; } public ConfigEntry ChanceToActivatePlayerExtraJump { get; private set; } public ConfigEntry ChanceToActivatePlayerTumbleLaunch { get; private set; } public ConfigEntry ChanceToActivatePlayerGrabRange { get; private set; } public ConfigEntry ChanceToActivatePlayerGrabStrength { get; private set; } public ConfigEntry ChanceToActivatePlayerGrabThrow { get; private set; } public ConfigEntry ChanceToActivatePlayerTumbleClimb { get; private set; } public ConfigEntry ChanceToActivatePlayerTumbleWings { get; private set; } public ConfigEntry ChanceToActivatePlayerCrouchRest { get; private set; } public ConfigEntry ChanceToActivateDeathHeadBattery { get; private set; } public ConfigEntry ChanceToActivateMapPlayerCount { get; private set; } public ConfigEntry 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("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(0, 100), Array.Empty())); } 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 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("ModdedUpgrades", upgradeId, defaultChance, new ConfigDescription("% Chance to share the '" + upgradeId + "' upgrade (added by another mod)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); } private ConfigEntry 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("Upgrades", key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "LuckyUpgradesFork"; public const string PLUGIN_NAME = "LuckyUpgradesFork"; public const string PLUGIN_VERSION = "1.0.0"; } }