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 v1.4.2
SharedUpgradesPlus.dll
Decompiled 17 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using SharedUpgradesPlus.Configuration; using SharedUpgradesPlus.Models; using SharedUpgradesPlus.Services; 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: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("Vippy")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.4.2.0")] [assembly: AssemblyInformationalVersion("1.4.2+5c18fd22269d6d7c1e3e137162f6425d54660de2")] [assembly: AssemblyProduct("SharedUpgradesPlus")] [assembly: AssemblyTitle("SharedUpgradesPlus")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.4.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SharedUpgradesPlus { internal static class BuildInfo { public const string Version = "1.4.2"; } [BepInPlugin("Vippy.SharedUpgradesPlus", "SharedUpgradesPlus", "1.4.2")] public class SharedUpgradesPlus : BaseUnityPlugin { internal static SharedUpgradesPlus Instance { get; private set; } internal static ManualLogSource Logger => Instance.BaseLogger; private ManualLogSource BaseLogger => ((BaseUnityPlugin)this).Logger; internal Harmony? Harmony { get; set; } private void Awake() { Instance = this; ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; PluginConfig.Init(((BaseUnityPlugin)this).Config); Patch(); Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded! LogLevel={PluginConfig.LoggingLevel.Value}"); } internal static void LogAlways(string msg) { Logger.LogInfo((object)msg); } internal static void LogInfo(string msg) { if (ConfigService.IsDebugLoggingEnabled()) { Logger.LogInfo((object)msg); } } internal static void LogVerbose(string msg) { if (ConfigService.IsVerboseLoggingEnabled()) { Logger.LogDebug((object)msg); } } internal void Patch() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0025: Expected O, but got Unknown if (Harmony == null) { Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID); Harmony val2 = val; Harmony = val; } Harmony.PatchAll(); } } } namespace SharedUpgradesPlus.Services { public static class ConfigService { private static readonly Dictionary<string, ConfigEntry<bool>> upgradeToggles = new Dictionary<string, ConfigEntry<bool>>(); private static readonly Dictionary<string, ConfigEntry<int>> limitSliders = new Dictionary<string, ConfigEntry<int>>(); private static readonly HashSet<string> _disabledByDefault = new HashSet<string> { "playerUpgradeObjectValue", "playerUpgradeObjectDurability" }; public static bool IsSharedUpgradesEnabled() { return PluginConfig.EnableSharedUpgrades.Value; } public static bool IsModdedUpgradesEnabled() { return PluginConfig.EnableModdedUpgrades.Value; } public static bool IsLateJoinSyncEnabled() { return PluginConfig.EnableLateJoinSync.Value; } public static bool IsSharedUpgradeHealEnabled() { return PluginConfig.EnableSharedUpgradeHeal.Value; } public static bool IsShareNotificationEnabled() { return PluginConfig.EnableShareNotification.Value; } public static int SharedUpgradesChancePercentage() { return PluginConfig.SharedUpgradeChance.Value; } public static bool RollSharedUpgradesChance() { return Roll(PluginConfig.SharedUpgradeChance.Value); } private static bool Roll(int chance) { return Random.Range(0, 100) < chance; } public static bool IsDebugLoggingEnabled() { return PluginConfig.LoggingLevel.Value >= VerbosityLevel.Debug; } public static bool IsVerboseLoggingEnabled() { return PluginConfig.LoggingLevel.Value >= VerbosityLevel.Verbose; } public static bool IsUpgradeEnabled(string upgradeKey) { if (upgradeToggles.TryGetValue(upgradeKey, out ConfigEntry<bool> value)) { return value.Value; } return true; } public static int UpgradeShareLimit(string upgradeKey) { if (limitSliders.TryGetValue(upgradeKey, out ConfigEntry<int> value)) { return value.Value; } return 0; } public static void LoadModsIntoConfig() { if (PluginConfig.ConfigFile != null) { RegisterToggles(RegistryService.Instance.VanillaUpgrades, "Vanilla Upgrades"); RegisterToggles(RegistryService.Instance.ModdedUpgrades, "Modded Upgrades"); } } private static void RegisterToggles(IEnumerable<Upgrade> upgrades, string section) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown foreach (Upgrade upgrade in upgrades) { if (!upgradeToggles.ContainsKey(upgrade.Name)) { upgradeToggles[upgrade.Name] = PluginConfig.ConfigFile.Bind<bool>(section, upgrade.CleanName, !_disabledByDefault.Contains(upgrade.Name), "Enable sharing for " + upgrade.CleanName); limitSliders[upgrade.Name] = PluginConfig.ConfigFile.Bind<int>(section, upgrade.CleanName + " Share Limit", 0, new ConfigDescription("Others won't receive this upgrade past this level (0 = unlimited)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); } } } } public static class DiscoveryService { public static DiscoveredUpgradesResult DiscoveredUpgrades(StatsManager statsManager) { HashSet<string> hashSet = new HashSet<string>(); HashSet<string> hashSet2 = new HashSet<string>(); foreach (string item in statsManager.dictionaryOfDictionaries.Keys.Where((string key) => key.StartsWith("playerUpgrade"))) { if (AccessTools.Field(typeof(StatsManager), item) != null) { hashSet.Add(item); } else { hashSet2.Add(item); } } foreach (string moddedUpgradeKey in RepoLibInterop.GetModdedUpgradeKeys()) { if (!hashSet.Contains(moddedUpgradeKey)) { hashSet2.Add(moddedUpgradeKey); } } return new DiscoveredUpgradesResult(hashSet, hashSet2); } } public static class DistributionService { public static bool IsDistributing { get; private set; } public static void DistributeUpgrade(UpgradeContext context, string upgradeKey, int difference) { SharedUpgradesPlus.LogVerbose($"[Distribute] {context.PlayerName} bought {upgradeKey} (+{difference})"); int num = ConfigService.UpgradeShareLimit(upgradeKey); PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>(); if ((Object)(object)component == (Object)null) { SharedUpgradesPlus.Logger.LogWarning((object)"[Distribute] PhotonView not found on PunManager, can't distribute."); return; } bool flag = RegistryService.Instance.IsVanilla(upgradeKey); if (!flag && !ConfigService.IsModdedUpgradesEnabled()) { SharedUpgradesPlus.LogInfo("[Distribute] " + upgradeKey + " is modded and modded upgrades are off, skipping."); return; } if (!ConfigService.IsUpgradeEnabled(upgradeKey)) { SharedUpgradesPlus.LogInfo("[Distribute] " + upgradeKey + " is disabled in config, skipping."); return; } string text = (flag ? new Upgrade(upgradeKey).CleanName : null); List<PlayerAvatar> list = SemiFunc.PlayerGetAll(); int num2 = ConfigService.SharedUpgradesChancePercentage(); SharedUpgradesPlus.LogVerbose($"[Distribute] {upgradeKey} (+{difference}): {list.Count} player(s), limit={num}, chance={num2}%"); IsDistributing = true; int num3 = 0; int num4 = 0; try { foreach (PlayerAvatar item in list) { if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null || item.photonView.ViewID == context.ViewID) { continue; } string steamID = item.steamID; if (string.IsNullOrEmpty(steamID)) { continue; } int value = 0; if (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(upgradeKey, out var value2)) { value2.TryGetValue(steamID, out value); } SharedUpgradesPlus.LogVerbose($"[Distribute] {item.playerName}: level={value}, limit={num}"); if (num > 0 && num <= value) { SharedUpgradesPlus.LogInfo($"[Distribute] {item.playerName} hit share limit ({num}), skipping."); num4++; continue; } if (!ConfigService.RollSharedUpgradesChance()) { SharedUpgradesPlus.LogInfo($"[Distribute] {item.playerName} roll failed ({num2}%), skipping."); num4++; continue; } int num5 = value + difference; SharedUpgradesPlus.LogAlways($"[Distribute] {item.playerName}: {value} -> {num5} (+{difference})"); if (flag) { component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, text, difference }); } else { component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { upgradeKey, steamID, num5 }); } num3++; } } catch (Exception ex) { SharedUpgradesPlus.Logger.LogError((object)("[Distribute] exception distributing " + upgradeKey + " for " + context.PlayerName + ": " + ex.Message)); } finally { IsDistributing = false; } SharedUpgradesPlus.LogVerbose($"[Distribute] done {upgradeKey}: sent={num3}, skipped={num4}"); HealBuyer(context, upgradeKey, difference); } private static void HealBuyer(UpgradeContext context, string upgradeKey, int difference) { if (upgradeKey != "playerUpgradeHealth" || !ConfigService.IsSharedUpgradeHealEnabled()) { return; } PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(context.SteamID); if (!((Object)(object)val == (Object)null)) { int num = val.playerHealth.maxHealth + 20 * difference - val.playerHealth.health; SharedUpgradesPlus.LogVerbose($"[Distribute] healing {context.PlayerName}: max={val.playerHealth.maxHealth}, current={val.playerHealth.health}, healing={num}"); if (num > 0) { val.playerHealth.HealOther(num, false); } } } } public class NetworkCallbackService : MonoBehaviourPunCallbacks { [CompilerGenerated] private sealed class <LateSyncPlayer>d__9 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public PlayerAvatar avatar; public string steamID; public Dictionary<string, int> teamSnapshot; public NetworkCallbackService <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LateSyncPlayer>d__9(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; NetworkCallbackService networkCallbackService = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = SyncService.ApplyTeamSnapshot(avatar, steamID, teamSnapshot); <>1__state = 1; return true; case 1: <>1__state = -1; networkCallbackService._pendingSync.Remove(avatar.photonView.Owner); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private readonly HashSet<Player> _pendingSync = new HashSet<Player>(); public static NetworkCallbackService? Instance { get; private set; } public override void OnJoinedRoom() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0038: Expected O, but got Unknown SharedUpgradesPlus.LogVerbose($"OnJoinedRoom (isMaster={PhotonNetwork.IsMasterClient})"); try { if (PhotonNetwork.IsMasterClient) { Hashtable val = new Hashtable(); ((Dictionary<object, object>)val).Add((object)"su__v1", (object)"1.4.2"); Hashtable val2 = val; PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); SharedUpgradesPlus.LogVerbose("Set room property: su__v1=1.4.2"); } } catch (Exception ex) { SharedUpgradesPlus.Logger.LogError((object)("Couldn't set room properties: " + ex.Message)); } } public override void OnPlayerEnteredRoom(Player newPlayer) { SharedUpgradesPlus.LogVerbose($"OnPlayerEnteredRoom: {newPlayer.NickName} (isMaster={SemiFunc.IsMasterClientOrSingleplayer()} lateJoin={ConfigService.IsLateJoinSyncEnabled()})"); if (ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled() && SemiFunc.IsMasterClientOrSingleplayer()) { _pendingSync.Add(newPlayer); SharedUpgradesPlus.LogAlways($"Deferred sync: {newPlayer.NickName} joined, queued. ({_pendingSync.Count} pending)"); } } public override void OnPlayerLeftRoom(Player otherPlayer) { bool flag = _pendingSync.Remove(otherPlayer); SharedUpgradesPlus.LogVerbose($"OnPlayerLeftRoom: {otherPlayer.NickName} (was pending: {flag}, pending count: {_pendingSync.Count})"); } public static bool IsPlayerPendingSync(Player player) { if ((Object)(object)Instance != (Object)null) { return Instance._pendingSync.Contains(player); } return false; } [IteratorStateMachine(typeof(<LateSyncPlayer>d__9))] public IEnumerator LateSyncPlayer(PlayerAvatar avatar, string steamID, Dictionary<string, int> teamSnapshot) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LateSyncPlayer>d__9(0) { <>4__this = this, avatar = avatar, steamID = steamID, teamSnapshot = teamSnapshot }; } private void Awake() { Instance = this; CatchUpExistingPlayers(); } private void CatchUpExistingPlayers() { if (ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled() && SemiFunc.IsMasterClientOrSingleplayer()) { Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { _pendingSync.Add(val); SharedUpgradesPlus.LogAlways($"Catch-up sync: {val.NickName} was already in room, queued. ({_pendingSync.Count} pending)"); } } } } public sealed class RegistryService { private readonly HashSet<Upgrade> vanillaUpgrades; private readonly HashSet<Upgrade> moddedUpgrades; private static readonly RegistryService instance = new RegistryService(); public IReadOnlyCollection<Upgrade> VanillaUpgrades => vanillaUpgrades; public IReadOnlyCollection<Upgrade> ModdedUpgrades => moddedUpgrades; public static RegistryService Instance => instance; private RegistryService() { vanillaUpgrades = new HashSet<Upgrade>(); moddedUpgrades = new HashSet<Upgrade>(); } public void RegisterAll(DiscoveredUpgradesResult result) { vanillaUpgrades.UnionWith(result.Vanilla.Select(MakeUpgradeFromKey)); moddedUpgrades.UnionWith(result.Modded.Select(MakeUpgradeFromKey)); SharedUpgradesPlus.Logger.LogInfo((object)$"Discovered {vanillaUpgrades.Count} vanilla and {moddedUpgrades.Count} modded upgrade(s)."); if (result.Vanilla.Count > 0) { SharedUpgradesPlus.LogVerbose("Vanilla: " + string.Join(", ", result.Vanilla)); } if (result.Modded.Count > 0) { SharedUpgradesPlus.LogVerbose("Modded: " + string.Join(", ", result.Modded)); } } public void Clear() { SharedUpgradesPlus.LogVerbose($"Registry cleared ({vanillaUpgrades.Count} vanilla, {moddedUpgrades.Count} modded)."); vanillaUpgrades.Clear(); moddedUpgrades.Clear(); } public bool IsVanilla(string key) { string key2 = key; return vanillaUpgrades.Any((Upgrade upgrade) => upgrade.Name.Equals(key2)); } public bool IsRegistered(string key) { string key2 = key; if (!IsVanilla(key2)) { return moddedUpgrades.Any((Upgrade upgrade) => upgrade.Name.Equals(key2)); } return true; } private Upgrade MakeUpgradeFromKey(string key) { return new Upgrade(key); } } internal static class RepoLibInterop { private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static bool _resolved; private static PropertyInfo? _playerUpgradesProp; private static PropertyInfo? _upgradeIdProp; public static HashSet<string> GetModdedUpgradeKeys() { HashSet<string> hashSet = new HashSet<string>(); if (!ResolveReflection()) { return hashSet; } try { if (!(_playerUpgradesProp.GetValue(null) is IEnumerable enumerable)) { return hashSet; } foreach (object item in enumerable) { if (item != null && _upgradeIdProp.GetValue(item) is string text && !string.IsNullOrEmpty(text)) { hashSet.Add("playerUpgrade" + text); } } } catch (Exception ex) { SharedUpgradesPlus.LogVerbose("[RepoLibInterop] reflection failed: " + ex.Message); } return hashSet; } private static bool ResolveReflection() { if (_resolved) { if (_playerUpgradesProp != null) { return _upgradeIdProp != null; } return false; } _resolved = true; Type type = AccessTools.TypeByName("REPOLib.Modules.Upgrades"); if (type == null) { return false; } _playerUpgradesProp = type.GetProperty("PlayerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (_playerUpgradesProp == null) { return false; } Type type2 = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade"); if (type2 == null) { return false; } _upgradeIdProp = type2.GetProperty("UpgradeId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); return _upgradeIdProp != null; } } public static class SnapshotService { public static Dictionary<string, int> SnapshotPlayerStats(string steamID) { string steamID2 = steamID; if (string.IsNullOrEmpty(steamID2) || StatsManager.instance == null) { return new Dictionary<string, int>(); } Dictionary<string, int> dictionary = StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> kvp) => RegistryService.Instance.IsRegistered(kvp.Key)).ToDictionary((KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Key, (KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Value.GetValueOrDefault(steamID2, 0)); SharedUpgradesPlus.LogVerbose($"[Snapshot] Player snapshot for {steamID2}: {dictionary.Count} upgrade(s)."); return dictionary; } public static Dictionary<string, int> SnapshotTeamMaxLevels(string? excludeSteamID = null) { string excludeSteamID2 = excludeSteamID; Dictionary<string, int> dictionary = new Dictionary<string, int>(); if (StatsManager.instance == null) { return dictionary; } foreach (KeyValuePair<string, Dictionary<string, int>> item in StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> k) => RegistryService.Instance.IsRegistered(k.Key))) { IEnumerable<int> enumerable; if (!string.IsNullOrEmpty(excludeSteamID2)) { enumerable = from p in item.Value where p.Key != excludeSteamID2 select p.Value; } else { IEnumerable<int> values = item.Value.Values; enumerable = values; } IEnumerable<int> source = enumerable; dictionary[item.Key] = source.DefaultIfEmpty(0).Max(); } SharedUpgradesPlus.LogVerbose(string.Format("[Snapshot] Team snapshot (exclude={0}): {1} upgrade(s).", excludeSteamID2 ?? "none", dictionary.Count)); return dictionary; } } public static class SyncService { [CompilerGenerated] private sealed class <ApplyTeamSnapshot>d__0 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public PlayerAvatar player; public Dictionary<string, int> teamSnapshot; public string steamID; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ApplyTeamSnapshot>d__0(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { if (<>1__state != 0) { return false; } <>1__state = -1; if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null) { return false; } PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>(); if ((Object)(object)component == (Object)null) { SharedUpgradesPlus.Logger.LogWarning((object)"[LateJoin] PhotonView not found on PunManager, skipping sync."); return false; } string playerName = player.playerName; int num = ConfigService.SharedUpgradesChancePercentage(); SharedUpgradesPlus.LogAlways($"[LateJoin] syncing {playerName}: {teamSnapshot.Count} upgrade(s), chance={num}%"); int num2 = 0; int num3 = 0; foreach (KeyValuePair<string, int> item in teamSnapshot) { int num4 = ConfigService.UpgradeShareLimit(item.Key); bool flag = RegistryService.Instance.IsVanilla(item.Key); SharedUpgradesPlus.LogVerbose($"[LateJoin] {item.Key}: teamMax={item.Value}, isVanilla={flag}, limit={num4}"); if (!flag && !ConfigService.IsModdedUpgradesEnabled()) { SharedUpgradesPlus.LogVerbose("[LateJoin] " + item.Key + ": skipped (modded upgrades disabled)."); num3++; continue; } if (!ConfigService.IsUpgradeEnabled(item.Key)) { SharedUpgradesPlus.LogVerbose("[LateJoin] " + item.Key + ": skipped (disabled in config)."); num3++; continue; } Dictionary<string, int> value; int num5 = (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(item.Key, out value) ? value.GetValueOrDefault(steamID, 0) : 0); if (num4 > 0 && num4 <= num5) { SharedUpgradesPlus.LogInfo($"[LateJoin] {item.Key}: {playerName} hit share limit ({num4}), skipping."); num3++; continue; } int value2 = item.Value; int num6 = value2 - num5; if (num4 > 0) { num6 = Math.Min(num6, num4 - num5); } SharedUpgradesPlus.LogVerbose($"[LateJoin] {item.Key}: level={num5}, teamMax={item.Value}, diff={num6} (pre-roll)"); num6 = SimulateRealisticLevelling(num6); value2 = num5 + num6; if (num6 <= 0) { SharedUpgradesPlus.LogInfo("[LateJoin] " + item.Key + ": rolled 0 after chance simulation, skipping."); num3++; continue; } if (flag) { SharedUpgradesPlus.LogVerbose("[LateJoin] " + item.Key + ": sending TesterUpgradeCommandRPC to " + playerName); component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, new Upgrade(item.Key).CleanName, num6 }); } else { SharedUpgradesPlus.LogVerbose("[LateJoin] " + item.Key + ": sending UpdateStatRPC to " + playerName); component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { item.Key, steamID, value2 }); } SharedUpgradesPlus.LogVerbose($"[LateJoin] sent {item.Key} (+{num6}) to {playerName}."); num2++; } SharedUpgradesPlus.LogAlways($"[LateJoin] done {playerName}: sent={num2}, skipped={num3}"); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [IteratorStateMachine(typeof(<ApplyTeamSnapshot>d__0))] public static IEnumerator ApplyTeamSnapshot(PlayerAvatar player, string steamID, Dictionary<string, int> teamSnapshot) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ApplyTeamSnapshot>d__0(0) { player = player, steamID = steamID, teamSnapshot = teamSnapshot }; } private static int SimulateRealisticLevelling(int value) { int num = ConfigService.SharedUpgradesChancePercentage(); if (num >= 100 || value <= 0) { return value; } int num2 = 0; for (int i = 0; i < value; i++) { if (ConfigService.RollSharedUpgradesChance()) { num2++; } } SharedUpgradesPlus.LogVerbose($"[LateJoin] roll simulation: input={value}, chance={num}%, result={num2}"); return num2; } } internal class WatermarkService : MonoBehaviour { [CompilerGenerated] private sealed class <Poll>d__10 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public WatermarkService <>4__this; private float <elapsed>5__2; object? IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <Poll>d__10(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown int num = <>1__state; WatermarkService watermarkService = <>4__this; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; <elapsed>5__2 += 1f; try { if (PhotonNetwork.InRoom) { if (string.IsNullOrEmpty(OwnerID)) { goto IL_0171; } PlayerAvatar val = null; foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if ((Object)(object)item?.photonView != (Object)null && item.photonView.IsMine) { val = item; break; } } if (!((Object)(object)val == (Object)null)) { if (val.steamID != OwnerID) { goto IL_0171; } bool isMasterClient = PhotonNetwork.IsMasterClient; bool flag = ((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).ContainsKey((object)"su__v1"); if (isMasterClient || flag) { if (((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"su__v1", out object value)) { Version = (value as string) ?? "UNKNOWN"; } watermarkService.show = !isMasterClient && flag; goto IL_0171; } } } } catch { } } else { <>1__state = -1; watermarkService.polling = true; <elapsed>5__2 = 0f; } if (<elapsed>5__2 < 10f) { <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; } goto IL_0171; IL_0171: watermarkService.polling = false; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal const string RoomKey = "su__v1"; private static string Version = "UNKNOWN"; private static readonly string? OwnerID = LoadOwnerID(); private bool show; private bool polling; private Object? lastLevel; private string? lastRoom; private GUIStyle? style; private static string? LoadOwnerID() { try { string path = Path.Combine(Paths.ConfigPath, "SharedUpgradesPlus.owner"); if (!File.Exists(path)) { return null; } return File.ReadAllText(path).Trim(); } catch { return null; } } private void Update() { if ((Object)(object)RunManager.instance == (Object)null) { return; } Level levelCurrent = RunManager.instance.levelCurrent; Room currentRoom = PhotonNetwork.CurrentRoom; string text = ((currentRoom != null) ? currentRoom.Name : null); if ((Object)(object)levelCurrent == lastLevel && text == lastRoom) { return; } lastLevel = (Object?)(object)levelCurrent; lastRoom = text; if (text == null) { show = false; } if ((Object)(object)levelCurrent == (Object)(object)RunManager.instance.levelLobbyMenu) { show = false; if (!polling) { ((MonoBehaviour)this).StartCoroutine(Poll()); } } } [IteratorStateMachine(typeof(<Poll>d__10))] private IEnumerator Poll() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <Poll>d__10(0) { <>4__this = this }; } private void OnGUI() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown if (!show) { return; } try { if (style == null) { GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 24 }; val.normal.textColor = new Color(1f, 1f, 1f, 0.15f); style = val; } GUI.Label(new Rect(6f, (float)(Screen.height - 72), 160f, 56f), "SUP: " + Version, style); } catch { } } } } namespace SharedUpgradesPlus.Patches { [HarmonyPatch(typeof(PunManager), "UpdateStatRPC")] internal class ModdedUpgradesPatch { [HarmonyPrefix] public static void Prefix(string dictionaryName, string key, out int __state) { __state = 0; if (!((Object)(object)StatsManager.instance == (Object)null) && StatsManager.instance.dictionaryOfDictionaries.TryGetValue(dictionaryName, out var value)) { value.TryGetValue(key, out __state); } } [HarmonyPostfix] public static void Postfix(string dictionaryName, string key, int value, int __state) { //IL_0123: Unknown result type (might be due to invalid IL or missing references) if (!ConfigService.IsSharedUpgradesEnabled() || !ConfigService.IsModdedUpgradesEnabled() || !RegistryService.Instance.IsRegistered(dictionaryName) || RegistryService.Instance.IsVanilla(dictionaryName) || !ConfigService.IsUpgradeEnabled(dictionaryName)) { return; } PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(key); int num = value - __state; SharedUpgradesPlus.LogVerbose(string.Format("[ModdedPatch] {0} ({1}): {2} -> {3} (+{4}), player={5}, distributing={6}", dictionaryName, key, __state, value, num, val?.playerName ?? "not found", DistributionService.IsDistributing)); if ((Object)(object)val != (Object)null && ConfigService.IsShareNotificationEnabled()) { SharedUpgradesPlus.LogVerbose("[ModdedPatch] running effects for " + val.playerName); if (val.isLocal) { SharedUpgradesPlus.LogVerbose("[ModdedPatch] local player, triggering StatsUI + CameraGlitch."); StatsUI.instance.Fetch(); StatsUI.instance.ShowStats(); CameraGlitch.Instance.PlayUpgrade(); } else { SharedUpgradesPlus.LogVerbose("[ModdedPatch] remote player, shaking camera."); GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)val).transform.position, 0.2f); } if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient) { SharedUpgradesPlus.LogVerbose("[ModdedPatch] applying upgrade material effect to " + val.playerName + "."); val.playerHealth.MaterialEffectOverride((Effect)0); } } if (num > 0 && SemiFunc.IsMasterClientOrSingleplayer()) { if (DistributionService.IsDistributing) { SharedUpgradesPlus.LogVerbose("[ModdedPatch] already distributing, skipping " + dictionaryName + "."); } else if ((Object)(object)val == (Object)null || (Object)(object)val.photonView == (Object)null) { SharedUpgradesPlus.Logger.LogWarning((object)("[ModdedPatch] no PlayerAvatar found for " + key + ", can't distribute " + dictionaryName + ".")); } else { string playerName = val.playerName; SharedUpgradesPlus.LogAlways($"[ModdedPatch] {playerName} bought {dictionaryName}: {__state} -> {value} (+{num}), distributing..."); DistributionService.DistributeUpgrade(new UpgradeContext(key, val.photonView.ViewID, playerName, new Dictionary<string, int>()), dictionaryName, num); } } } } [HarmonyPatch(typeof(PlayerTumble), "SetupDone")] internal class PlayerTumbleSetupDonePatch { [HarmonyPostfix] public static void Postfix(PlayerTumble __instance) { if (SemiFunc.IsMasterClientOrSingleplayer() && !string.IsNullOrEmpty(__instance.playerAvatar.steamID) && NetworkCallbackService.IsPlayerPendingSync(__instance.playerAvatar.photonView.Owner) && ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled()) { SharedUpgradesPlus.LogAlways("[LateJoin] SetupDone fired for " + __instance.playerAvatar.steamID + ", triggering sync."); Dictionary<string, int> teamSnapshot = SnapshotService.SnapshotTeamMaxLevels(__instance.playerAvatar.steamID); if ((Object)(object)NetworkCallbackService.Instance == (Object)null) { SharedUpgradesPlus.Logger.LogError((object)"NetworkCallbackService instance is null. Cannot sync player stats."); } else { ((MonoBehaviour)NetworkCallbackService.Instance).StartCoroutine(NetworkCallbackService.Instance.LateSyncPlayer(__instance.playerAvatar, __instance.playerAvatar.steamID, teamSnapshot)); } } } } [HarmonyPatch(typeof(PunManager), "TesterUpgradeCommandRPC")] internal class PlayerUpgradeEffectPatch { [HarmonyPostfix] public static void Postfix(string _steamID, string upgradeName, int upgradeNum, PhotonMessageInfo _info) { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) bool flag = upgradeName == "Health"; PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(_steamID); if ((Object)(object)val == (Object)null) { SharedUpgradesPlus.Logger.LogError((object)("[Effects] TesterUpgradeCommandRPC fired for " + _steamID + " but no PlayerAvatar found, skipping effects.")); return; } SharedUpgradesPlus.LogVerbose($"[Effects] {val.playerName} got {upgradeName} x{upgradeNum} (local={val.isLocal})"); if (ConfigService.IsShareNotificationEnabled()) { if (val.isLocal) { SharedUpgradesPlus.LogVerbose("[Effects] " + val.playerName + " is local, StatsUI + CameraGlitch."); StatsUI.instance.Fetch(); StatsUI.instance.ShowStats(); CameraGlitch.Instance.PlayUpgrade(); } else { SharedUpgradesPlus.LogVerbose("[Effects] " + val.playerName + " is remote, camera shake."); GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)val).transform.position, 0.2f); } if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient) { SharedUpgradesPlus.LogVerbose("[Effects] applying upgrade material effect to " + val.playerName + "."); val.playerHealth.MaterialEffectOverride((Effect)0); } } SharedUpgradesPlus.LogVerbose("[Effects] " + val.playerName + " effects done."); if (flag && SemiFunc.IsMasterClientOrSingleplayer() && ConfigService.IsSharedUpgradeHealEnabled()) { int num = val.playerHealth.maxHealth + 20 * upgradeNum - val.playerHealth.health; SharedUpgradesPlus.LogVerbose($"[Effects] healing {val.playerName}: max={val.playerHealth.maxHealth}, current={val.playerHealth.health}, healing={num}"); if (num > 0) { val.playerHealth.HealOther(num, false); } } } } [HarmonyPatch(typeof(StatsManager), "RunStartStats")] internal class REPOLibSyncPatch { private static readonly Type _upgradeType = AccessTools.TypeByName("REPOLib.Modules.Upgrades"); private static readonly Type _playerUpgradeType = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade"); private static readonly FieldInfo? _playerDictionaryField = ((_playerUpgradeType != null) ? AccessTools.Field(_playerUpgradeType, "PlayerDictionary") : null); private static readonly FieldInfo? _playerUpgradesField = ((_upgradeType != null) ? AccessTools.Field(_upgradeType, "_playerUpgrades") : null); [HarmonyPostfix] [HarmonyPriority(0)] public static void Postfix() { if (_playerDictionaryField == null || _playerUpgradesField == null || (Object)(object)StatsManager.instance == (Object)null || !(_playerUpgradesField.GetValue(null) is IDictionary dictionary)) { return; } SharedUpgradesPlus.LogVerbose($"Syncing {dictionary.Count} REPOLib upgrade(s) to StatsManager."); int num = 0; foreach (DictionaryEntry item in dictionary) { if (item.Value != null) { string text = $"playerUpgrade{item.Key}"; if (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(text, out var value)) { _playerDictionaryField.SetValue(item.Value, value); SharedUpgradesPlus.LogInfo("Synced PlayerDictionary for " + text + "."); num++; } } } SharedUpgradesPlus.LogVerbose($"REPOLib sync done: {num}/{dictionary.Count} upgrade(s)."); } } [HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")] internal class SharedUpgradesPatch { private static PlayerAvatar? GetUpgradePlayer(ItemUpgrade instance, out int viewID) { viewID = 0; ItemToggle itemToggle = instance.itemToggle; if (itemToggle == null || !itemToggle.toggleState) { return null; } viewID = instance.itemToggle.playerTogglePhotonID; return SemiFunc.PlayerAvatarGetFromPhotonID(viewID); } [HarmonyPrefix] public static void Prefix(ItemUpgrade __instance, out UpgradeContext? __state) { __state = null; if (!ConfigService.IsSharedUpgradesEnabled() || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } int viewID; PlayerAvatar upgradePlayer = GetUpgradePlayer(__instance, out viewID); if (upgradePlayer == null) { SharedUpgradesPlus.LogVerbose("[Purchase] upgrade interaction fired but couldn't find a player, skipping."); return; } string steamID = upgradePlayer.steamID; if (!string.IsNullOrEmpty(steamID)) { string text = null; ItemAttributes itemAttributes = __instance.itemAttributes; if (itemAttributes != null && (Object)(object)itemAttributes.item != (Object)null) { text = ((Object)itemAttributes.item).name; } SharedUpgradesPlus.LogVerbose("[Purchase] " + upgradePlayer.playerName + " is buying '" + text + "'"); __state = new UpgradeContext(steamID, playerName: upgradePlayer.playerName, viewID: viewID, levelsBefore: SnapshotService.SnapshotPlayerStats(steamID), itemName: text); } } [HarmonyPostfix] public static void Postfix(UpgradeContext? __state) { if (!SemiFunc.IsMasterClientOrSingleplayer() || __state == null) { return; } SharedUpgradesPlus.LogVerbose("[Purchase] checking what " + __state.PlayerName + " just bought (item='" + __state.ItemName + "')"); bool flag = false; foreach (KeyValuePair<string, Dictionary<string, int>> item in StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> key) => RegistryService.Instance.IsRegistered(key.Key))) { item.Value.TryGetValue(__state.SteamID, out var value); __state.LevelsBefore.TryGetValue(item.Key, out var value2); SharedUpgradesPlus.LogVerbose($"[Purchase] {item.Key}: {value2} -> {value}"); if (value > value2) { int num = value - value2; flag = true; SharedUpgradesPlus.LogAlways($"[Purchase] {__state.PlayerName} bought {item.Key} (+{num}), distributing..."); DistributionService.DistributeUpgrade(__state, item.Key, num); } } SharedUpgradesPlus.LogVerbose($"[Purchase] vanilla scan done, distributed={flag}"); if (!flag && __state.ItemName != null && ConfigService.IsModdedUpgradesEnabled()) { SharedUpgradesPlus.LogVerbose("[Purchase] no vanilla upgrades changed, checking modded match for '" + __state.ItemName + "'"); string text = MatchItemNameToModdedUpgrade(__state.ItemName); if (text != null) { SharedUpgradesPlus.LogInfo("[Purchase] " + __state.PlayerName + " (" + __state.SteamID + ") bought modded " + text + " (+1), distributing..."); DistributionService.DistributeUpgrade(__state, text, 1); } else { SharedUpgradesPlus.LogVerbose("[Purchase] no match for '" + __state.ItemName + "', nothing to distribute."); } } } private static string? MatchItemNameToModdedUpgrade(string itemName) { string text = itemName.Replace(" ", ""); foreach (Upgrade moddedUpgrade in RegistryService.Instance.ModdedUpgrades) { string value = moddedUpgrade.CleanName.Replace(" ", ""); if (text.EndsWith(value, StringComparison.OrdinalIgnoreCase)) { return moddedUpgrade.Name; } } return null; } } [HarmonyPatch(typeof(StatsManager), "Start")] internal class StatsManagerPatch { private static NetworkCallbackService? _callbackService; [HarmonyPostfix] public static void Postfix(StatsManager __instance) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown SharedUpgradesPlus.LogVerbose("StatsManager.Start: initial discovery."); RefreshRegistry(__instance); if ((Object)(object)_callbackService == (Object)null) { GameObject val = new GameObject("SharedUpgradesPlus_Services"); _callbackService = val.AddComponent<NetworkCallbackService>(); val.AddComponent<WatermarkService>(); Object.DontDestroyOnLoad((Object)val); SharedUpgradesPlus.LogVerbose("Created NetworkCallbackService and WatermarkService."); } else { SharedUpgradesPlus.LogVerbose("NetworkCallbackService already exists, skipping."); } } internal static void RefreshRegistry(StatsManager statsManager) { DiscoveredUpgradesResult discoveredUpgradesResult = DiscoveryService.DiscoveredUpgrades(statsManager); SharedUpgradesPlus.LogVerbose($"Found {discoveredUpgradesResult.Vanilla.Count} vanilla and {discoveredUpgradesResult.Modded.Count} modded upgrade(s)."); RegistryService.Instance.Clear(); RegistryService.Instance.RegisterAll(discoveredUpgradesResult); ConfigService.LoadModsIntoConfig(); } } [HarmonyPatch(typeof(StatsManager), "RunStartStats")] [HarmonyAfter(new string[] { "REPOLib" })] internal class StatsManagerRunStartStatsPatch { [HarmonyPostfix] public static void Postfix(StatsManager __instance) { SharedUpgradesPlus.LogVerbose("StatsManager.RunStartStats: re-discovering after REPOLib."); StatsManagerPatch.RefreshRegistry(__instance); } } [HarmonyPatch(typeof(StatsManager), "LoadGame")] internal class StatsManagerLoadGamePatch { [HarmonyPostfix] public static void Postfix(StatsManager __instance) { SharedUpgradesPlus.LogVerbose("StatsManager.LoadGame: re-discovering after save load."); StatsManagerPatch.RefreshRegistry(__instance); } } } namespace SharedUpgradesPlus.Models { public sealed class DiscoveredUpgradesResult { public HashSet<string> Vanilla { get; } public HashSet<string> Modded { get; } public DiscoveredUpgradesResult(HashSet<string> vanilla, HashSet<string> modded) { Vanilla = vanilla; Modded = modded; base..ctor(); } } public sealed class Upgrade : IEquatable<Upgrade> { public string Name { get; } public string CleanName { get { if (!Name.StartsWith("playerUpgrade")) { return Name; } string name = Name; int length = "playerUpgrade".Length; return name.Substring(length, name.Length - length); } } public Upgrade(string Name) { this.Name = Name; base..ctor(); } public bool Equals(Upgrade? other) { if (other != null) { return other.Name == Name; } return false; } public override bool Equals(object? obj) { return Equals(obj as Upgrade); } public override int GetHashCode() { return Name.GetHashCode(); } } public sealed class UpgradeContext { public string SteamID { get; } public int ViewID { get; } public string PlayerName { get; } public Dictionary<string, int> LevelsBefore { get; } public string? ItemName { get; } public UpgradeContext(string steamID, int viewID, string playerName, Dictionary<string, int> levelsBefore, string? itemName = null) { SteamID = steamID; ViewID = viewID; PlayerName = playerName; LevelsBefore = levelsBefore; ItemName = itemName; base..ctor(); } } } namespace SharedUpgradesPlus.Configuration { public enum VerbosityLevel { Off, Debug, Verbose } internal static class PluginConfig { public static ConfigEntry<bool> EnableSharedUpgrades; public static ConfigEntry<int> SharedUpgradeChance; public static ConfigEntry<bool> EnableLateJoinSync; public static ConfigEntry<bool> EnableModdedUpgrades; public static ConfigEntry<bool> EnableSharedUpgradeHeal; public static ConfigEntry<bool> EnableShareNotification; public static ConfigEntry<VerbosityLevel> LoggingLevel; public static ConfigFile? ConfigFile; public static void Init(ConfigFile config) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown ConfigFile = config; EnableSharedUpgrades = config.Bind<bool>("General", "EnableSharedUpgrades", true, "Enable or disable all upgrade sharing"); SharedUpgradeChance = config.Bind<int>("General", "SharedUpgradesChance", 100, new ConfigDescription("Chance per upgrade level to be shared with each player", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); EnableLateJoinSync = config.Bind<bool>("General", "LateJoinSync", true, "Sync upgrades to players who join mid-run"); EnableModdedUpgrades = config.Bind<bool>("General", "EnableModdedUpgrades", true, "Sync upgrades added by other mods"); EnableSharedUpgradeHeal = config.Bind<bool>("Effects", "EnableSharedUpgradeHeal", false, "Heal players to full HP when receiving a shared health upgrade"); EnableShareNotification = config.Bind<bool>("Effects", "EnableShareNotification", true, "Provide a visual effect when upgrades are shared with you"); LoggingLevel = config.Bind<VerbosityLevel>("General", "LogLevel", VerbosityLevel.Off, "Off: key events only (sync start/result, purchases). Debug: per-player distribution results and skips. Verbose: full trace of every step."); } } }