using 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 SharedUpgrades__.Configuration;
using SharedUpgrades__.Models;
using SharedUpgrades__.Services;
using UnityEngine;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("Vippy")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyInformationalVersion("1.3.1+b6ecc940065b3d032d090282e54a364f64c74104")]
[assembly: AssemblyProduct("SharedUpgrades++")]
[assembly: AssemblyTitle("SharedUpgrades++")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.1.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 SharedUpgrades__
{
internal static class BuildInfo
{
public const string Version = "1.3.1";
}
[BepInPlugin("Vippy.SharedUpgradesPlus", "SharedUpgradesPlus", "1.3.1")]
public class SharedUpgrades__ : BaseUnityPlugin
{
internal static SharedUpgrades__ 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_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Expected O, but got Unknown
//IL_0026: Expected O, but got Unknown
if (Harmony == null)
{
Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
Harmony val2 = val;
Harmony = val;
}
Harmony.PatchAll();
}
internal void Unpatch()
{
Harmony? harmony = Harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
}
}
namespace SharedUpgrades__.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_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00b2: 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);
}
}
return new DiscoveredUpgradesResult(hashSet, hashSet2);
}
}
public static class DistributionService
{
private static readonly FieldInfo _steamID = AccessTools.Field(typeof(PlayerAvatar), "steamID");
public static bool IsDistributing { get; private set; }
public static void DistributeUpgrade(UpgradeContext context, string upgradeKey, int difference, int currentValue)
{
SharedUpgrades__.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)
{
SharedUpgrades__.Logger.LogWarning((object)"[Distribute] PhotonView not found on PunManager, can't distribute.");
return;
}
bool flag = RegistryService.Instance.IsVanilla(upgradeKey);
if (!flag && !ConfigService.IsModdedUpgradesEnabled())
{
SharedUpgrades__.LogInfo("[Distribute] " + upgradeKey + " is modded and modded upgrades are off, skipping.");
return;
}
if (!ConfigService.IsUpgradeEnabled(upgradeKey))
{
SharedUpgrades__.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();
SharedUpgrades__.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 text2 = (string)_steamID.GetValue(item);
if (string.IsNullOrEmpty(text2))
{
continue;
}
int value = 0;
if (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(upgradeKey, out var value2))
{
value2.TryGetValue(text2, out value);
}
SharedUpgrades__.LogVerbose($"[Distribute] {item.playerName} — level={value}, limit={num}");
if (num > 0 && num <= value)
{
SharedUpgrades__.LogInfo($"[Distribute] {item.playerName} hit share limit ({num}), skipping.");
num4++;
continue;
}
if (!ConfigService.RollSharedUpgradesChance())
{
SharedUpgrades__.LogInfo($"[Distribute] {item.playerName} roll failed ({num2}%), skipping.");
num4++;
continue;
}
if (flag)
{
SharedUpgrades__.LogVerbose("[Distribute] sending TesterUpgradeCommandRPC to " + item.playerName);
component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { text2, text, difference });
}
else
{
SharedUpgrades__.LogVerbose("[Distribute] sending UpdateStatRPC to " + item.playerName);
component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { upgradeKey, text2, currentValue });
}
SharedUpgrades__.LogInfo($"[Distribute] sent {upgradeKey} (+{difference}) to {item.playerName}.");
num3++;
}
}
catch (Exception ex)
{
SharedUpgrades__.Logger.LogError((object)("[Distribute] exception distributing " + upgradeKey + " for " + context.PlayerName + ": " + ex.Message));
}
finally
{
IsDistributing = false;
}
SharedUpgrades__.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;
SharedUpgrades__.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 <>c__DisplayClass12_0
{
public Player joiningPlayer;
public Func<PlayerAvatar, bool> <>9__0;
internal bool <WaitAndSync>b__0(PlayerAvatar p)
{
int result;
if ((Object)(object)p.photonView != (Object)null)
{
Player owner = p.photonView.Owner;
result = ((((owner != null) ? new int?(owner.ActorNumber) : null) == joiningPlayer.ActorNumber) ? 1 : 0);
}
else
{
result = 0;
}
return (byte)result != 0;
}
}
[CompilerGenerated]
private sealed class <WaitAndSync>d__12 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public Player joiningPlayer;
public NetworkCallbackService <>4__this;
private <>c__DisplayClass12_0 <>8__1;
private float <elapsed>5__2;
private PlayerAvatar <avatar>5__3;
private string <steamID>5__4;
private Dictionary<string, int> <teamSnapshot>5__5;
private bool <tumbleReady>5__6;
private bool <grabberReady>5__7;
private bool <healthReady>5__8;
private bool <statsReady>5__9;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <WaitAndSync>d__12(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>8__1 = null;
<avatar>5__3 = null;
<steamID>5__4 = null;
<teamSnapshot>5__5 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
//IL_00b3: Expected O, but got Unknown
//IL_037a: Unknown result type (might be due to invalid IL or missing references)
//IL_0384: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>8__1 = new <>c__DisplayClass12_0();
<>8__1.joiningPlayer = joiningPlayer;
<elapsed>5__2 = 0f;
<avatar>5__3 = null;
<steamID>5__4 = string.Empty;
SharedUpgrades__.LogVerbose($"Waiting for {<>8__1.joiningPlayer.NickName} to be ready (max {12f}s)...");
goto IL_028f;
case 1:
<>1__state = -1;
<elapsed>5__2 += 0.3f;
if (<avatar>5__3 == null)
{
<avatar>5__3 = ((IEnumerable<PlayerAvatar>)SemiFunc.PlayerGetAll()).FirstOrDefault((Func<PlayerAvatar, bool>)delegate(PlayerAvatar p)
{
int result;
if ((Object)(object)p.photonView != (Object)null)
{
Player owner = p.photonView.Owner;
result = ((((owner != null) ? new int?(owner.ActorNumber) : null) == <>8__1.joiningPlayer.ActorNumber) ? 1 : 0);
}
else
{
result = 0;
}
return (byte)result != 0;
});
}
if ((Object)(object)<avatar>5__3 != (Object)null)
{
<steamID>5__4 = (string)_steamID.GetValue(<avatar>5__3);
}
<tumbleReady>5__6 = (Object)(object)<avatar>5__3 != (Object)null && _tumble.GetValue(<avatar>5__3) != null;
<grabberReady>5__7 = (Object)(object)<avatar>5__3 != (Object)null && _physGrabber.GetValue(<avatar>5__3) != null;
<healthReady>5__8 = (Object)(object)<avatar>5__3 != (Object)null && _playerHealth.GetValue(<avatar>5__3) != null;
<statsReady>5__9 = !string.IsNullOrEmpty(<steamID>5__4) && StatsManager.instance.playerUpgradeStrength.ContainsKey(<steamID>5__4);
SharedUpgrades__.LogVerbose($"{<>8__1.joiningPlayer.NickName} not ready yet ({<elapsed>5__2:F1}s) — tumble={<tumbleReady>5__6}, grabber={<grabberReady>5__7}, health={<healthReady>5__8}, stats={<statsReady>5__9}");
if (!(!string.IsNullOrEmpty(<steamID>5__4) & <tumbleReady>5__6 & <grabberReady>5__7 & <healthReady>5__8 & <statsReady>5__9))
{
goto IL_028f;
}
goto IL_02a5;
case 2:
<>1__state = -1;
<teamSnapshot>5__5 = SnapshotService.SnapshotTeamMaxLevels(<steamID>5__4);
SharedUpgrades__.LogVerbose(<>8__1.joiningPlayer.NickName + " — snapshot taken (exclude=" + <steamID>5__4 + "), starting sync.");
<>2__current = SyncService.ApplyTeamSnapshot(<avatar>5__3, <steamID>5__4, <teamSnapshot>5__5);
<>1__state = 3;
return true;
case 3:
{
<>1__state = -1;
return false;
}
IL_02a5:
if ((Object)(object)<avatar>5__3 == (Object)null || string.IsNullOrEmpty(<steamID>5__4) || _tumble.GetValue(<avatar>5__3) == null || _physGrabber.GetValue(<avatar>5__3) == null || _playerHealth.GetValue(<avatar>5__3) == null || !StatsManager.instance.playerUpgradeStrength.ContainsKey(<steamID>5__4))
{
SharedUpgrades__.Logger.LogWarning((object)$"Late join: timed out waiting for {<>8__1.joiningPlayer.NickName} after {12f}s, skipping sync.");
return false;
}
SharedUpgrades__.LogVerbose(<>8__1.joiningPlayer.NickName + " (" + <steamID>5__4 + ") is ready, waiting for stats to stabilize...");
<>2__current = (object)new WaitForSeconds(2f);
<>1__state = 2;
return true;
IL_028f:
if (<elapsed>5__2 < 12f)
{
<>2__current = (object)new WaitForSeconds(0.3f);
<>1__state = 1;
return true;
}
goto IL_02a5;
}
}
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 static readonly FieldInfo _steamID = AccessTools.Field(typeof(PlayerAvatar), "steamID");
private static readonly FieldInfo _tumble = AccessTools.Field(typeof(PlayerAvatar), "tumble");
private static readonly FieldInfo _physGrabber = AccessTools.Field(typeof(PlayerAvatar), "physGrabber");
private static readonly FieldInfo _playerHealth = AccessTools.Field(typeof(PlayerAvatar), "playerHealth");
private readonly HashSet<Player> _pendingSync = new HashSet<Player>();
public override void OnJoinedRoom()
{
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Expected O, but got Unknown
//IL_0041: Expected O, but got Unknown
SharedUpgrades__.LogVerbose($"OnJoinedRoom (isMaster={PhotonNetwork.IsMasterClient})");
try
{
if (PhotonNetwork.IsMasterClient)
{
Hashtable val = new Hashtable();
((Dictionary<object, object>)val).Add((object)"su__v1", (object)"1.3.1");
Hashtable val2 = val;
PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
SharedUpgrades__.LogVerbose("Set room property: su__v1=1.3.1");
}
}
catch (Exception ex)
{
SharedUpgrades__.Logger.LogError((object)("Couldn't set room properties: " + ex.Message));
}
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
SharedUpgrades__.LogVerbose($"OnPlayerEnteredRoom: {newPlayer.NickName} (isMaster={SemiFunc.IsMasterClientOrSingleplayer()}, activeRun={IsActiveRun()}, lateJoin={ConfigService.IsLateJoinSyncEnabled()})");
if (ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled() && SemiFunc.IsMasterClientOrSingleplayer())
{
if (!IsActiveRun())
{
_pendingSync.Add(newPlayer);
SharedUpgrades__.LogAlways($"Deferred sync: {newPlayer.NickName} joined outside of a level, queued. ({_pendingSync.Count} pending)");
}
else
{
SharedUpgrades__.LogVerbose("Starting immediate sync for " + newPlayer.NickName + ".");
((MonoBehaviour)this).StartCoroutine(WaitAndSync(newPlayer));
}
}
}
public override void OnPlayerLeftRoom(Player otherPlayer)
{
bool flag = _pendingSync.Remove(otherPlayer);
SharedUpgrades__.LogVerbose($"OnPlayerLeftRoom: {otherPlayer.NickName} (was pending: {flag}, pending count: {_pendingSync.Count})");
}
private static bool IsActiveRun()
{
if ((Object)(object)RunManager.instance == (Object)null)
{
return false;
}
Level levelCurrent = RunManager.instance.levelCurrent;
return (Object)(object)levelCurrent != (Object)(object)RunManager.instance.levelMainMenu && (Object)(object)levelCurrent != (Object)(object)RunManager.instance.levelLobbyMenu && (Object)(object)levelCurrent != (Object)(object)RunManager.instance.levelRecording && (Object)(object)levelCurrent != (Object)(object)RunManager.instance.levelSplashScreen;
}
public override void OnEnable()
{
((MonoBehaviourPunCallbacks)this).OnEnable();
SceneManager.sceneLoaded += OnSceneLoaded;
}
public override void OnDisable()
{
((MonoBehaviourPunCallbacks)this).OnDisable();
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
SharedUpgrades__.LogVerbose($"OnSceneLoaded: {((Scene)(ref scene)).name} ({_pendingSync.Count} pending, activeRun={IsActiveRun()})");
if (_pendingSync.Count == 0 || !IsActiveRun() || !ConfigService.IsLateJoinSyncEnabled() || !ConfigService.IsSharedUpgradesEnabled() || !SemiFunc.IsMasterClientOrSingleplayer())
{
return;
}
SharedUpgrades__.LogAlways($"Deferred sync: {((Scene)(ref scene)).name} loaded, processing {_pendingSync.Count} queued player(s).");
foreach (Player item in _pendingSync)
{
SharedUpgrades__.LogVerbose("Starting deferred sync for " + item.NickName + ".");
((MonoBehaviour)this).StartCoroutine(WaitAndSync(item));
}
_pendingSync.Clear();
}
[IteratorStateMachine(typeof(<WaitAndSync>d__12))]
private IEnumerator WaitAndSync(Player joiningPlayer)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <WaitAndSync>d__12(0)
{
<>4__this = this,
joiningPlayer = joiningPlayer
};
}
}
public sealed class RegistryService
{
private HashSet<Upgrade> vanillaUpgrades = null;
private HashSet<Upgrade> moddedUpgrades = null;
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));
SharedUpgrades__.Logger.LogInfo((object)$"Discovered {vanillaUpgrades.Count} vanilla and {moddedUpgrades.Count} modded upgrade(s).");
if (result.Vanilla.Count > 0)
{
SharedUpgrades__.LogVerbose("Vanilla: " + string.Join(", ", result.Vanilla));
}
if (result.Modded.Count > 0)
{
SharedUpgrades__.LogVerbose("Modded: " + string.Join(", ", result.Modded));
}
}
public void Clear()
{
SharedUpgrades__.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;
return IsVanilla(key2) || moddedUpgrades.Any((Upgrade upgrade) => upgrade.Name.Equals(key2));
}
private Upgrade MakeUpgradeFromKey(string key)
{
return new Upgrade(key);
}
}
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));
SharedUpgrades__.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();
}
SharedUpgrades__.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__1 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public PlayerAvatar player;
public string steamID;
public Dictionary<string, int> teamSnapshot;
private PhotonView <photonView>5__1;
private string <playerName>5__2;
private int <chance>5__3;
private int <sent>5__4;
private int <skipped>5__5;
private Dictionary<string, int>.Enumerator <>s__6;
private KeyValuePair<string, int> <kvp>5__7;
private int <upgradeLimit>5__8;
private bool <isVanilla>5__9;
private int <playerLevel>5__10;
private Dictionary<string, int> <upgradeDict>5__11;
private int <value>5__12;
private int <difference>5__13;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ApplyTeamSnapshot>d__1(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<photonView>5__1 = null;
<playerName>5__2 = null;
<>s__6 = default(Dictionary<string, int>.Enumerator);
<kvp>5__7 = default(KeyValuePair<string, int>);
<upgradeDict>5__11 = null;
<>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>5__1 = ((Component)PunManager.instance).GetComponent<PhotonView>();
if ((Object)(object)<photonView>5__1 == (Object)null)
{
SharedUpgrades__.Logger.LogWarning((object)"[LateJoin] PhotonView not found on PunManager, skipping sync.");
return false;
}
<playerName>5__2 = (string)_playerName.GetValue(player);
<chance>5__3 = ConfigService.SharedUpgradesChancePercentage();
SharedUpgrades__.LogAlways($"[LateJoin] syncing {<playerName>5__2} — {teamSnapshot.Count} upgrade(s), chance={<chance>5__3}%");
<sent>5__4 = 0;
<skipped>5__5 = 0;
<>s__6 = teamSnapshot.GetEnumerator();
try
{
while (<>s__6.MoveNext())
{
<kvp>5__7 = <>s__6.Current;
<upgradeLimit>5__8 = ConfigService.UpgradeShareLimit(<kvp>5__7.Key);
<isVanilla>5__9 = RegistryService.Instance.IsVanilla(<kvp>5__7.Key);
SharedUpgrades__.LogVerbose($"[LateJoin] {<kvp>5__7.Key} — teamMax={<kvp>5__7.Value}, isVanilla={<isVanilla>5__9}, limit={<upgradeLimit>5__8}");
if (!<isVanilla>5__9 && !ConfigService.IsModdedUpgradesEnabled())
{
SharedUpgrades__.LogVerbose("[LateJoin] " + <kvp>5__7.Key + " — skipped (modded upgrades disabled).");
<skipped>5__5++;
continue;
}
if (!ConfigService.IsUpgradeEnabled(<kvp>5__7.Key))
{
SharedUpgrades__.LogVerbose("[LateJoin] " + <kvp>5__7.Key + " — skipped (disabled in config).");
<skipped>5__5++;
continue;
}
<playerLevel>5__10 = (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(<kvp>5__7.Key, out <upgradeDict>5__11) ? <upgradeDict>5__11.GetValueOrDefault(steamID, 0) : 0);
if (<upgradeLimit>5__8 > 0 && <upgradeLimit>5__8 <= <playerLevel>5__10)
{
SharedUpgrades__.LogInfo($"[LateJoin] {<kvp>5__7.Key} — {<playerName>5__2} hit share limit ({<upgradeLimit>5__8}), skipping.");
<skipped>5__5++;
continue;
}
<value>5__12 = <kvp>5__7.Value;
<difference>5__13 = <value>5__12 - <playerLevel>5__10;
if (<upgradeLimit>5__8 > 0)
{
<difference>5__13 = Math.Min(<difference>5__13, <upgradeLimit>5__8 - <playerLevel>5__10);
}
SharedUpgrades__.LogVerbose($"[LateJoin] {<kvp>5__7.Key} — level={<playerLevel>5__10}, teamMax={<kvp>5__7.Value}, diff={<difference>5__13} (pre-roll)");
<difference>5__13 = SimulateRealisticLevelling(<difference>5__13);
<value>5__12 = <playerLevel>5__10 + <difference>5__13;
if (<difference>5__13 <= 0)
{
SharedUpgrades__.LogInfo("[LateJoin] " + <kvp>5__7.Key + " — rolled 0 after chance simulation, skipping.");
<skipped>5__5++;
continue;
}
if (<isVanilla>5__9)
{
SharedUpgrades__.LogVerbose("[LateJoin] " + <kvp>5__7.Key + " — sending TesterUpgradeCommandRPC to " + <playerName>5__2);
<photonView>5__1.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3]
{
steamID,
new Upgrade(<kvp>5__7.Key).CleanName,
<difference>5__13
});
}
else
{
SharedUpgrades__.LogVerbose("[LateJoin] " + <kvp>5__7.Key + " — sending UpdateStatRPC to " + <playerName>5__2);
<photonView>5__1.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { <kvp>5__7.Key, steamID, <value>5__12 });
}
SharedUpgrades__.LogVerbose($"[LateJoin] sent {<kvp>5__7.Key} (+{<difference>5__13}) to {<playerName>5__2}.");
<sent>5__4++;
<upgradeDict>5__11 = null;
<kvp>5__7 = default(KeyValuePair<string, int>);
}
}
finally
{
((IDisposable)<>s__6).Dispose();
}
<>s__6 = default(Dictionary<string, int>.Enumerator);
SharedUpgrades__.LogAlways($"[LateJoin] done — {<playerName>5__2}: sent={<sent>5__4}, skipped={<skipped>5__5}");
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 static readonly FieldInfo _playerName = AccessTools.Field(typeof(PlayerAvatar), "playerName");
[IteratorStateMachine(typeof(<ApplyTeamSnapshot>d__1))]
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__1(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++;
}
}
SharedUpgrades__.LogVerbose($"[LateJoin] roll simulation — input={value}, chance={num}%, result={num2}");
return num2;
}
}
internal class WatermarkService : MonoBehaviour
{
[CompilerGenerated]
private sealed class <Poll>d__11 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public WatermarkService <>4__this;
private float <elapsed>5__1;
private PlayerAvatar <localPlayer>5__2;
private bool <isHost>5__3;
private bool <modPresent>5__4;
private object <val>5__5;
private List<PlayerAvatar>.Enumerator <>s__6;
private PlayerAvatar <p>5__7;
object? IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object? IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <Poll>d__11(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<localPlayer>5__2 = null;
<val>5__5 = null;
<>s__6 = default(List<PlayerAvatar>.Enumerator);
<p>5__7 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Expected O, but got Unknown
int num = <>1__state;
if (num != 0)
{
if (num != 1)
{
return false;
}
<>1__state = -1;
<elapsed>5__1 += 1f;
try
{
if (PhotonNetwork.InRoom)
{
if (string.IsNullOrEmpty(OwnerID))
{
goto IL_0233;
}
<localPlayer>5__2 = null;
<>s__6 = SemiFunc.PlayerGetAll().GetEnumerator();
try
{
while (<>s__6.MoveNext())
{
<p>5__7 = <>s__6.Current;
if ((Object)(object)<p>5__7?.photonView != (Object)null && <p>5__7.photonView.IsMine)
{
<localPlayer>5__2 = <p>5__7;
break;
}
<p>5__7 = null;
}
}
finally
{
((IDisposable)<>s__6).Dispose();
}
<>s__6 = default(List<PlayerAvatar>.Enumerator);
if (!((Object)(object)<localPlayer>5__2 == (Object)null))
{
if ((string)_steamID.GetValue(<localPlayer>5__2) != OwnerID)
{
goto IL_0233;
}
<isHost>5__3 = PhotonNetwork.IsMasterClient;
<modPresent>5__4 = ((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).ContainsKey((object)"su__v1");
if (<isHost>5__3 | <modPresent>5__4)
{
if (((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"su__v1", out <val>5__5))
{
Version = (<val>5__5 as string) ?? "UNKNOWN";
}
<>4__this.show = !<isHost>5__3 & <modPresent>5__4;
goto IL_0233;
}
<localPlayer>5__2 = null;
<val>5__5 = null;
}
}
}
catch
{
}
}
else
{
<>1__state = -1;
<>4__this.polling = true;
<elapsed>5__1 = 0f;
}
if (<elapsed>5__1 < 10f)
{
<>2__current = (object)new WaitForSeconds(1f);
<>1__state = 1;
return true;
}
goto IL_0233;
IL_0233:
<>4__this.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 static readonly FieldInfo _steamID = AccessTools.Field(typeof(PlayerAvatar), "steamID");
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, "SharedUpgrades++.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__11))]
private IEnumerator Poll()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <Poll>d__11(0)
{
<>4__this = this
};
}
private void OnGUI()
{
//IL_0078: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: 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_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0060: 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 SharedUpgrades__.Patches
{
[HarmonyPatch(typeof(PunManager), "UpdateStatRPC")]
internal class ModdedUpgradesPatch
{
private static readonly FieldInfo _playerName = AccessTools.Field(typeof(PlayerAvatar), "playerName");
[HarmonyPostfix]
public static void Postfix(string dictionaryName, string key, int value)
{
//IL_0153: 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);
SharedUpgrades__.LogVerbose(string.Format("[ModdedPatch] {0} ({1}) — value={2}, player={3}, distributing={4}", dictionaryName, key, value, val?.playerName ?? "not found", DistributionService.IsDistributing));
if ((Object)(object)val != (Object)null && ConfigService.IsShareNotificationEnabled())
{
SharedUpgrades__.LogVerbose("[ModdedPatch] running effects for " + val.playerName);
if (val.isLocal)
{
SharedUpgrades__.LogVerbose("[ModdedPatch] local player, triggering StatsUI + CameraGlitch.");
StatsUI.instance.Fetch();
StatsUI.instance.ShowStats();
CameraGlitch.Instance.PlayUpgrade();
}
else
{
SharedUpgrades__.LogVerbose("[ModdedPatch] remote player, shaking camera.");
GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)val).transform.position, 0.2f);
}
if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient)
{
SharedUpgrades__.LogVerbose("[ModdedPatch] applying upgrade material effect to " + val.playerName + ".");
val.playerHealth.MaterialEffectOverride((Effect)0);
}
}
if (SemiFunc.IsMasterClientOrSingleplayer())
{
if (DistributionService.IsDistributing)
{
SharedUpgrades__.LogVerbose("[ModdedPatch] already distributing, skipping " + dictionaryName + ".");
}
else if ((Object)(object)val == (Object)null || (Object)(object)val.photonView == (Object)null)
{
SharedUpgrades__.Logger.LogWarning((object)("[ModdedPatch] no PlayerAvatar found for " + key + ", can't distribute " + dictionaryName + "."));
}
else
{
string text = (string)_playerName.GetValue(val);
SharedUpgrades__.LogAlways("[ModdedPatch] " + text + " bought " + dictionaryName + ", distributing...");
UpgradeContext context = new UpgradeContext(key, val.photonView.ViewID, text, new Dictionary<string, int>());
DistributionService.DistributeUpgrade(context, dictionaryName, 1, value);
}
}
}
}
[HarmonyPatch(typeof(PunManager), "TesterUpgradeCommandRPC")]
internal class PlayerUpgradeEffectPatch
{
[HarmonyPostfix]
public static void Postfix(string _steamID, string upgradeName, int upgradeNum, PhotonMessageInfo _info)
{
//IL_010e: 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)
{
SharedUpgrades__.Logger.LogError((object)("[Effects] TesterUpgradeCommandRPC fired for " + _steamID + " but no PlayerAvatar found — skipping effects."));
return;
}
SharedUpgrades__.LogVerbose($"[Effects] {val.playerName} got {upgradeName} x{upgradeNum} (local={val.isLocal})");
if (ConfigService.IsShareNotificationEnabled())
{
if (val.isLocal)
{
SharedUpgrades__.LogVerbose("[Effects] " + val.playerName + " is local — StatsUI + CameraGlitch.");
StatsUI.instance.Fetch();
StatsUI.instance.ShowStats();
CameraGlitch.Instance.PlayUpgrade();
}
else
{
SharedUpgrades__.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)
{
SharedUpgrades__.LogVerbose("[Effects] applying upgrade material effect to " + val.playerName + ".");
val.playerHealth.MaterialEffectOverride((Effect)0);
}
}
SharedUpgrades__.LogVerbose("[Effects] " + val.playerName + " effects done.");
if (flag && SemiFunc.IsMasterClientOrSingleplayer() && ConfigService.IsSharedUpgradeHealEnabled())
{
int num = val.playerHealth.maxHealth + 20 * upgradeNum - val.playerHealth.health;
SharedUpgrades__.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;
}
SharedUpgrades__.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);
SharedUpgrades__.LogInfo("Synced PlayerDictionary for " + text + ".");
num++;
}
}
}
SharedUpgrades__.LogVerbose($"REPOLib sync done — {num}/{dictionary.Count} upgrade(s).");
}
}
[HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")]
internal class SharedUpgradesPatch
{
private static readonly FieldInfo _itemToggle = AccessTools.Field(typeof(ItemUpgrade), "itemToggle");
private static readonly FieldInfo _playerTogglePhotonId = AccessTools.Field(typeof(ItemToggle), "playerTogglePhotonID");
private static readonly FieldInfo _steamID = AccessTools.Field(typeof(PlayerAvatar), "steamID");
private static readonly FieldInfo _playerName = AccessTools.Field(typeof(PlayerAvatar), "playerName");
private static readonly FieldInfo _itemAttributes = AccessTools.Field(typeof(ItemUpgrade), "itemAttributes");
private static PlayerAvatar? GetUpgradePlayer(ItemUpgrade instance, out int viewID)
{
viewID = 0;
object? value = _itemToggle.GetValue(instance);
ItemToggle val = (ItemToggle)((value is ItemToggle) ? value : null);
if (val == null || !val.toggleState)
{
return null;
}
viewID = (int)_playerTogglePhotonId.GetValue(val);
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)
{
SharedUpgrades__.LogVerbose("[Purchase] upgrade interaction fired but couldn't find a player, skipping.");
return;
}
string text = (string)_steamID.GetValue(upgradePlayer);
if (!string.IsNullOrEmpty(text))
{
string text2 = null;
object? value = _itemAttributes.GetValue(__instance);
ItemAttributes val = (ItemAttributes)((value is ItemAttributes) ? value : null);
if (val != null && (Object)(object)val.item != (Object)null)
{
text2 = ((Object)val.item).name;
}
SharedUpgrades__.LogVerbose("[Purchase] " + upgradePlayer.playerName + " is buying '" + text2 + "'");
__state = new UpgradeContext(text, playerName: (string)_playerName.GetValue(upgradePlayer), viewID: viewID, levelsBefore: SnapshotService.SnapshotPlayerStats(text), itemName: text2);
}
}
[HarmonyPostfix]
public static void Postfix(UpgradeContext? __state)
{
if (!SemiFunc.IsMasterClientOrSingleplayer() || __state == null)
{
return;
}
SharedUpgrades__.LogVerbose("[Purchase] checking what " + __state.PlayerName + " just bought (item='" + __state.ItemName + "')");
bool flag = false;
IEnumerable<KeyValuePair<string, Dictionary<string, int>>> enumerable = StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> key) => RegistryService.Instance.IsRegistered(key.Key));
foreach (KeyValuePair<string, Dictionary<string, int>> item in enumerable)
{
item.Value.TryGetValue(__state.SteamID, out var value);
__state.LevelsBefore.TryGetValue(item.Key, out var value2);
SharedUpgrades__.LogVerbose($"[Purchase] {item.Key}: {value2} → {value}");
if (value > value2)
{
int num = value - value2;
flag = true;
SharedUpgrades__.LogAlways($"[Purchase] {__state.PlayerName} bought {item.Key} (+{num}), distributing...");
DistributionService.DistributeUpgrade(__state, item.Key, num, value);
}
}
SharedUpgrades__.LogVerbose($"[Purchase] vanilla scan done, distributed={flag}");
if (!flag && __state.ItemName != null && ConfigService.IsModdedUpgradesEnabled())
{
SharedUpgrades__.LogVerbose("[Purchase] no vanilla upgrades changed — checking modded match for '" + __state.ItemName + "'");
string text = MatchItemNameToModdedUpgrade(__state.ItemName);
if (text != null)
{
__state.LevelsBefore.TryGetValue(text, out var value3);
int currentValue = value3 + 1;
SharedUpgrades__.LogInfo("[Purchase] " + __state.PlayerName + " (" + __state.SteamID + ") bought modded " + text + " (+1), distributing...");
DistributionService.DistributeUpgrade(__state, text, 1, currentValue);
}
else
{
SharedUpgrades__.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_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_007b: Expected O, but got Unknown
SharedUpgrades__.LogVerbose("StatsManager.Start — discovering upgrades.");
DiscoveredUpgradesResult discoveredUpgradesResult = DiscoveryService.DiscoveredUpgrades(__instance);
SharedUpgrades__.LogVerbose($"Found {discoveredUpgradesResult.Vanilla.Count} vanilla and {discoveredUpgradesResult.Modded.Count} modded upgrade(s).");
RegistryService.Instance.Clear();
RegistryService.Instance.RegisterAll(discoveredUpgradesResult);
ConfigService.LoadModsIntoConfig();
if ((Object)(object)_callbackService == (Object)null)
{
GameObject val = new GameObject("NetworkCallbackService");
_callbackService = val.AddComponent<NetworkCallbackService>();
val.AddComponent<WatermarkService>();
Object.DontDestroyOnLoad((Object)(object)val);
SharedUpgrades__.LogVerbose("Created NetworkCallbackService and WatermarkService.");
}
else
{
SharedUpgrades__.LogVerbose("NetworkCallbackService already exists, skipping.");
}
}
}
}
namespace SharedUpgrades__.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 => Name.StartsWith("playerUpgrade") ? Name.Substring("playerUpgrade".Length) : Name;
public Upgrade(string Name)
{
this.Name = Name;
base..ctor();
}
public bool Equals(Upgrade? other)
{
return other != null && other.Name == Name;
}
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 SharedUpgrades__.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_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: 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.");
}
}
}