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 SharedUpgradesPlus compatibilitytweaks v1.3.1
BepInEx/plugins/SharedUpgradesPlus.CompatibilityTweaks/SharedUpgradesPlus.CompatibilityTweaks.dll
Decompiled 2 hours 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.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ByteSizeMetal")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.3.1.0")] [assembly: AssemblyInformationalVersion("1.3.1")] [assembly: AssemblyProduct("SharedUpgradesPlus-compatibilitytweaks")] [assembly: AssemblyTitle("SharedUpgradesPlus.CompatibilityTweaks")] [assembly: AssemblyMetadata("Author", "ByteSizeMetal")] [assembly: AssemblyVersion("1.3.1.0")] [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 SharedUpgradesFix { internal enum UpgradeKind { Vanilla, RepoLib, StatKey } internal sealed class UpgradeInfo { public string Key; public string Id; public string Source; public UpgradeKind Kind; public Assembly Owner; public object Target; public bool DetectedPooled; public string PooledReason; public string TeamEffect; public PropertyInfo MaxLevelProp; public string DisplayName => Catalog.Pretty(Id); public bool Pooled => UpgradeOptions.Pooled(this); public int MaxLevel() { if (MaxLevelProp == null || Target == null) { return 0; } try { object? value = MaxLevelProp.GetValue(Target); ConfigEntryBase val = (ConfigEntryBase)((value is ConfigEntryBase) ? value : null); if (val != null) { object boxedValue = val.BoxedValue; if (boxedValue is int) { return (int)boxedValue; } } } catch { } return 0; } } internal static class Catalog { public const string KeyPrefix = "playerUpgrade"; private static FieldInfo _repoDict; private static FieldInfo _idField; private static PropertyInfo _repoProp; private static FieldInfo[] _callbackFields; private static Type _repoPlayerUpgrade; private static MethodInfo _repoGetLevel; private static readonly Dictionary<string, UpgradeInfo> ByKey = new Dictionary<string, UpgradeInfo>(); private static readonly HashSet<Assembly> Scanned = new HashSet<Assembly>(); private static int _lastRepoCount = -1; public static bool RepoLibReady { get; private set; } public static IEnumerable<UpgradeInfo> All => ByKey.Values; internal static void RefreshForDisplay() { RefreshRepoLib(force: false); } public static void InitRepoLib(Assembly repoLib) { if (repoLib == null) { Plugin.Log.LogWarning((object)"REPOLib not found - REPOLib upgrades can't be identified; share-once and team-pool detection are off."); return; } Type type = repoLib.GetType("REPOLib.Modules.Upgrades"); _repoPlayerUpgrade = repoLib.GetType("REPOLib.Modules.PlayerUpgrade"); if (type == null || _repoPlayerUpgrade == null) { Plugin.Log.LogWarning((object)"REPOLib: 'REPOLib.Modules.Upgrades' or 'REPOLib.Modules.PlayerUpgrade' not found - REPOLib-specific fixes are off."); return; } _repoDict = type.GetField("_playerUpgrades", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _repoProp = type.GetProperty("PlayerUpgrades", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _idField = _repoPlayerUpgrade.GetField("UpgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _repoGetLevel = _repoPlayerUpgrade.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetLevel"); _callbackFields = new FieldInfo[2] { _repoPlayerUpgrade.GetField("_upgradeAction", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), _repoPlayerUpgrade.GetField("StartAction", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) }.Where((FieldInfo f) => f != null && typeof(Delegate).IsAssignableFrom(f.FieldType)).ToArray(); if ((_repoDict == null && _repoProp == null) || _idField == null) { Plugin.Log.LogWarning((object)"REPOLib: upgrade list ('Upgrades._playerUpgrades'/'PlayerUpgrades') or 'PlayerUpgrade.UpgradeId' not found - REPOLib-specific fixes are off."); } else { RepoLibReady = true; } } public static bool TryGet(string key, out UpgradeInfo info) { info = null; if (key == null || !key.StartsWith("playerUpgrade", StringComparison.Ordinal)) { return false; } RefreshRepoLib(force: false); return ByKey.TryGetValue(key, out info); } public static bool IsRepoLib(string key) { if (TryGet(key, out var info)) { return info.Kind == UpgradeKind.RepoLib; } return false; } public static void Rebuild(IEnumerable<string> supVanilla, IEnumerable<string> supModded) { foreach (string item in supVanilla ?? Enumerable.Empty<string>()) { if (!ByKey.ContainsKey(item)) { ByKey[item] = new UpgradeInfo { Key = item, Id = Strip(item), Source = "R.E.P.O. (vanilla)", Kind = UpgradeKind.Vanilla }; } } RefreshRepoLib(force: true); foreach (string item2 in supModded ?? Enumerable.Empty<string>()) { if (!ByKey.ContainsKey(item2)) { ByKey[item2] = new UpgradeInfo { Key = item2, Id = Strip(item2), Source = "unknown mod (stat key only)", Kind = UpgradeKind.StatKey }; } } ScanNewOwners(); UpgradeOptions.Register(ByKey.Values); } private static void RefreshRepoLib(bool force) { if (!RepoLibReady) { return; } List<object> list; try { if (_repoDict?.GetValue(null) is IDictionary dictionary) { if (!force && dictionary.Count == _lastRepoCount) { return; } _lastRepoCount = dictionary.Count; list = dictionary.Values.Cast<object>().ToList(); } else { list = (_repoProp.GetValue(null) as IEnumerable)?.Cast<object>().ToList() ?? new List<object>(); if (!force && list.Count == _lastRepoCount) { return; } _lastRepoCount = list.Count; } } catch (Exception ex) { Plugin.Debug("reading REPOLib upgrades failed: " + ex.Message); return; } bool flag = false; foreach (object pu in list) { string text = ((pu == null) ? null : (_idField.GetValue(pu) as string)); if (string.IsNullOrEmpty(text)) { continue; } string key = "playerUpgrade" + text; if (!ByKey.TryGetValue(key, out var value) || value.Kind != UpgradeKind.RepoLib) { Delegate? obj = _callbackFields.Select((FieldInfo f) => f.GetValue(pu) as Delegate).FirstOrDefault((Delegate x) => (object)x != null); Assembly assembly = obj?.Method?.DeclaringType?.Assembly; if (assembly == null) { assembly = MoreInterop.NativeOwner(pu); } object obj2 = obj?.Target; UpgradeInfo upgradeInfo = new UpgradeInfo { Key = key, Id = text, Kind = UpgradeKind.RepoLib, Owner = assembly, Target = obj2, Source = (PluginName(assembly) ?? "unknown REPOLib mod") }; PropertyInfo propertyInfo = obj2?.GetType().GetProperty("MaxLevel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null && typeof(ConfigEntryBase).IsAssignableFrom(propertyInfo.PropertyType)) { upgradeInfo.MaxLevelProp = propertyInfo; } upgradeInfo.TeamEffect = TeamEffectCompat.Description(upgradeInfo); if (upgradeInfo.TeamEffect != null) { Mark(upgradeInfo, upgradeInfo.TeamEffect); } ByKey[key] = upgradeInfo; flag = true; } } if (flag && !force) { ScanNewOwners(); UpgradeOptions.Register(ByKey.Values); } } private static string PluginName(Assembly a) { if (a == null) { return null; } foreach (PluginInfo value in Chainloader.PluginInfos.Values) { if ((Object)(object)value.Instance != (Object)null && ((object)value.Instance).GetType().Assembly == a) { return $"{value.Metadata.Name} {value.Metadata.Version}"; } } return a.GetName().Name; } private static void ScanNewOwners() { foreach (Assembly item in (from u in ByKey.Values where u.Kind == UpgradeKind.RepoLib && u.Owner != null select u.Owner).Distinct().ToList()) { if (Scanned.Add(item)) { try { ScanAssembly(item); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Team-pool scan of " + item.GetName().Name + " failed: " + ex.Message)); } } } } private static void ScanAssembly(Assembly asm) { List<UpgradeInfo> source = ByKey.Values.Where((UpgradeInfo u) => u.Owner == asm).ToList(); Dictionary<string, UpgradeInfo> dictionary = source.GroupBy<UpgradeInfo, string>((UpgradeInfo u) => u.Id, StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<string, UpgradeInfo>, string, UpgradeInfo>((IGrouping<string, UpgradeInfo> g) => g.Key, (IGrouping<string, UpgradeInfo> g) => g.First(), StringComparer.OrdinalIgnoreCase); Dictionary<Type, List<UpgradeInfo>> dictionary2 = (from u in source where u.Target != null group u by u.Target.GetType()).ToDictionary((IGrouping<Type, UpgradeInfo> g) => g.Key, (IGrouping<Type, UpgradeInfo> g) => g.ToList()); int num = 0; foreach (Type item in SafeTypes(asm)) { MethodInfo[] methods; try { methods = item.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch { continue; } if (dictionary2.TryGetValue(item, out var value) && methods.Any(IsTeamTotalName)) { foreach (UpgradeInfo item2 in value) { Mark(item2, item.Name + " declares " + methods.First(IsTeamTotalName).Name + "()"); } } MethodInfo[] array = methods; foreach (MethodInfo methodInfo in array) { List<KeyValuePair<OpCode, object>> list; try { if (methodInfo.IsAbstract || methodInfo.GetMethodBody() == null) { continue; } list = PatchProcessor.ReadMethodBody((MethodBase)methodInfo).ToList(); num++; goto IL_0245; } catch { } continue; IL_0245: bool flag = list.Any((KeyValuePair<OpCode, object> i) => IsPlayerCollection(i.Value)); UpgradeInfo upgradeInfo = null; Type type = null; for (int num3 = 0; num3 < list.Count; num3++) { OpCode key = list[num3].Key; object value2 = list[num3].Value; if (key == OpCodes.Ldstr && value2 is string text) { string key2 = (text.StartsWith("playerUpgrade", StringComparison.Ordinal) ? text.Substring("playerUpgrade".Length) : text); if (dictionary.TryGetValue(key2, out var value3)) { upgradeInfo = value3; } } else if (value2 is FieldInfo fieldInfo && dictionary2.ContainsKey(fieldInfo.FieldType)) { type = fieldInfo.FieldType; } else { if (!(value2 is MethodInfo methodInfo2)) { continue; } if (IsTeamTotalName(methodInfo2)) { Type type2 = (dictionary2.ContainsKey(methodInfo2.DeclaringType) ? methodInfo2.DeclaringType : type); if (!(type2 != null)) { continue; } foreach (UpgradeInfo item3 in dictionary2[type2]) { Mark(item3, item.Name + "." + methodInfo.Name + " calls " + methodInfo2.Name + "()"); } } else if (dictionary2.ContainsKey(methodInfo2.ReturnType)) { type = methodInfo2.ReturnType; } else { if (!flag || !(_repoGetLevel != null) || !(methodInfo2.DeclaringType == _repoPlayerUpgrade) || !(methodInfo2.Name == "GetLevel") || upgradeInfo == null) { continue; } int num4 = num3 + 1; int num5 = 0; for (; num4 < list.Count; num4++) { if (num5 >= 3) { break; } if (!(list[num4].Key == OpCodes.Nop)) { num5++; if (list[num4].Key == OpCodes.Add) { Mark(upgradeInfo, item.Name + "." + methodInfo.Name + " adds up players' levels"); break; } } } } } } } } Plugin.Debug($"team-pool scan of {asm.GetName().Name}: {num} methods"); } private static void Mark(UpgradeInfo u, string why) { if (!u.DetectedPooled) { u.DetectedPooled = true; u.PooledReason = why; } } private static bool IsTeamTotalName(MethodInfo m) { if (m.Name.StartsWith("GetTotal", StringComparison.Ordinal)) { return m.Name.EndsWith("Levels", StringComparison.Ordinal); } return false; } private static bool IsPlayerCollection(object operand) { if (operand is MethodInfo methodInfo) { if (!(methodInfo.Name == "PlayerGetAll")) { return methodInfo.Name == "get_PlayerList"; } return true; } if (operand is FieldInfo fieldInfo) { if (!(fieldInfo.Name == "PlayerList")) { return fieldInfo.Name == "playerGrabbing"; } return true; } return false; } private static IEnumerable<Type> SafeTypes(Assembly a) { try { return a.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type t) => t != null); } } public static string Strip(string key) { if (!key.StartsWith("playerUpgrade", StringComparison.Ordinal)) { return key; } return key.Substring("playerUpgrade".Length); } public static string Pretty(string id) { if (string.IsNullOrEmpty(id) || id.Contains(' ')) { return id; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < id.Length; i++) { if (i > 0 && char.IsUpper(id[i]) && !char.IsUpper(id[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(id[i]); } return stringBuilder.ToString(); } public static bool MatchesAny(string list, UpgradeInfo u) { if (string.IsNullOrWhiteSpace(list)) { return false; } string[] array = list.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); int num = text.LastIndexOf(':'); string a = ((num >= 0) ? text.Substring(num + 1).Trim() : text); if (string.Equals(a, u.Id, StringComparison.OrdinalIgnoreCase) || string.Equals(a, u.Key, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } public static void LogCatalog() { if (ByKey.Count == 0) { return; } Plugin.Log.LogInfo((object)$"Upgrades SharedUpgradesPlus can share ({ByKey.Count}):"); foreach (IGrouping<string, UpgradeInfo> item in from u in ByKey.Values group u by u.Source into g orderby (!(g.Key == "R.E.P.O. (vanilla)")) ? g.Key : "" select g) { IEnumerable<string> values = item.OrderBy((UpgradeInfo u) => u.Id).Select(delegate(UpgradeInfo u) { List<string> list = new List<string>(); if (UpgradeOptions.Effective(u) == UpgradeHandling.Consumable) { list.Add("consumable: no highest-level refill"); } if (!UpgradeOptions.SharingAllowed(u)) { list.Add("sharing disabled in patch options"); } if (u.Pooled && Plugin.SkipTeamPooled.Value) { list.Add("Team: one contribution per purchase"); } int num = u.MaxLevel(); if (num > 0 && Plugin.CapAtModMaxLevel.Value) { list.Add($"max {num}"); } return (list.Count != 0) ? (u.DisplayName + " (" + string.Join(", ", list) + ")") : u.DisplayName; }); Plugin.Log.LogInfo((object)(" [" + item.Key + "] " + string.Join(", ", values))); } foreach (UpgradeInfo item2 in ByKey.Values.Where((UpgradeInfo u) => u.Pooled && u.PooledReason != null)) { Plugin.Debug(item2.Id + ": team-pooled because " + item2.PooledReason); } } } internal static class ConnectedTeam { private static object _room; private static readonly Dictionary<int, string> Ids = new Dictionary<int, string>(); internal static string[] UpgradeIds(Dictionary<string, int> levels) { if (!SemiFunc.IsMultiplayer()) { PlayerAvatar val = SemiFunc.PlayerAvatarLocal(); string text = (((Object)(object)val == (Object)null) ? null : SemiFunc.PlayerGetSteamID(val)); if (!string.IsNullOrEmpty(text)) { return new string[1] { text }; } return Array.Empty<string>(); } if (_room != PhotonNetwork.CurrentRoom) { _room = PhotonNetwork.CurrentRoom; Ids.Clear(); } Player[] playerList = PhotonNetwork.PlayerList; HashSet<int> actors = new HashSet<int>(playerList.Select((Player p) => p.ActorNumber)); int[] array = Ids.Keys.Where((int a) => !actors.Contains(a)).ToArray(); foreach (int key in array) { Ids.Remove(key); } foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item.photonView == (Object)null) && actors.Contains(item.photonView.OwnerActorNr)) { string value = SemiFunc.PlayerGetSteamID(item); if (!string.IsNullOrEmpty(value)) { Ids[item.photonView.OwnerActorNr] = value; } } } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); Player[] array2 = playerList; foreach (Player val2 in array2) { if (Ids.TryGetValue(val2.ActorNumber, out var value2)) { hashSet.Add(value2); } else if (!string.IsNullOrEmpty(val2.UserId) && levels != null && levels.ContainsKey(val2.UserId)) { hashSet.Add(val2.UserId); } } return hashSet.ToArray(); } } internal sealed class HighestSync : MonoBehaviour { private static MethodInfo _enabled; private static MethodInfo _chance; private static MethodInfo _modded; private static MethodInfo _upgradeEnabled; private static MethodInfo _limit; private static MethodInfo _trySetLevel; private static MethodInfo _repoApply; private static FieldInfo _repoDictionary; private static bool _ready; private static bool _applying; private static float _nextCheck; private static float _nextHeartbeat; private static object _session; private static readonly Dictionary<int, Dictionary<string, int>> Reports = new Dictionary<int, Dictionary<string, int>>(); private static readonly Dictionary<int, float> Peers = new Dictionary<int, float>(); private static readonly Dictionary<string, MethodInfo> Vanilla = new Dictionary<string, MethodInfo>(); private static readonly HashSet<string> Warned = new HashSet<string>(); private static readonly HashSet<string> HostOwned = new HashSet<string>(); private static readonly FieldInfo StatsDictionaries = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries"); private static SortedDictionary<string, Dictionary<string, int>> Dictionaries => StatsDictionaries.GetValue(StatsManager.instance) as SortedDictionary<string, Dictionary<string, int>>; public static bool Active { get { if (_ready && Plugin.ReconcileHighest.Value && (bool)_enabled.Invoke(null, null)) { return (int)_chance.Invoke(null, null) == 100; } return false; } } public static void Initialize(Assembly sup, Assembly repo) { Type type = sup?.GetType("SharedUpgradesPlus.Services.ConfigService"); _enabled = (_chance = (_modded = (_upgradeEnabled = (_limit = (_trySetLevel = null))))); if (type != null) { _enabled = AccessTools.Method(type, "IsSharedUpgradesEnabled", (Type[])null, (Type[])null); _chance = AccessTools.Method(type, "SharedUpgradesChancePercentage", (Type[])null, (Type[])null); _modded = AccessTools.Method(type, "IsModdedUpgradesEnabled", (Type[])null, (Type[])null); _upgradeEnabled = AccessTools.Method(type, "IsUpgradeEnabled", new Type[1] { typeof(string) }, (Type[])null); _limit = AccessTools.Method(type, "UpgradeShareLimit", new Type[1] { typeof(string) }, (Type[])null); _trySetLevel = AccessTools.Method(sup.GetType("SharedUpgradesPlus.Services.RepoLibInterop"), "TrySetLevel", (Type[])null, (Type[])null); } Type type2 = repo?.GetType("REPOLib.Modules.PlayerUpgrade"); _repoApply = ((type2 == null) ? null : AccessTools.Method(type2, "ApplyUpgrade", new Type[2] { typeof(string), typeof(int) }, (Type[])null)); Type type3 = repo?.GetType("REPOLib.Modules.Upgrades"); _repoDictionary = ((type3 == null) ? null : AccessTools.Field(type3, "_playerUpgrades")); string[] array = new string[13] { "CrouchRest", "DeathHeadBattery", "ExtraJump", "Health", "Launch", "MapPlayerCount", "Range", "Speed", "Stamina", "Strength", "Throw", "TumbleClimb", "TumbleWings" }; string[] array2 = new string[13] { "UpgradePlayerCrouchRest", "UpgradeDeathHeadBattery", "UpgradePlayerExtraJump", "UpgradePlayerHealth", "UpgradePlayerTumbleLaunch", "UpgradeMapPlayerCount", "UpgradePlayerGrabRange", "UpgradePlayerSprintSpeed", "UpgradePlayerEnergy", "UpgradePlayerGrabStrength", "UpgradePlayerThrowStrength", "UpgradePlayerTumbleClimb", "UpgradePlayerTumbleWings" }; for (int i = 0; i < array.Length; i++) { Vanilla["playerUpgrade" + array[i]] = AccessTools.Method(typeof(PunManager), array2[i], new Type[2] { typeof(string), typeof(int) }, (Type[])null); } _ready = _enabled != null && _chance != null && _modded != null && _upgradeEnabled != null && _limit != null; if (sup != null && !_ready) { Plugin.Log.LogWarning((object)"Highest-level reconciliation unavailable: SharedUpgradesPlus configuration API changed."); } } public static bool Owns(string key) { if (Active && !UpgradeOptions.Excluded(key) && Catalog.TryGet(key, out var info) && (!Plugin.SkipTeamPooled.Value || !info.Pooled) && (info.Kind == UpgradeKind.Vanilla || (bool)_modded.Invoke(null, null)) && UpgradeOptions.SharingAllowed(info)) { return (bool)_upgradeEnabled.Invoke(null, new object[1] { key }); } return false; } internal static bool HostOwns(string key) { if (!SemiFunc.IsMasterClientOrSingleplayer()) { return HostOwned.Contains(key); } return Owns(key); } public static void Request() { _nextCheck = Math.Min(_nextCheck, Time.unscaledTime + 0.25f); } private void Update() { if (Time.unscaledTime < _nextCheck) { return; } _nextCheck = Time.unscaledTime + Mathf.Max(1f, Plugin.ReconcileSeconds.Value); try { object currentRoom = PhotonNetwork.CurrentRoom; if (_session != currentRoom) { _session = currentRoom; Reports.Clear(); Peers.Clear(); HostOwned.Clear(); _nextHeartbeat = 0f; } if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null || (Object)(object)SemiFunc.PlayerAvatarLocal() == (Object)null) { return; } if (SemiFunc.IsMasterClientOrSingleplayer()) { if (!Active) { if (PhotonNetwork.InRoom && Time.unscaledTime >= _nextHeartbeat) { _nextHeartbeat = Time.unscaledTime + 5f; Net.SendStates(Array.Empty<object>()); } return; } bool flag = Reconcile(); if (PhotonNetwork.InRoom && (flag || Time.unscaledTime >= _nextHeartbeat)) { _nextHeartbeat = Time.unscaledTime + 5f; SendStates(); } } else if (PhotonNetwork.InRoom && Time.unscaledTime >= _nextHeartbeat) { _nextHeartbeat = Time.unscaledTime + 5f; string text = SemiFunc.PlayerGetSteamID(SemiFunc.PlayerAvatarLocal()); Net.SendLevels("report", text, Snapshot(text), new int[1] { PhotonNetwork.MasterClient.ActorNumber }); } } catch (Exception ex) { Warn("tick", "Highest-level check failed; will retry: " + ex.GetBaseException().Message); } } internal static Dictionary<string, int> Snapshot(string steamId) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); if ((Object)(object)StatsManager.instance == (Object)null || string.IsNullOrEmpty(steamId)) { return dictionary; } foreach (KeyValuePair<string, Dictionary<string, int>> dictionary2 in Dictionaries) { if (dictionary2.Key.StartsWith("playerUpgrade", StringComparison.Ordinal) && !UpgradeOptions.Excluded(dictionary2.Key)) { dictionary[dictionary2.Key] = (dictionary2.Value.TryGetValue(steamId, out var value) ? value : 0); } } return dictionary; } internal static void ReceiveReport(int actor, string steamId, Dictionary<string, int> levels) { if (PhotonNetwork.IsMasterClient && Active && !((Object)(object)((IEnumerable<PlayerAvatar>)SemiFunc.PlayerGetAll()).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar p) => (Object)(object)p != (Object)null && SemiFunc.PlayerGetSteamID(p) == steamId && (Object)(object)p.photonView != (Object)null && p.photonView.OwnerActorNr == actor)) == (Object)null)) { Peers[actor] = Time.unscaledTime; Reports[actor] = levels; Request(); } } internal static void ReceiveState(string steamId, Dictionary<string, int> levels) { if (SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null) { return; } foreach (KeyValuePair<string, int> level in levels) { if (UpgradeOptions.Excluded(level.Key)) { continue; } HostOwned.Add(level.Key); if (!Dictionaries.TryGetValue(level.Key, out var value)) { continue; } int value2; int num = (value.TryGetValue(steamId, out value2) ? value2 : 0); if (level.Value > num) { try { ApplyLocal(level.Key, steamId, level.Value, num); } catch (Exception ex) { Warn(level.Key, "Applying host level for " + level.Key + " failed: " + ex.GetBaseException().Message); } } } } internal static void BeginHostState() { HostOwned.Clear(); } private static bool Reconcile() { if (_applying) { return false; } _applying = true; bool result = false; try { List<PlayerAvatar> list = (from p in SemiFunc.PlayerGetAll() where (Object)(object)p != (Object)null && !string.IsNullOrEmpty(SemiFunc.PlayerGetSteamID(p)) && (Object)(object)p.photonView != (Object)null group p by SemiFunc.PlayerGetSteamID(p) into g select g.First()).ToList(); foreach (UpgradeInfo item in Catalog.All.ToList()) { if (!Owns(item.Key) || !Dictionaries.TryGetValue(item.Key, out var value)) { continue; } List<int> list2 = new List<int>(); foreach (PlayerAvatar item2 in list) { list2.Add(value.TryGetValue(SemiFunc.PlayerGetSteamID(item2), out var value2) ? value2 : 0); if (Reports.TryGetValue(item2.photonView.OwnerActorNr, out var value3) && value3.TryGetValue(item.Key, out var value4)) { list2.Add(value4); } } int num = SyncPolicy.Target(list2, (int)_limit.Invoke(null, new object[1] { item.Key })); foreach (PlayerAvatar item3 in list) { string text = SemiFunc.PlayerGetSteamID(item3); int value5; int num2 = (value.TryGetValue(text, out value5) ? value5 : 0); if (num <= num2) { continue; } try { if (item.Kind == UpgradeKind.RepoLib && _trySetLevel != null) { if (!(bool)_trySetLevel.Invoke(null, new object[3] { item.Key, text, num })) { throw new InvalidOperationException("REPOLib SetLevel returned false"); } } else { ApplyLocal(item.Key, text, num, num2); if (PhotonNetwork.InRoom) { PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>(); Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { if (!Patched(val.ActorNumber)) { component.RPC("TesterUpgradeCommandRPC", val, new object[3] { text, item.Id, num - num2 }); } } } } result = true; Plugin.Debug($"Reconciled {SemiFunc.PlayerGetName(item3)}: {item.Id} {num2} -> {num}"); } catch (Exception ex) { Warn(item.Key, "Reconciliation for " + item.Key + " failed: " + ex.GetBaseException().Message); } } } return result; } finally { Reports.Clear(); _applying = false; } } private static bool Patched(int actor) { return Peers.ContainsKey(actor); } private static void SendStates() { List<object> list = new List<object>(); foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if (!((Object)(object)item == (Object)null) && !string.IsNullOrEmpty(SemiFunc.PlayerGetSteamID(item))) { string text = SemiFunc.PlayerGetSteamID(item); Dictionary<string, int> dictionary = (from kv in Snapshot(text) where Owns(kv.Key) select kv).ToDictionary((KeyValuePair<string, int> kv) => kv.Key, (KeyValuePair<string, int> kv) => kv.Value); if (dictionary.Count > 0) { list.Add(new object[3] { text, dictionary.Keys.ToArray(), dictionary.Values.ToArray() }); } } } Net.SendStates(list.ToArray()); } private static void ApplyLocal(string key, string steamId, int target, int current) { MethodInfo value; if (_repoDictionary?.GetValue(null) is IDictionary dictionary && dictionary.Contains(Catalog.Strip(key)) && _repoApply != null) { _repoApply.Invoke(dictionary[Catalog.Strip(key)], new object[2] { steamId, target }); } else if (Vanilla.TryGetValue(key, out value) && value != null) { value.Invoke(PunManager.instance, new object[2] { steamId, SyncPolicy.Delta(current, target) }); } else { StatsManager.instance.DictionaryUpdateValue(key, steamId, target); } } private static void Warn(string key, string message) { if (Warned.Add(key)) { Plugin.Log.LogWarning((object)message); } } } internal static class Hooks { private const string SupNs = "SharedUpgradesPlus."; private const string MoreUpgradesGuid = "bulletbot.moreupgrades"; private static int _purchaseDepth; private static int _supHookDepth; private static FieldInfo _supToggles; private static FieldInfo _supVanilla; private static FieldInfo _supModded; private static FieldInfo _statsDicts; private static PropertyInfo _ctxPlayerName; private static PropertyInfo _ctxSteamId; private static readonly HashSet<string> LoggedOnce = new HashSet<string>(); private static bool _gateAvailable; private static bool _hookBracketed; private static bool _supHooksRepoLib; public static void Apply(Harmony h, Assembly sup, Assembly repoLib) { Type type = SupType(sup, "Services.ConfigService"); Type type2 = SupType(sup, "Services.RegistryService"); Type type3 = SupType(sup, "Services.CompatibilityService"); Type type4 = SupType(sup, "Patches.PlayerUpgradePatches"); Type type5 = SupType(sup, "Services.DistributionService"); Type type6 = SupType(sup, "Models.UpgradeContext"); _supVanilla = ((type2 != null) ? AccessTools.Field(type2, "vanillaUpgrades") : null); _supModded = ((type2 != null) ? AccessTools.Field(type2, "moddedUpgrades") : null); Patch(h, type2, "RegisterAll", null, "upgrade list", null, "RegisterAllPostfix"); Patch(h, type, "IsUpgradeEnabled", new Type[1] { typeof(string) }, "team-pooled skip", null, "IsUpgradeEnabledPostfix"); Patch(h, type, "UpgradeShareLimit", new Type[1] { typeof(string) }, "max level cap", null, "UpgradeShareLimitPostfix"); if (Chainloader.PluginInfos.ContainsKey("bulletbot.moreupgrades")) { Patch(h, type3, "IsSharedByMoreUpgrades", new Type[1] { typeof(string) }, "MoreUpgrades Sync Host fix", null, "IsSharedByMoreUpgradesPostfix"); } MethodInfo methodInfo = AccessTools.Method(typeof(ItemUpgrade), "PlayerUpgrade", (Type[])null, (Type[])null); Type type7 = repoLib?.GetType("REPOLib.Modules.Upgrades"); MethodInfo methodInfo2 = ((type7 != null) ? AccessTools.Method(type7, "HandleUpgradeEvent", (Type[])null, (Type[])null) : null); int num = 0; if (methodInfo != null && Patch(h, methodInfo, "purchase origin", "PurchasePrefix", null, "PurchaseFinalizer")) { num++; } if (methodInfo2 != null && Patch(h, methodInfo2, "purchase origin", "PurchasePrefix", null, "PurchaseFinalizer")) { num++; } if (num < 2) { Plugin.Log.LogWarning((object)("Couldn't hook " + ((methodInfo == null) ? "ItemUpgrade.PlayerUpgrade" : "REPOLib Upgrades.HandleUpgradeEvent") + " - 'only real purchases' is off.")); } MethodInfo applyHook = ((type4 != null) ? AccessTools.Method(type4, "ApplyUpgradePostfix", (Type[])null, (Type[])null) : null); if (applyHook == null) { Warn("SharedUpgradesPlus.Patches.PlayerUpgradePatches.ApplyUpgradePostfix", "'only real purchases' and share-once"); } _gateAvailable = (_hookBracketed = applyHook != null && Patch(h, applyHook, "SUP REPOLib hook", "SupHookPrefix", null, "SupHookFinalizer")) && num == 2; MethodInfo methodInfo3 = ((type5 != null) ? AccessTools.Method(type5, "DistributeUpgrade", (Type[])null, (Type[])null) : null); Type type8 = repoLib?.GetType("REPOLib.Modules.PlayerUpgrade"); MethodInfo methodInfo4 = ((type8 != null) ? AccessTools.Method(type8, "ApplyUpgrade", (Type[])null, (Type[])null) : null); _supHooksRepoLib = applyHook != null && methodInfo4 != null && (Harmony.GetPatchInfo((MethodBase)methodInfo4)?.Postfixes.Any((Patch p) => p.PatchMethod == applyHook) ?? false); if (!_supHooksRepoLib) { Plugin.Log.LogWarning((object)"SharedUpgradesPlus isn't hooked into REPOLib's PlayerUpgrade.ApplyUpgrade - share-once is off (its fallback paths are the only ones that see modded purchases)."); } _ctxPlayerName = ((type6 != null) ? AccessTools.Property(type6, "PlayerName") : null); _ctxSteamId = ((type6 != null) ? AccessTools.Property(type6, "SteamID") : null); _statsDicts = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries"); if (methodInfo3 == null) { Warn("SharedUpgradesPlus.Services.DistributionService.DistributeUpgrade", "share-once and share pop-ups"); } else { Patch(h, methodInfo3, "share-once / pop-ups", "DistributePrefix", "DistributePostfix"); } _supToggles = ((type != null) ? AccessTools.Field(type, "upgradeToggles") : null); if (_supToggles != null) { Patch(h, type, "LoadModsIntoConfig", Type.EmptyTypes, "config check", null, "LoadModsIntoConfigPostfix"); } } private static void RegisterAllPostfix(object __instance) { try { List<string> supVanilla = (_supVanilla?.GetValue(__instance) as IDictionary)?.Keys.Cast<string>().ToList(); List<string> supModded = (_supModded?.GetValue(__instance) as IDictionary)?.Keys.Cast<string>().ToList(); Catalog.Rebuild(supVanilla, supModded); if (LoggedOnce.Add("catalog")) { Catalog.LogCatalog(); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Reading SharedUpgradesPlus' upgrade list failed: " + ex.Message)); } } private static void IsUpgradeEnabledPostfix(object[] __args, ref bool __result) { if (!__result || !Catalog.TryGet(Arg(__args, 0), out var u)) { return; } if (!UpgradeOptions.SharingAllowed(u)) { __result = false; } else if (Plugin.SkipTeamPooled.Value && u.Pooled) { __result = false; Once("pooled:" + u.Key, delegate { Plugin.Log.LogInfo((object)(u.DisplayName + " (" + u.Source + ") keeps one team contribution per purchase. Its named compatibility setting controls how it is handled.")); }); } } private static void UpgradeShareLimitPostfix(object[] __args, ref int __result) { if (Plugin.CapAtModMaxLevel.Value && Catalog.TryGet(Arg(__args, 0), out var info)) { int num = info.MaxLevel(); if (num > 0) { __result = ((__result <= 0) ? num : Math.Min(__result, num)); } } } private static void IsSharedByMoreUpgradesPostfix(object[] __args, ref bool __result) { if (!Plugin.ShareSyncHostUpgrades.Value) { return; } string key = Arg(__args, 0); if (key != null && MoreInterop.TryTeamSharing(Catalog.Strip(key), out var enabled)) { __result = enabled; } else if (__result && key != null && !MoreUpgradesAllowsTeam(Catalog.Strip(key))) { __result = false; Once("synchost:" + key, delegate { Plugin.Log.LogInfo((object)(Catalog.Strip(key) + ": MoreUpgrades only has 'Sync Host Upgrades' on for it, which resets clients to the host's level, so SharedUpgradesPlus shares it instead (client purchases stick).")); }); } } private static bool MoreUpgradesAllowsTeam(string id) { if (MoreInterop.TryTeamSharing(id, out var enabled)) { return enabled; } try { if (!Chainloader.PluginInfos.TryGetValue("bulletbot.moreupgrades", out var value) || (Object)(object)value.Instance == (Object)null) { return true; } ConfigEntry<bool> val = default(ConfigEntry<bool>); foreach (ConfigDefinition key in value.Instance.Config.Keys) { if (!(key.Key != "Allow Team Upgrades")) { string text = key.Section; int num = text.LastIndexOf(" (", StringComparison.Ordinal); if (num >= 0 && text.EndsWith(")", StringComparison.Ordinal)) { text = text.Substring(0, num); } if (string.Equals(Norm(text), Norm(id), StringComparison.Ordinal)) { return value.Instance.Config.TryGetEntry<bool>(key, ref val) && val.Value; } } } } catch { return true; } return false; } private static void PurchasePrefix(ref bool __state) { _purchaseDepth++; __state = true; } private static Exception PurchaseFinalizer(bool __state, Exception __exception) { if (__state) { _purchaseDepth = Math.Max(0, _purchaseDepth - 1); TeamContributionSync.Request(); } return __exception; } private static bool SupHookPrefix(object[] __args, ref bool __state) { __state = false; if (TeamContributionSync.Applying) { return false; } if (__args.Length != 0) { string text = MoreInterop.UpgradeKey(__args[0]); if (text != null && HighestSync.Owns(text)) { HighestSync.Request(); return false; } } if (Plugin.OnlyRealPurchases.Value && _gateAvailable && _purchaseDepth == 0) { Plugin.Debug("REPOLib level change outside a purchase (a mod setting it itself) - not shared."); return false; } _supHookDepth++; __state = true; return true; } private static Exception SupHookFinalizer(bool __state, Exception __exception) { if (__state) { _supHookDepth = Math.Max(0, _supHookDepth - 1); } return __exception; } private static bool DistributePrefix(object[] __args, ref Dictionary<string, int> __state) { __state = null; if (TeamContributionSync.Applying) { return false; } string text = Arg(__args, 1); if (HighestSync.Owns(text)) { HighestSync.Request(); return false; } if (Plugin.ShareOnce.Value && _supHooksRepoLib && _hookBracketed && _supHookDepth == 0 && Catalog.IsRepoLib(text)) { Plugin.Debug(text + ": fallback share skipped, SharedUpgradesPlus' REPOLib hook shares this purchase."); return false; } if (Plugin.SendPopups.Value) { __state = Levels(text); } return true; } private static void DistributePostfix(object[] __args, Dictionary<string, int> __state) { if (__state == null) { return; } try { string text = Arg(__args, 1); Dictionary<string, int> dictionary = Levels(text); if (dictionary == null) { return; } object obj = ((__args.Length != 0) ? __args[0] : null); string text2 = ((obj != null) ? (_ctxPlayerName?.GetValue(obj) as string) : null); string text3 = ((obj != null) ? (_ctxSteamId?.GetValue(obj) as string) : null); UpgradeInfo info; string upgrade = (Catalog.TryGet(text, out info) ? info.DisplayName : Catalog.Pretty(Catalog.Strip(text ?? "?"))); foreach (KeyValuePair<string, int> item in dictionary) { if (!(item.Key == text3)) { __state.TryGetValue(item.Key, out var value); if (item.Value > value) { Net.SendShared(item.Key, text2 ?? "A teammate", upgrade, item.Value - value); } } } } catch (Exception ex) { Plugin.Debug("pop-up send failed: " + ex.Message); } } private static Dictionary<string, int> Levels(string key) { if (key == null || _statsDicts == null || (Object)(object)StatsManager.instance == (Object)null) { return null; } if (!(_statsDicts.GetValue(StatsManager.instance) is IDictionary dictionary) || !dictionary.Contains(key)) { return null; } if (!(dictionary[key] is Dictionary<string, int> dictionary2)) { return null; } return new Dictionary<string, int>(dictionary2); } private static void LoadModsIntoConfigPostfix() { try { if (!(_supToggles.GetValue(null) is IDictionary dictionary)) { return; } foreach (UpgradeInfo u in Catalog.All.Where((UpgradeInfo x) => x.Kind == UpgradeKind.RepoLib)) { if (!dictionary.Contains(u.Key)) { Once("cfg:" + u.Key, delegate { Plugin.Log.LogWarning((object)(u.DisplayName + " (" + u.Source + ") has no entry in SharedUpgradesPlus' config, so it won't be shared. Is EnableModdedUpgrades on?")); }); } } } catch (Exception ex) { Plugin.Debug("config check failed: " + ex.Message); } } private static Type SupType(Assembly a, string name) { Type? obj = a?.GetType("SharedUpgradesPlus." + name); if (obj == null) { Plugin.Log.LogWarning((object)("SharedUpgradesPlus: type 'SharedUpgradesPlus." + name + "' not found.")); } return obj; } private static void Warn(string member, string feature) { Plugin.Log.LogWarning((object)("'" + member + "' not found - " + feature + " off.")); } private static string Arg(object[] a, int i) { if (a == null || a.Length <= i) { return null; } return a[i] as string; } private static string Norm(string s) { return new string((s ?? "").Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray()); } private static void Once(string id, Action a) { if (LoggedOnce.Add(id)) { a(); } } private static bool Patch(Harmony h, Type type, string method, Type[] args, string feature, string prefix = null, string postfix = null, string finalizer = null) { if (type == null) { return false; } MethodInfo methodInfo = ((args == null) ? AccessTools.Method(type, method, (Type[])null, (Type[])null) : AccessTools.Method(type, method, args, (Type[])null)); if (methodInfo == null) { Warn(type.FullName + "." + method, feature); return false; } return Patch(h, methodInfo, feature, prefix, postfix, finalizer); } private static bool Patch(Harmony h, MethodBase target, string feature, string prefix = null, string postfix = null, string finalizer = null) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) try { h.Patch(target, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Hooks), prefix, (Type[])null), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Hooks), postfix, (Type[])null), (HarmonyMethod)null, (finalizer == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Hooks), finalizer, (Type[])null), (HarmonyMethod)null); Plugin.Debug("patched " + target.DeclaringType?.FullName + "." + target.Name + " (" + feature + ")"); return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Patching '" + target.DeclaringType?.FullName + "." + target.Name + "' failed - " + feature + " off. " + ex.Message)); return false; } } } internal static class MoreInterop { private static FieldInfo _items; private static FieldInfo _playerUpgrade; private static FieldInfo _entries; private static FieldInfo _id; private static object _plugin; private static readonly HashSet<string> NativeIds = new HashSet<string>(StringComparer.Ordinal) { "Sprint Usage", "Valuable Count", "Map Enemy Tracker", "Map Player Tracker", "Map Cosmetics Tracker", "Item Resist", "Item Value", "Extra Life", "Autoscan", "Map Zoom" }; internal static Assembly NativeOwner(object registered) { if (_plugin == null || registered == null || _playerUpgrade == null || !(_items?.GetValue(_plugin) is IEnumerable enumerable)) { return null; } foreach (object item in enumerable) { if (_playerUpgrade.GetValue(item) == registered) { string text = registered.GetType().GetField("UpgradeId")?.GetValue(registered) as string; object obj = item.GetType().GetField("upgradeBase")?.GetValue(item); string text2 = obj?.GetType().GetField("name")?.GetValue(obj) as string; return (NativeIds.Contains(text ?? "") && text == text2) ? _plugin.GetType().Assembly : null; } } return null; } internal static string UpgradeKey(object upgrade) { if (upgrade == null) { return null; } if (_id == null) { _id = AccessTools.Field(upgrade.GetType(), "UpgradeId"); } if (_id?.GetValue(upgrade) is string text) { return "playerUpgrade" + text; } return null; } internal static void Apply(Harmony harmony) { //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Expected O, but got Unknown //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown if (!Chainloader.PluginInfos.TryGetValue("bulletbot.moreupgrades", out var value) || (Object)(object)value.Instance == (Object)null) { return; } _plugin = value.Instance; Assembly assembly = _plugin.GetType().Assembly; Type type = assembly.GetType("MoreUpgrades.Classes.UpgradeItem"); _items = AccessTools.Field(_plugin.GetType(), "upgradeItems"); _playerUpgrade = ((type == null) ? null : AccessTools.Field(type, "playerUpgrade")); _entries = ((type == null) ? null : AccessTools.Field(type, "configEntries")); if (_items == null || _playerUpgrade == null || _entries == null) { return; } try { MethodInfo methodInfo = AccessTools.Method(assembly.GetType("MoreUpgrades.Classes.MoreUpgradesManager"), "FixedUpdate", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(assembly.GetType("MoreUpgrades.REPOLibPatches.REPOLibItemUpgradePatch"), "Upgrade", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { throw new MissingMethodException("MoreUpgrades sharing methods"); } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(MoreInterop), "ConfigTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(MoreInterop), "ConfigTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Plugin.Log.LogWarning((object)("MoreUpgrades ownership hooks unavailable: " + ex.GetBaseException().Message)); } } internal static bool TryTeamSharing(string id, out bool enabled) { enabled = false; if (!(_items?.GetValue(_plugin) is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { string text = UpgradeKey(_playerUpgrade.GetValue(item)); if (text != null && !(SyncPolicy.Normalize(Catalog.Strip(text)) != SyncPolicy.Normalize(id))) { enabled = ConfigBool(item, "Allow Team Upgrades"); return true; } } return false; } private static IEnumerable<CodeInstruction> ConfigTranspiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = instructions.ToList(); int num = 0; for (int i = 1; i < list.Count; i++) { if (list[i].operand is MethodInfo methodInfo && !(methodInfo.Name != "GetConfig") && methodInfo.IsGenericMethod && !(methodInfo.GetGenericArguments()[0] != typeof(bool)) && !(list[i - 1].opcode != OpCodes.Ldstr) && list[i - 1].operand is string text && (!(text != "Sync Host Upgrades") || !(text != "Allow Team Upgrades"))) { list[i].opcode = OpCodes.Call; list[i].operand = AccessTools.Method(typeof(MoreInterop), "ConfigBool", (Type[])null, (Type[])null); num++; } } if (num != 1) { throw new InvalidOperationException("Expected one MoreUpgrades sharing setting call, found " + num); } return list; } private static bool ConfigBool(object item, string name) { if (!(_entries.GetValue(item) is IDictionary dictionary) || !(dictionary[name] is ConfigEntry<bool> val) || !val.Value) { return false; } string text = UpgradeKey(_playerUpgrade.GetValue(item)); if (text == null) { return true; } if (name == "Sync Host Upgrades" && UpgradeOptions.Excluded(text)) { return false; } if (Plugin.SkipTeamPooled.Value && Catalog.TryGet(text, out var info) && info.Pooled) { return false; } if (HighestSync.HostOwns(text)) { return false; } return true; } } internal static class Net { private const byte EventCode = 169; private const string Magic = "SUPFIX1"; private const byte SyncCode = 170; private const string SyncMagic = "SUPFIX2"; public static void Register() { try { PhotonNetwork.NetworkingClient.EventReceived += OnEvent; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Couldn't listen for share pop-ups: " + ex.Message)); } } public static void SendShared(string steamId, string buyer, string upgrade, int amount) { //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(steamId); if ((Object)(object)val == (Object)null) { return; } string text = buyer + " shared " + upgrade + ((amount > 1) ? $" (+{amount})" : ""); if ((Object)(object)val == (Object)(object)SemiFunc.PlayerAvatarLocal()) { if (Plugin.ShowPopups.Value) { Popups.Show(text); } return; } Player val2 = (((Object)(object)val.photonView != (Object)null) ? val.photonView.Owner : null); if (val2 != null && PhotonNetwork.InRoom) { object[] obj = new object[2] { "SUPFIX1", text }; RaiseEventOptions val3 = new RaiseEventOptions(); val3.TargetActors = new int[1] { val2.ActorNumber }; PhotonNetwork.RaiseEvent((byte)169, (object)obj, val3, SendOptions.SendReliable); } } private static void OnEvent(EventData e) { if (e.Code == 170) { try { if (e.CustomData is object[] array && array.Length >= 3 && !(array[0] as string != "SUPFIX2")) { if (array[1] as string == "report" && array.Length == 5 && array[2] is string steamId) { HighestSync.ReceiveReport(e.Sender, steamId, Decode(array[3], array[4])); } else if (array[1] as string == "poolreport" && array.Length == 5 && array[2] is string owner) { TeamContributionSync.ReceiveReport(e.Sender, owner, Decode(array[3], array[4])); } else if (array[1] as string == "poolstate" && array.Length == 3 && PhotonNetwork.MasterClient != null && e.Sender == PhotonNetwork.MasterClient.ActorNumber && array[2] is object[] array2 && array2.Length <= 32) { object[] array3 = array2; for (int i = 0; i < array3.Length; i++) { if (array3[i] is object[] array4 && array4.Length == 3 && array4[0] is string owner2) { TeamContributionSync.ReceiveState(e.Sender, owner2, Decode(array4[1], array4[2])); } } } else if (array[1] as string == "pooled" && array.Length == 4) { TeamUpgradeDisplay.ReceiveHostTotals(e.Sender, Decode(array[2], array[3])); } else if (array[1] as string == "state" && PhotonNetwork.MasterClient != null && e.Sender == PhotonNetwork.MasterClient.ActorNumber && array[2] is object[] array5 && array5.Length <= 32) { HighestSync.BeginHostState(); object[] array3 = array5; for (int i = 0; i < array3.Length; i++) { if (array3[i] is object[] array6 && array6.Length == 3 && array6[0] is string steamId2) { HighestSync.ReceiveState(steamId2, Decode(array6[1], array6[2])); } } } } return; } catch (Exception ex) { Plugin.Debug("State message ignored: " + ex.GetBaseException().Message); return; } } if (e.Code == 169 && Plugin.ShowPopups.Value && PhotonNetwork.MasterClient != null && e.Sender == PhotonNetwork.MasterClient.ActorNumber && e.CustomData is object[] array7 && array7.Length >= 2 && array7[0] as string == "SUPFIX1" && array7[1] is string text) { Popups.Show(text); } } internal static void SendLevels(string kind, string id, Dictionary<string, int> levels, int[] actors) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (PhotonNetwork.InRoom && levels.Count != 0) { PhotonNetwork.RaiseEvent((byte)170, (object)new object[5] { "SUPFIX2", kind, id, levels.Keys.ToArray(), levels.Values.ToArray() }, new RaiseEventOptions { TargetActors = actors }, SendOptions.SendReliable); } } internal static void SendStates(object[] rows) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient) { PhotonNetwork.RaiseEvent((byte)170, (object)new object[3] { "SUPFIX2", "state", rows }, new RaiseEventOptions { Receivers = (ReceiverGroup)0 }, SendOptions.SendReliable); } } internal static void SendPooledTotals(Dictionary<string, int> totals) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient) { PhotonNetwork.RaiseEvent((byte)170, (object)new object[4] { "SUPFIX2", "pooled", totals.Keys.ToArray(), totals.Values.ToArray() }, new RaiseEventOptions { Receivers = (ReceiverGroup)0 }, SendOptions.SendReliable); } } internal static void SendPooledStates(object[] rows) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient) { PhotonNetwork.RaiseEvent((byte)170, (object)new object[3] { "SUPFIX2", "poolstate", rows }, new RaiseEventOptions { Receivers = (ReceiverGroup)0 }, SendOptions.SendReliable); } } private static Dictionary<string, int> Decode(object keyData, object valueData) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); if (!(keyData is string[] array) || !(valueData is int[] array2) || array.Length != array2.Length || array.Length > 256) { return dictionary; } for (int i = 0; i < array.Length; i++) { if (array[i] != null && array[i].StartsWith("playerUpgrade", StringComparison.Ordinal) && array2[i] >= 0) { dictionary[array[i]] = array2[i]; } } return dictionary; } } [BepInPlugin("nakeem.SharedUpgradesFix", "SharedUpgradesPlus-compatibilitytweaks", "1.3.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string Guid = "nakeem.SharedUpgradesFix"; public const string Name = "SharedUpgradesPlus-compatibilitytweaks"; public const string Version = "1.3.1"; public const string SupGuid = "Vippy.SharedUpgradesPlus"; private const string TestedSup = "1.6.2"; private const string TestedRepoLib = "4.3.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> OnlyRealPurchases; internal static ConfigEntry<bool> ShareOnce; internal static ConfigEntry<bool> SkipTeamPooled; internal static ConfigEntry<bool> CapAtModMaxLevel; internal static ConfigEntry<bool> ShareSyncHostUpgrades; internal static ConfigEntry<bool> SendPopups; internal static ConfigEntry<bool> ShowPopups; internal static ConfigEntry<bool> DebugLogging; internal static ConfigEntry<float> PopupSeconds; internal static ConfigEntry<bool> ReconcileHighest; internal static ConfigEntry<float> ReconcileSeconds; internal static ConfigEntry<bool> ShowTeamPooledLevels; internal static ConfigEntry<bool> SyncTeamContributions; internal static ConfigEntry<bool> RepairSlrTeamIdentity; internal static ConfigEntry<bool> RepairMoreItemValue; internal static ConfigEntry<bool> RepairMoreItemResist; private void Awake() { //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; UpgradeOptions.Initialize(((BaseUnityPlugin)this).Config); BindConfig(); ((Component)this).gameObject.AddComponent<Popups>(); Net.Register(); ((Component)this).gameObject.AddComponent<HighestSync>(); if (Chainloader.PluginInfos.ContainsKey("nakeem.SharedUpgrades_SLRPatch")) { Log.LogWarning((object)"SharedUpgrades_SLRPatch is also installed. This compatibility mod replaces it - please remove SharedUpgrades_SLRPatch."); } Chainloader.PluginInfos.TryGetValue("Vippy.SharedUpgradesPlus", out var value); Chainloader.PluginInfos.TryGetValue("REPOLib", out var value2); Assembly assembly = (((Object)(object)((value2 != null) ? value2.Instance : null) != (Object)null) ? ((object)value2.Instance).GetType().Assembly : AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "REPOLib")); Catalog.InitRepoLib(assembly); HighestSync.Initialize(((Object)(object)((value != null) ? value.Instance : null) != (Object)null) ? ((object)value.Instance).GetType().Assembly : null, assembly); Harmony val = new Harmony("nakeem.SharedUpgradesFix"); TeamEffectCompat.Apply(val); TeamContributionSync.Initialize(assembly); ((Component)this).gameObject.AddComponent<TeamContributionSync>(); TeamUpgradeDisplay.Apply(val); ((Component)this).gameObject.AddComponent<TeamUpgradeDisplay>(); MoreInterop.Apply(val); if ((Object)(object)((value != null) ? value.Instance : null) == (Object)null) { Log.LogInfo((object)"Client support ready. SharedUpgradesPlus is required on the host; this client can report and apply host reconciliation states without it."); return; } WarnVersion("SharedUpgradesPlus", value, "1.6.2"); if (value2 != null) { WarnVersion("REPOLib", value2, "4.3.0"); } try { Hooks.Apply(val, ((object)value.Instance).GetType().Assembly, assembly); Log.LogInfo((object)"Ready. Sharing uses events 169/170; team effects keep individual contributions synchronized without multiplying purchases. The list of shareable upgrades is logged when a run starts."); } catch (Exception ex) { Log.LogError((object)("Setup failed, SharedUpgradesPlus runs unmodified: " + ex)); } } private void Start() { Catalog.RefreshForDisplay(); } private void BindConfig() { SyncTeamContributions = ((BaseUnityPlugin)this).Config.Bind<bool>("Team effects", "SynchronizeContributions", true, "Repair each connected player's own permanent team-pooled contribution on every machine. Never copy a team total into individual counts or refill consumable charges. Install on everyone."); RepairSlrTeamIdentity = ((BaseUnityPlugin)this).Config.Bind<bool>("Team effects", "SLRUpgradePack", true, "Fix SLR Valuable Density's connected-player pool lookup using avatar upgrade IDs when Photon user IDs are missing or differ. Includes dead connected contributors; retains native value/durability active-player eligibility."); RepairMoreItemValue = ((BaseUnityPlugin)this).Config.Bind<bool>("Team effects", "MoreUpgradesItemValue", true, "Fix MoreUpgrades Item Value using the sum of connected contributions rather than the last iterated player's level. Automatically uses TeamPooled handling; one contribution per purchase."); RepairMoreItemResist = ((BaseUnityPlugin)this).Config.Bind<bool>("Team effects", "MoreUpgradesItemResist", true, "Handle MoreUpgrades Item Resist as one team effect using connected contributions once, independent of which player grabs it. Preserve native item exclusions and held/last-grab eligibility."); ShowTeamPooledLevels = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowTeamPooledLevels", true, "Show Investor and other detected team-pooled upgrades as '(Team)' totals in the normal upgrade stats panel, including when your personal contribution is zero. Cosmetic only: no upgrade levels or effects are copied."); OnlyRealPurchases = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "OnlyShareRealPurchases", true, "Only share a modded (REPOLib) level increase when it comes from someone using an upgrade. Levels a mod sets by itself (starting amounts, MoreUpgrades' host sync, revive costs) aren't treated as purchases."); ShareOnce = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "ShareEachPurchaseOnce", true, "Share a modded (REPOLib) purchase only through SharedUpgradesPlus' REPOLib hook. Its stat-diff and item-name fallbacks can see the same purchase again and give teammates +2."); SkipTeamPooled = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "SkipTeamPooledUpgrades", true, "Don't share upgrades whose mod already adds up every player's level (found automatically, e.g. SLR Object Value / Object Durability / Valuable Density, Nikki's Investor). Sharing those multiplies the effect by the player count."); CapAtModMaxLevel = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "CapAtModMaxLevel", true, "Don't push a teammate past an upgrade's own 'Maximum Level' (e.g. SLR Inventory Slot). Combines with SharedUpgradesPlus' Share Limit; the lower one wins."); ShareSyncHostUpgrades = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "ShareMoreUpgradesSyncHostUpgrades", true, "Let SharedUpgradesPlus share MoreUpgrades upgrades that only have 'Sync Host Upgrades' on. That option copies the host's level to everyone, which wipes a client's purchase; SharedUpgradesPlus normally leaves those alone."); SendPopups = ((BaseUnityPlugin)this).Config.Bind<bool>("Popups", "SendSharePopups", true, "Host: tell each player when they receive a shared upgrade. Only players who also have this patch see it."); ShowPopups = ((BaseUnityPlugin)this).Config.Bind<bool>("Popups", "ShowSharePopups", true, "Show 'X shared Y' pop-ups in the bottom-left stack (Semibot RPG's stack when it's installed)."); PopupSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Popups", "PopupSeconds", 4f, "How long a built-in pop-up stays on screen."); DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLogging", false, "Log every decision this patch makes."); ReconcileHighest = ((BaseUnityPlugin)this).Config.Bind<bool>("Reconciliation", "MatchHighestPermanentLevel", true, "At 100% sharing chance, periodically raise enabled permanent upgrades to the highest connected player's count. Never lowers levels. Team-pooled and excluded consumable upgrades are not reconciled. Install this patch on every player for private client-state repair."); ReconcileSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Reconciliation", "CheckSeconds", 2f, "Seconds between bounded host checks (minimum 1). Idle checks send no upgrade writes. Client reports and host snapshots run every 5 seconds."); } private static void WarnVersion(string name, PluginInfo info, string tested) { object obj; if (info == null) { obj = null; } else { BepInPlugin metadata = info.Metadata; obj = ((metadata == null) ? null : metadata.Version?.ToString()); } if (obj == null) { obj = "?"; } string text = (string)obj; if (!text.StartsWith(tested)) { Log.LogWarning((object)(name + " " + text + " is installed; this patch was built against " + tested + ". If something it relies on changed, that part turns itself off and says so here.")); } } internal static void Debug(string msg) { if (DebugLogging != null && DebugLogging.Value) { Log.LogInfo((object)("[debug] " + msg)); } } } internal static class PooledDisplayPolicy { internal static int Total(Dictionary<string, int> levels, IEnumerable<string> connectedIds) { if (levels == null || connectedIds == null) { return 0; } long num = 0L; HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (string connectedId in connectedIds) { if (!string.IsNullOrEmpty(connectedId) && hashSet.Add(connectedId) && levels.TryGetValue(connectedId, out var value) && value > 0) { num = Math.Min(2147483647L, num + value); } } return (int)num; } internal static Dictionary<string, int> DisplayCopy(Dictionary<string, int> personal, Dictionary<string, int> team) { Dictionary<string, int> dictionary = ((personal == null) ? new Dictionary<string, int>() : new Dictionary<string, int>(personal)); foreach (KeyValuePair<string, int> item in team) { dictionary[item.Key] = Math.Max(0, item.Value); } return dictionary; } internal static bool Equal(Dictionary<string, int> left, Dictionary<string, int> right) { if (left.Count != right.Count) { return false; } foreach (KeyValuePair<string, int> item in left) { if (!right.TryGetValue(item.Key, out var value) || value != item.Value) { return false; } } return true; } internal static string Label(string name) { if (!(name ?? "").EndsWith(" (Team)", StringComparison.Ordinal)) { return name + " (Team)"; } return name; } } internal class Popups : MonoBehaviour { private const string RpgGuid = "nakeem.semibotrpg"; private static readonly List<KeyValuePair<string, float>> Items = new List<KeyValuePair<string, float>>(); private static readonly Color Tint = new Color(0.45f, 0.85f, 1f); private static bool _rpgResolved; private static PropertyInfo _rpgInstance; private static MethodInfo _rpgNotify; private static object _rpgKind; private static GUIStyle _style; private static GUIStyle _shadow; public static void Show(string text) { if (!TryRpg(text)) { Items.Add(new KeyValuePair<string, float>(text, Time.unscaledTime + Mathf.Max(1f, Plugin.PopupSeconds.Value))); while (Items.Count > 5) { Items.RemoveAt(0); } } } private static bool TryRpg(string text) { //IL_0176: Unknown result type (might be due to invalid IL or missing references) if (!_rpgResolved) { _rpgResolved = true; try { if (Chainloader.PluginInfos.TryGetValue("nakeem.semibotrpg", out var value) && (Object)(object)value.Instance != (Object)null) { Type? type = ((object)value.Instance).GetType().Assembly.GetType("SemibotRPG.RpgRuntime"); _rpgInstance = type?.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _rpgNotify = type?.GetMethod("Notify", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); ParameterInfo[] array = _rpgNotify?.GetParameters(); if (array != null && array.Length == 4 && array[0].ParameterType.IsEnum) { _rpgKind = (Enum.IsDefined(array[0].ParameterType, "Other") ? Enum.Parse(array[0].ParameterType, "Other") : Enum.ToObject(array[0].ParameterType, 0)); } else { _rpgNotify = null; } if (_rpgInstance == null || _rpgNotify == null) { Plugin.Log.LogWarning((object)"Semibot RPG is installed but its RpgRuntime.Notify wasn't found - using the built-in pop-up stack."); } } } catch (Exception ex) { _rpgNotify = null; Plugin.Debug("Semibot RPG pop-ups unavailable: " + ex.Message); } } if (_rpgNotify == null) { return false; } try { object value2 = _rpgInstance.GetValue(null); if (value2 == null) { return false; } _rpgNotify.Invoke(value2, new object[4] { _rpgKind, text, Tint, "sharedupgradesfix:" + text }); return true; } catch (Exception ex2) { Plugin.Debug("Semibot RPG Notify failed: " + ex2.Message); return false; } } private void OnGUI() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0078: 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: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) if (Items.Count != 0) { float now = Time.unscaledTime; Items.RemoveAll((KeyValuePair<string, float> i) => i.Value < now); if (_style == null) { _style = new GUIStyle(GUI.skin.label) { fontSize = Mathf.RoundToInt((float)Screen.height / 45f), fontStyle = (FontStyle)1 }; _style.normal.textColor = Tint; _shadow = new GUIStyle(_style); _shadow.normal.textColor = new Color(0f, 0f, 0f, 0.8f); } float num = (float)_style.fontSize * 1.5f; float num2 = (float)Screen.width * 0.015f; float num3 = (float)Screen.height * 0.78f; Rect val = default(Rect); for (int num4 = Items.Count - 1; num4 >= 0; num4--) { float num5 = Items[num4].Value - now; Color color = GUI.color; GUI.color = new Color(1f, 1f, 1f, Mathf.Clamp01(num5)); ((Rect)(ref val))..ctor(num2, num3, (float)Screen.width * 0.5f, num); GUI.Label(new Rect(((Rect)(ref val)).x + 2f, ((Rect)(ref val)).y + 2f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), Items[num4].Key, _shadow); GUI.Label(val, Items[num4].Key, _style); GUI.color = color; num3 -= num; } } } } internal static class SyncPolicy { public static bool Excluded(string key, string exclusions) { string id = ((key != null && key.StartsWith("playerUpgrade", StringComparison.Ordinal)) ? key.Substring("playerUpgrade".Length) : key); if (Consumable(key)) { return true; } return (exclusions ?? "").Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries).Any((string s) => string.Equals(s.Trim(), id, StringComparison.OrdinalIgnoreCase) || string.Equals(s.Trim(), key, StringComparison.OrdinalIgnoreCase)); } public static bool Consumable(string key) { string text = Normalize((key != null && key.StartsWith("playerUpgrade", StringComparison.Ordinal)) ? key.Substring("playerUpgrade".Length) : key); if (!(text == "extralife")) { return text == "secondchance"; } return true; } public static int Target(IEnumerable<int> levels, int cap) { int num = 0; foreach (int level in levels) { if (level > num) { num = level; } } if (cap <= 0) { return num; } return Math.Min(num, cap); } public static int Delta(int current, int target) { if (target <= current) { return 0; } return target - Math.Max(0, current); } public static string Normalize(string value) { return new string((value ?? "").Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray()); } } internal sealed class TeamContributionSync : MonoBehaviour { private static FieldInfo _registry; private static FieldInfo _levels; private static MethodInfo _apply; private static object _room; private static int _host; private static float _nextTick; private static float _nextHeartbeat; private static readonly Dictionary<string, Dictionary<string, int>> Pending = new Dictionary<string, Dictionary<string, int>>(); private static readonly HashSet<string> Warned = new HashSet<string>(); internal static bool Applying { get; private set; } private static bool Ready { get { if (Plugin.SyncTeamContributions.Value && _registry != null && _levels != null) { return _apply != null; } return false; } } internal static void Request() { _nextTick = Math.Min(_nextTick, Time.unscaledTime + 0.25f); _nextHeartbeat = 0f; } internal static void Initialize(Assembly repo) { _registry = repo?.GetType("REPOLib.Modules.Upgrades")?.GetField("_playerUpgrades", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); Type? obj = repo?.GetType("REPOLib.Modules.PlayerUpgrade"); _levels = obj?.GetField("PlayerDictionary", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _apply = obj?.GetMethod("ApplyUpgrade", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(int) }, null); Pending.Clear(); _room = null; _host = 0; _nextTick = (_nextHeartbeat = 0f); } private static bool Eligible(string key) { if (!UpgradeOptions.Excluded(key) && Catalog.TryGet(key, out var info) && info.Kind == UpgradeKind.RepoLib && info.Pooled) { return UpgradeOptions.SharingAllowed(info); } return false; } private static object Upgrade(string key) { if (!(_registry?.GetValue(null) is IDictionary dictionary) || !dictionary.Contains(Catalog.Strip(key))) { return null; } return dictionary[Catalog.Strip(key)]; } internal static Dictionary<string, int> Levels(string key) { object obj = Upgrade(key); if (obj != null) { return _levels?.GetValue(obj) as Dictionary<string, int>; } return null; } private static void Session() { Player masterClient = PhotonNetwork.MasterClient; int num = ((masterClient != null) ? masterClient.ActorNumber : 0); if (_room != PhotonNetwork.CurrentRoom || _host != num) { _room = PhotonNetwork.CurrentRoom; _host = num; Pending.Clear(); _nextTick = (_nextHeartbeat = 0f); } } internal static Dictionary<string, int> Snapshot(string owner) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); if (!Ready || string.IsNullOrEmpty(owner)) { return dictionary; } UpgradeInfo[] array = Catalog.All.ToArray(); foreach (UpgradeInfo upgradeInfo in array) { if (Eligible(upgradeInfo.Key)) { Dictionary<string, int> dictionary2 = Levels(upgradeInfo.Key); if (dictionary2 != null) { dictionary[upgradeInfo.Key] = (dictionary2.TryGetValue(owner, out var value) ? Math.Max(0, value) : 0); } } } return dictionary; } internal static void ReceiveReport(int sender, string owner, Dictionary<string, int> report) { if (!Ready || !PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient) { return; } Session(); if (!SemiFunc.PlayerGetAll().Any((PlayerAvatar p) => (Object)(object)p != (Object)null && (Object)(object)p.photonView != (Object)null && p.photonView.OwnerActorNr == sender && SemiFunc.PlayerGetSteamID(p) == owner) || !PhotonNetwork.PlayerList.Any((Player p) => p.ActorNumber == sender)) { return; } if (!Pending.TryGetValue(owner, out var value)) { value = (Pending[owner] = new Dictionary<string, int>()); } foreach (KeyValuePair<string, int> item in report) { if (item.Value >= 0 && Eligible(item.Key)) { value[item.Key] = (value.TryGetValue(item.Key, out var value2) ? Math.Max(value2, item.Value) : item.Value); } } _nextTick = Math.Min(_nextTick, Time.unscaledTime + 0.25f); } internal static void ReceiveState(int sender, string owner, Dictionary<string, int> state) { if (!Ready || !PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient) { return; } Player masterClient = PhotonNetwork.MasterClient; if (masterClient == null || masterClient.ActorNumber != sender) { return; } Session(); foreach (KeyValuePair<string, int> item in state) { if (item.Value >= 0 && Eligible(item.Key)) { Dictionary<string, int> dictionary = Levels(item.Key); if (dictionary != null && ConnectedTeam.UpgradeIds(dictionary).Contains(owner)) { Merge(item.Key, owner, item.Value); } } } } private static bool Merge(string key, string owner, int proposed) { Dictionary<string, int> dictionary = Levels(key); if (dictionary == null) { return false; } int value; int num = (dictionary.TryGetValue(owner, out value) ? value : 0); UpgradeInfo info; int num2 = (Catalog.TryGet(key, out info) ? info.MaxLevel() : 0); int num3 = ((num2 > 0) ? Math.Min(proposed, num2) : proposed); if (num3 <= num) { return false; } try { Applying = true; _apply.Invoke(Upgrade(key), new object[2] { owner, num3 }); return true; } catch (Exception ex) { if (Warned.Add(key)) { Plugin.Log.LogWarning((object)("Team contribution repair failed for " + key + ": " + ex.GetBaseException().Message)); } return false; } finally { Applying = false; } } internal static bool Reconcile() { bool flag = false; KeyValuePair<string, Dictionary<string, int>>[] array = Pending.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair<string, Dictionary<string, int>> keyValuePair = array[i]; foreach (KeyValuePair<string, int> item in keyValuePair.Value) { if (Eligible(item.Key)) { Dictionary<string, int> dictionary = Levels(item.Key); if (dictionary != null && ConnectedTeam.UpgradeIds(dictionary).Contains(keyValuePair.Key)) { flag |= Merge(item.Key, keyValuePair.Key, item.Value); } } } } Pending.Clear(); return flag; } private void Update() { if (Time.unscaledTime < _nextTick) { return; } _nextTick = Time.unscaledTime + 2f; try { Session(); if (!Ready || (Object)(object)StatsManager.instance == (Object)null || (Object)(object)SemiFunc.PlayerAvatarLocal() == (Object)null) { return; } Catalog.RefreshForDisplay(); if (SemiFunc.IsMasterClientOrSingleplayer()) { bool flag = Reconcile(); if (!PhotonNetwork.InRoom || (!flag && Time.unscaledTime < _nextHeartbeat)) { return; } List<object> list = new List<object>(); string[] array = Catalog.All.Where((UpgradeInfo i) => Eligible(i.Key)).SelectMany((UpgradeInfo i) => ConnectedTeam.UpgradeIds(Levels(i.Key))).Distinct() .ToArray(); foreach (string text in array) { Dictionary<string, int> dictionary = Snapshot(text); if (dictionary.Count > 0) { list.Add(new object[3] { text, dictionary.Keys.ToArray(), dictionary.Values.ToArray() }); } } if (list.Count > 0) { Net.SendPooledStates(list.ToArray()); } _nextHeartbeat = Time.unscaledTime + 5f; } else if (PhotonNetwork.InRoom && PhotonNetwork.MasterClient != null && Time.unscaledTime >= _nextHeartbeat) { string text2 = SemiFunc.PlayerGetSteamID(SemiFunc.PlayerAvatarLocal()); Net.SendLevels("poolreport", text2, Snapshot(text2), new int[1] { PhotonNetwork.MasterClient.ActorNumber }); _nextHeartbeat = Time.unscaledTime + 5f; } } catch (Exception ex) { if (Warned.Add("tick")) { Plugin.Log.LogWarning((object)("Team contribution check failed; will retry: " + ex.GetBaseException().Message)); } } } } internal static class TeamEffectCompat { private static bool _moreApplied; private static PropertyInfo _slrRegister; private static FieldInfo _slrLevels; private static FieldInfo _slrId; private static FieldInfo _moreRegister; private static FieldInfo _moreId; internal static bool MoreValueActive { get; private set; } internal static bool MoreResistActive { get; private set; } internal static void Apply(Harmony harmony) { //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Expected O, but got Unknown //IL_047d: Unknown result type (might be due to invalid IL or missing references) //IL_048b: Expected O, but got Unknown //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Expected O, but got Unknown if (Plugin.RepairSlrTeamIdentity.Value && Chainloader.PluginInfos.TryGetValue("SolarAaron.SLRUpgradePack", out var value) && (Object)(object)value.Instance != (Object)null) { try { Type? type = ((object)value.Instance).GetType().Assembly.GetType("SLRUpgradePack.UpgradeManagers.ValuableDensityUpgrade"); MethodInfo methodInfo = type?.GetMethod("GetTotalConnectedPlayerLevels", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); List<KeyValuePair<OpCode, object>> source = ((methodInfo == null) ? null : PatchProcessor.ReadMethodBody((MethodBase)methodInfo).ToList()); _slrRegister = type?.GetProperty("UpgradeRegister", BindingFlags.Instance | BindingFlags.Public); _slrLevels = _slrRegister?.PropertyType.GetField("PlayerDictionary"); _slrId = _slrRegister?.PropertyType.GetField("UpgradeId"); if (methodInfo?.ReturnType != typeof(int) || _slrLevels?.FieldType != typeof(Dictionary<string, int>) || _slrId?.FieldType != typeof(string) || !source.Any((KeyValuePair<OpCode, object> i) => i.Value is MethodInfo methodInfo4 && methodInfo4.DeclaringType.FullName == "Photon.Pun.PhotonNetwork" && methodInfo4.Name == "get_PlayerList") || !source.Any((KeyValuePair<OpCode, object> i) => i.Value is FieldInfo fieldInfo && fieldInfo == _slrLevels)) { throw new InvalidOperationException("SLR connected-pool layout is unsupported"); } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(TeamEffectCompat), "ConnectedPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"SLR Valuable Density: team pool resolves connected avatar upgrade IDs rather than relying on published Photon user IDs."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("SLR team-pool identity repair unavailable: " + ex.GetBaseException().Message)); } } if (Plugin.RepairMoreItemValue.Value && Chainloader.PluginInfos.TryGetValue("bulletbot.moreupgrades", out var value2) && (Object)(object)value2.Instance != (Object)null) { try { MethodInfo methodInfo2 = ((object)value2.Instance).GetType().Assembly.GetType("MoreUpgrades.Patches.ValuableObjectPatch")?.GetMethod("DollarValueSetLogic", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(float) }, null); if (methodInfo2?.ReturnType != typeof(float)) { throw new MissingMethodException("MoreUpgrades Item Value calculation"); } _moreApplied = false; harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(TeamEffectCompat), "ItemValueTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); MoreValueActive = _moreApplied; if (!_moreApplied) { throw new InvalidOperationException("MoreUpgrades Item Value layout is unsupported; original handling retained"); } Plugin.Log.LogInfo((object)"MoreUpgrades Item Value: one team contribution per purchase; world values use the connected pool instead of the last player's level."); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("MoreUpgrades Item Value team adapter unavailable: " + ex2.GetBaseException().Message)); } } if (!Plugin.RepairMoreItemResist.Value || !Chainloader.PluginInfos.TryGetValue("bulletbot.moreupgrades", out var value3) || !((Object)(object)value3.Instance != (Object)null)) { return; } try { Assembly assembly = ((object)value3.Instance).GetType().Assembly; Type type2 = assembly.GetType("MoreUpgrades.Classes.UpgradeItem"); Type type3 = assembly.GetType("MoreUpgrades.Patches.PhysGrabObjectImpactDetectorPatch"); _moreRegister = type2?.GetField("playerUpgrade"); _moreId = _moreRegister?.FieldType.GetField("UpgradeId"); MethodInfo methodInfo3 = ((type2 == null) ? null : type3?.GetMethod("ReduceValueLost", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3] { type2, typeof(float), typeof(int) }, null)); if (methodInfo3?.ReturnType != typeof(float) || _moreId?.FieldType != typeof(string) || !PatchProcessor.ReadMethodBody((MethodBase)methodInfo3).Any((KeyValuePair<OpCode, object> i) => i.Value is MethodInfo methodInfo4 && methodInfo4.DeclaringType.FullName == "UnityEngine.Mathf" && methodInfo4.Name == "Pow")) { throw new InvalidOperationException("MoreUpgrades Item Resist layout is unsupported; original handling retained"); } harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(TeamEffectCompat), "ResistPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MoreResistActive = true; Plugin.Log.LogInfo((object)"MoreUpgrades Item Resist: connected team contributions apply once, preserving native item and grab eligibility."); } catch (Exception ex3) { Plugin.Log.LogWarning((object)("MoreUpgrades Item Resist team adapter unavailable: " + ex3.GetBaseException().Message)); } } private static bool ConnectedPrefix(object __instance, ref int __result) { if (!Plugin.RepairSlrTeamIdentity.Value || _slrRegister == null) { return true; } object value = _slrRegister.GetValue(__instance); if (value == null || _slrId.GetValue(value) as string != "ValuableDensity" || !(_slrLevels.GetValue(value) is Dictionary<string, int> levels)) { return true; } __result = PooledDisplayPolicy.Total(levels, ConnectedTeam.UpgradeIds(levels)); return false; } internal static int ItemValueLevel(int original) { if (!Plugin.RepairMoreItemValue.Value || !Catalog.TryGet("playerUpgradeItem Value", out var info) || !info.Pooled) { return original; } Dictionary<string, int> dictionary = TeamContributionSync.Levels("playerUpgradeItem Value"); if (dictionary != null) { return PooledDisplayPolicy.Total(dictionary, ConnectedTeam.UpgradeIds(dictionary)); } return original; } private static void ResistPrefix(object __0, ref int __2) { if (!Plugin.RepairMoreItemResist.Value || __0 == null || !Catalog.TryGet("playerUpgradeItem Resist", out var info) || !info.Pooled) { return; } object value = _moreRegister.GetValue(__0); if (value != null && !(_moreId.GetValue(value) as string != "Item Resist")) { Dictionary<string, int> dictionary = TeamContributionSync.Levels("playerUpgradeItem Resist"); if (dictionary != null) { __2 = PooledDisplayPolicy.Total(dictionary, ConnectedTeam.UpgradeIds(dictionary)); } } } internal static IEnumerable<CodeInstruction> ItemValueTranspiler(IEnumerable<CodeInstruction> instructions) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown List<CodeInstruction> code = instructions.ToList(); _moreApplied = false; if (code.Count((CodeInstruction i) => i.operand is MethodInfo methodInfo && methodInfo.DeclaringType.FullName == "REPOLib.Modules.PlayerUpgrade" && methodInfo.Name == "GetLevel" && methodInfo.ReturnType == typeof(int) && methodInfo.GetParameters().Length == 1 && methodInfo.GetParameters()[0].ParameterType == typeof(PlayerAvatar)) != 1) { return code; } List<int> list = (from i in Enumerable.Range(0, Math.Max(0, code.Count - 6)) where code[i].opcode == OpCodes.Ldarg_0 && code[i + 1].opcode == OpCodes.Ldc_R4 && object.Equals(code[i + 1].operand, 1f) && code[i + 2].opcode == OpCodes.Ldloc_1 && code[i + 3].opcode == OpCodes.Conv_R4 && code[i + 4].opcode == OpCodes.Ldloc_0 && code[i + 5].opcode == OpCodes.Ldstr && object.Equals(code[i + 5].operand, "Scaling Factor") && code[i + 6].operand is MethodInfo methodInfo && methodInfo.DeclaringType.FullName == "MoreUpgrades.Classes.UpgradeItem" && methodInfo.Name == "GetConfig" && methodInfo.IsGenericMethod && methodInfo.GetGenericArguments().SequenceEqual(new Type[1] { typeof(float) }) select i).ToList(); if (list.Count != 1) { return code; } code.Insert(list[0] + 3, new CodeInstruction(OpCodes.Call, (object)typeof(TeamEffectCompat).GetMethod("ItemValueLevel", BindingFlags.Static | BindingFlags.NonPublic))); _moreApplied = true; return code; } internal static string Description(UpgradeInfo info) { string text = info.Owner?.GetName().Name; if (text == "SLRUpgradePack") { if (info.Id == "ValuableDensity") { return "Team effect: valuable spawn quantity/value budgets at the next level generation; one contribution per purchase, including connected dead players."; } if (info.Id == "ObjectValue") { return "Team effect: world valuable prices; one contribution per purchase, using SLR's active-player pool."; } if (info.Id == "ObjectDurability") { return "Team effect: valuable damage resistance; one contribution per purchase, using SLR's active-player pool."; } } if (text == "NikkiUpgrades" && info.Id == "Investor") { return "Team effect: shared Investor income; one contribution per purchase."; } if (text == "MoreUpgrades" && info.Id == "Item Value" && MoreValueActive) { return "Team effect: world valuable prices; one contribution per purchase, summed over connected players."; } if (text == "MoreUpgrades" && info.Id == "Item Resist" && MoreResistActive) { return "Team effect: valuable damage resistance under the original held/last-grab rules; connected contributions apply once, regardless of who grabs."; } return null; } } internal sealed class TeamUpgradeDisplay : MonoBehaviour { private static readonly FieldInfo StatsDictionaries = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries"); private static readonly Dictionary<string, Func<int>> TotalReaders = new Dictionary<string, Func<int>>(); private static Dictionary<string, int> _display = new Dictionary<string, int>(); private static Dictionary<string, int> _remote = new Dictionary<string, int>(); private static Dictionary<string, int> _lastSent = new Dictionary<string, int>(); private static Func<StatsManager, string, Dictionary<string, int>> _fetch; private static object _session; private static int _host; private static bool _remoteValid; private static bool _sessionReady; private static bool _warned; private static float _nextCheck; private static float _nextHeartbeat; private static float _remoteAt; internal static void Apply(Harmony harmony) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(StatsManager), "FetchPlayerUpgrades", new Type[1] { typeof(string) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(StatsUI), "Fetch", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo3 = AccessTools.DeclaredMethod(typeof(StatsUI), "GetUpgradeDisplayName", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo == null || methodInfo2 == null || methodInfo3 == null) { throw new MissingMethodException("Upgrade stats display API"); } _fetch = AccessTools.MethodDelegate<Func<StatsManager, string, Dictionary<string, int>>>(methodInfo, (object)null, true); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(TeamUpgradeDisplay), "FetchTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(TeamUpgradeDisplay), "NamePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"Team-pooled upgrade display ready: stats show one read-only team total, including Investor, without copying benefits."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Team upgrade stats display unavailable; sharing unchanged: " + ex.GetBaseException().Message)); } } private static IEnumerable<CodeInstruction> FetchTranspiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = instructions.ToList(); MethodInfo original = AccessTools.DeclaredMethod(typeof(StatsManager), "FetchPlayerUpgrades", new Type[1] { typeof(string) }, (Type[])null); List<CodeInstruction> list2 = list.Where((CodeInstruction i) => CodeInstructionExtensions.Calls(i, original)).ToList(); if (list2.Count != 1) { throw new InvalidOperationException("StatsUI.Fetch must have one isolated FetchPlayerUpgrades call"); } list2[0].opcode = OpCodes.Call; list2[0].operand = AccessTools.DeclaredMethod(typeof(TeamUpgradeDisplay), "FetchForDisplay", (Type[])null, (Type[])null); return list; } internal static Dictionary<string, int> FetchForDisplay(StatsManager stats, string steamId) { Dictionary<string, int> dictionary = _fetch(stats, steamId); if (!Plugin.ShowTeamPooledLevels.Value || _display.Count <= 0) { return dictionary; } return PooledDisplayPolicy.DisplayCopy(dictionary, _display); } private static void NamePostfix(string __0, ref string __result) { if (Plugin.ShowTeamPooledLevels.Value && _display.ContainsKey(__0)) { __result = PooledDisplayPolicy.Label(__result); } } private static void ResetSession() { object currentRoom = PhotonNetwork.CurrentRoom; Player masterClient = PhotonNetwork.MasterClient; int num = ((masterClient != null) ? masterClient.ActorNumber : 0); if (!_sessionReady || _session != currentRoom || _host != num) { _session = currentRoom; _host = num; _sessionReady = true; _remoteValid = false; _display.Clear(); _remote.Clear(); _lastSent.Clear(); _nextHeartbeat = 0f; } } private void Update() { if (Time.unscaledTime < _nextCheck) { return; } _nextCheck = Time.unscaledTime + 1f; try { ResetSession(); if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)SemiFunc.PlayerAvatarLocal() == (Object)null) { _display.Clear(); return; } Catalog.RefreshForDisplay(); if (!SemiFunc.IsMasterClientOrSingleplayer() && _remoteValid && Time.unscaledTime - _remoteAt <= 12f) { _display = new Dictionary<string, int>(_remote); return; } Dictionary<string, int> dictionary = (_display = BuildLocalTotals()); if (SemiFunc.IsMasterClientOrSingleplayer() && PhotonNetwork.InRoom && (!PooledDisplayPolicy.Equal(dictionary, _lastSent) || Time.unscaledTime >= _nextHeartbeat)) { Net.SendPooledTotals(dictionary); _lastSent = new Dictionary<string, int>(dictionary); _nextHeartbeat = Time.unscaledTime + 5f; } } catch (Exception ex) { if (!_warned) { _warned = true; Plugin.Log.LogWarning((object)("Team level display update failed; sharing unchanged: " + ex.GetBaseException().Message)); } } } internal static Dictionary<string, int> BuildLocalTotals() { Dictionary<string, int> dictionary = new Dictionary<string, int>(); SortedDictionary<string, Dictionary<string, int>> sortedDictionary = (((Object)(object)StatsManager.instance == (Object)null) ? null : (StatsDictionaries.GetValue(StatsManager.instance) as SortedDictionary<string, Dictionary<string, int>>)); if (sortedDictionary == null) { return dictionary; } foreach (UpgradeInfo item in Catalog.All) { if (!item.Pooled || !sortedDictionary.TryGetValue(item.Key, out var value)) { continue; } if (!TotalReaders.TryGetValue(item.Key, out var value2)) { value2 = null; if (item.Target != null) { Type type = item.Target.GetType(); MethodInfo methodInfo = type.GetMethod("GetTotalConnectedPlayerLevels", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) ?? type.GetMethod("GetTotalActivePlayerLevels", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (methodInfo != null && !methodInfo.IsStatic && methodInfo.ReturnType == typeof(int)) { value2 = Delegate.CreateDelegate(typeof(Func<int>), item.Target, methodInfo, throwOnBindFailure: false) as Func<int>; } } TotalReaders[item.Key] = value2; } dictionary[item.Key] = ((value2 != null) ? Math.Max(0, value2()) : PooledDisplayPolicy.Total(value, ConnectedTeam.UpgradeIds(value))); } return dictionary; } internal static void ReceiveHostTotals(int sender, Dictionary<string, int> totals) { if (!PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient || PhotonNetwork.MasterClient == null || sender != PhotonNetwork.MasterClient.ActorNumber) { return; } ResetSession(); Catalog.RefreshForDisplay(); Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (KeyValuePair<string, int> total in totals) { if (total.Value >= 0 && Catalog.TryGet(total.Key, out var info) && info.Pooled) { dictionary[total.Key] = total.Value; } } _remote = dictionary; _remoteValid = true; _remoteAt = Time.unscaledTime; _display = new Dictionary<string, int>(dictionary); } } public enum UpgradeHandling { Automatic, SharedPermanent, TeamPooled, Consumable } public enum ConsumableHandling { Consumable } internal static class UpgradeOptions { private sealed class Options { internal ConfigEntry<UpgradeHandling> Handling; internal ConfigEntry<bool> AllowSharing; } private static ConfigFile _config; private static string _oldPooled = ""; private static string _oldIndividual = ""; private static string _oldExcluded = ""; private static readonly Dictionary<string, Options> Ent