using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BetterTeamUpgrades.Config;
using BetterTeamUpgrades.Patches;
using HarmonyLib;
using Photon.Pun;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("TeamUpgrades")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TeamUpgrades")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("ef74d5e5-8fe6-4b6a-86ed-0e29e12695bb")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace BetterTeamUpgrades
{
[BepInPlugin("MrBytesized.REPO.BetterTeamUpgrades", "Better Team Upgrades", "2.0.0")]
public class Plugin : BaseUnityPlugin
{
private const string mod_guid = "MrBytesized.REPO.BetterTeamUpgrades";
private const string mod_name = "Better Team Upgrades";
private const string mod_version = "2.0.0";
private readonly Harmony harmony = new Harmony("MrBytesized.REPO.BetterTeamUpgrades");
private static Plugin instance;
internal static ManualLogSource Log;
private (ConfigEntry<bool> configEntry, Action enablePatch, Action disablePatch, string description)[] _patchArray;
private void Awake()
{
if ((Object)(object)instance == (Object)null)
{
instance = this;
}
Log = Logger.CreateLogSource("MrBytesized.REPO.BetterTeamUpgrades");
harmony.PatchAll(typeof(StatsManagerInitPatch));
Configuration.Init(((BaseUnityPlugin)this).Config);
_patchArray = new(ConfigEntry<bool>, Action, Action, string)[2]
{
(Configuration.EnableSharedUpgradesPatch, delegate
{
harmony.PatchAll(typeof(SharedUpgradesPatch));
}, delegate
{
harmony.UnpatchSelf(typeof(SharedUpgradesPatch));
}, "Shared Upgrades"),
(Configuration.EnableLateJoinPlayerUpdateSyncPatch, delegate
{
harmony.PatchAll(typeof(LateJoinPlayerUpgradeSyncPatch));
}, delegate
{
harmony.UnpatchSelf(typeof(LateJoinPlayerUpgradeSyncPatch));
}, "Late Join Player Upgrade Sync")
};
(ConfigEntry<bool>, Action, Action, string)[] patchArray = _patchArray;
for (int i = 0; i < patchArray.Length; i++)
{
var (configEntry, enablePatch, disablePatch, description) = patchArray[i];
UpdatePatchFromConfig(configEntry, enablePatch, disablePatch, description);
configEntry.SettingChanged += delegate
{
UpdatePatchFromConfig(configEntry, enablePatch, disablePatch, description);
};
}
Log.LogInfo((object)"Better Team Upgrades mod has been activated");
}
private void UpdatePatchFromConfig(ConfigEntry<bool> configEntry, Action enablePatch, Action disablePatch, string description)
{
if (configEntry.Value)
{
try
{
enablePatch();
Log.LogInfo((object)(description + " patch enabled."));
return;
}
catch (Exception ex)
{
Log.LogError((object)("Failed to enable " + description + ": " + ex.Message));
return;
}
}
try
{
disablePatch();
Log.LogInfo((object)(description + " patch disabled."));
}
catch (Exception ex2)
{
Log.LogError((object)("Failed to disable " + description + ": " + ex2.Message));
}
}
}
public static class HarmonyExtensions
{
public static void UnpatchSelf(this Harmony harmony, Type patchClass)
{
HarmonyPatch[] array = patchClass.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).OfType<HarmonyPatch>().ToArray();
for (int i = 0; i < array.Length; i++)
{
HarmonyMethod info = ((HarmonyAttribute)array[i]).info;
if (info == null)
{
Plugin.Log.LogWarning((object)("Invalid HarmonyPatch method info on class: " + patchClass.FullName));
continue;
}
MethodInfo methodInfo = ResolveOriginal(info);
if (methodInfo == null)
{
Plugin.Log.LogWarning((object)("Original method not found for class patch: " + FormatInfo(info)));
}
else
{
harmony.Unpatch((MethodBase)methodInfo, (HarmonyPatchType)0, harmony.Id);
}
}
MethodInfo[] methods = patchClass.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo methodInfo2 in methods)
{
HarmonyPatch[] array2 = methodInfo2.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).OfType<HarmonyPatch>().ToArray();
if (array2.Length == 0)
{
continue;
}
array = array2;
for (int j = 0; j < array.Length; j++)
{
HarmonyMethod info2 = ((HarmonyAttribute)array[j]).info;
if (info2 == null)
{
Plugin.Log.LogWarning((object)("Invalid HarmonyPatch info on method: " + methodInfo2.DeclaringType.FullName + "." + methodInfo2.Name));
continue;
}
MethodInfo methodInfo3 = ResolveOriginal(info2);
if (methodInfo3 == null)
{
Plugin.Log.LogWarning((object)("Original method not found for method-level patch: " + FormatInfo(info2)));
}
else
{
harmony.Unpatch((MethodBase)methodInfo3, (HarmonyPatchType)0, harmony.Id);
}
}
}
}
private static MethodInfo ResolveOriginal(HarmonyMethod info)
{
if (info.method != null)
{
return info.method;
}
if (info.declaringType == null || string.IsNullOrEmpty(info.methodName))
{
return null;
}
return AccessTools.Method(info.declaringType, info.methodName, info.argumentTypes, (Type[])null);
}
private static string FormatInfo(HarmonyMethod info)
{
string obj = ((info.declaringType != null) ? info.declaringType.FullName : "<null>");
string text = ((!string.IsNullOrEmpty(info.methodName)) ? info.methodName : "<null>");
return obj + "." + text;
}
}
}
namespace BetterTeamUpgrades.Patches
{
[HarmonyPatch(typeof(PlayerAvatar), "Start")]
public class LateJoinPlayerUpgradeSyncPatch
{
[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
public string id;
internal bool <SyncWithDelay>b__2(PlayerAvatar p)
{
return SemiFunc.PlayerGetSteamID(p) == id;
}
}
[CompilerGenerated]
private sealed class <SyncWithDelay>d__1 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public PlayerAvatar newPlayer;
private float <timeWaited>5__2;
private float <timeout>5__3;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <SyncWithDelay>d__1(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Expected O, but got Unknown
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<timeWaited>5__2 = 0f;
<timeout>5__3 = 10f;
goto IL_006c;
case 1:
<>1__state = -1;
<timeWaited>5__2 += 0.5f;
goto IL_006c;
case 2:
{
<>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)
{
Plugin.Log.LogWarning((object)"Late Join: PunManager PhotonView not found.");
return false;
}
string text = SemiFunc.PlayerGetSteamID(newPlayer);
if (string.IsNullOrEmpty(text))
{
Plugin.Log.LogWarning((object)$"Late Join: Timed out waiting for SteamID for player {newPlayer.photonView.ViewID}. Skipping sync.");
return false;
}
string text2 = (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(newPlayer);
Plugin.Log.LogInfo((object)("Late Join: Player " + text2 + " (" + text + ") is ready. Starting sync..."));
List<PlayerAvatar> source = SemiFunc.PlayerGetAll();
List<string> list = (from p in source
select SemiFunc.PlayerGetSteamID(p) into id
where !string.IsNullOrEmpty(id)
select id).ToList();
foreach (KeyValuePair<string, Dictionary<string, int>> dictionaryOfDictionary in StatsManager.instance.dictionaryOfDictionaries)
{
if (!dictionaryOfDictionary.Key.StartsWith("playerUpgrade"))
{
continue;
}
string key = dictionaryOfDictionary.Key;
Dictionary<string, int> value = dictionaryOfDictionary.Value;
int num = 0;
foreach (string item in list)
{
if (value.TryGetValue(item, out var value2) && value2 > num)
{
num = value2;
}
}
if (num <= 0)
{
continue;
}
bool flag = SharedUpgradesPatch.VanillaKeys.Contains(key);
using List<string>.Enumerator enumerator2 = list.GetEnumerator();
while (enumerator2.MoveNext())
{
<>c__DisplayClass1_0 CS$<>8__locals0 = new <>c__DisplayClass1_0
{
id = enumerator2.Current
};
int num2 = (value.ContainsKey(CS$<>8__locals0.id) ? value[CS$<>8__locals0.id] : 0);
int num3 = num - num2;
if (num3 <= 0)
{
continue;
}
if (flag)
{
string text3 = key.Substring("playerUpgrade".Length);
component.RPC("TesterUpgradeCommandRPC", (RpcTarget)1, new object[3] { CS$<>8__locals0.id, text3, num3 });
if (value.ContainsKey(CS$<>8__locals0.id))
{
value[CS$<>8__locals0.id] += num3;
}
else
{
value[CS$<>8__locals0.id] = num3;
}
}
else
{
component.RPC("UpdateStatRPC", (RpcTarget)1, new object[3] { key, CS$<>8__locals0.id, num });
value[CS$<>8__locals0.id] = num;
}
string arg = "Unknown";
PlayerAvatar val = ((IEnumerable<PlayerAvatar>)source).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar p) => SemiFunc.PlayerGetSteamID(p) == CS$<>8__locals0.id));
if ((Object)(object)val != (Object)null)
{
arg = (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(val);
}
Plugin.Log.LogInfo((object)$"Late Join: Synced {key} for {arg} (+{num3})");
}
}
Plugin.Log.LogInfo((object)("Late Join: Sync complete for " + text2 + "."));
return false;
}
IL_006c:
if (string.IsNullOrEmpty(SemiFunc.PlayerGetSteamID(newPlayer)) && <timeWaited>5__2 < <timeout>5__3)
{
<>2__current = (object)new WaitForSeconds(0.5f);
<>1__state = 1;
return true;
}
<>2__current = (object)new WaitForSeconds(1f);
<>1__state = 2;
return true;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[HarmonyPostfix]
private static void Postfix(PlayerAvatar __instance)
{
if (SemiFunc.IsMasterClientOrSingleplayer())
{
RunManager instance = RunManager.instance;
if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelMainMenu) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelRecording) && !((Object)(object)instance.levelCurrent == (Object)(object)instance.levelSplashScreen))
{
((MonoBehaviour)__instance).StartCoroutine(SyncWithDelay(__instance));
}
}
}
[IteratorStateMachine(typeof(<SyncWithDelay>d__1))]
private static IEnumerator SyncWithDelay(PlayerAvatar newPlayer)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <SyncWithDelay>d__1(0)
{
newPlayer = newPlayer
};
}
}
[HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")]
public class SharedUpgradesPatch
{
public static HashSet<string> VanillaKeys = new HashSet<string>();
private static Dictionary<string, int> _preUpgradeStats = new Dictionary<string, int>();
private static string _targetSteamID;
private static int _targetViewID;
[HarmonyPrefix]
public static void Prefix(ItemUpgrade __instance)
{
if (!SemiFunc.IsMasterClientOrSingleplayer())
{
return;
}
object? value = AccessTools.Field(typeof(ItemUpgrade), "itemToggle").GetValue(__instance);
ItemToggle val = (ItemToggle)((value is ItemToggle) ? value : null);
if ((Object)(object)val == (Object)null || !val.toggleState)
{
return;
}
_targetViewID = (int)AccessTools.Field(typeof(ItemToggle), "playerTogglePhotonID").GetValue(val);
PlayerAvatar val2 = SemiFunc.PlayerAvatarGetFromPhotonID(_targetViewID);
if ((Object)(object)val2 == (Object)null)
{
return;
}
_targetSteamID = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(val2);
_preUpgradeStats.Clear();
if (!((Object)(object)StatsManager.instance != (Object)null))
{
return;
}
foreach (KeyValuePair<string, Dictionary<string, int>> dictionaryOfDictionary in StatsManager.instance.dictionaryOfDictionaries)
{
if (dictionaryOfDictionary.Key.StartsWith("playerUpgrade"))
{
if (dictionaryOfDictionary.Value.TryGetValue(_targetSteamID, out var value2))
{
_preUpgradeStats[dictionaryOfDictionary.Key] = value2;
}
else
{
_preUpgradeStats[dictionaryOfDictionary.Key] = 0;
}
}
}
}
[HarmonyPostfix]
public static void Postfix(ItemUpgrade __instance)
{
if (!SemiFunc.IsMasterClientOrSingleplayer() || string.IsNullOrEmpty(_targetSteamID) || (Object)(object)PunManager.instance == (Object)null)
{
return;
}
PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
if ((Object)(object)component == (Object)null)
{
Plugin.Log.LogError((object)"SharedUpgrades: PunManager PhotonView not found!");
return;
}
foreach (KeyValuePair<string, Dictionary<string, int>> dictionaryOfDictionary in StatsManager.instance.dictionaryOfDictionaries)
{
if (!dictionaryOfDictionary.Key.StartsWith("playerUpgrade"))
{
continue;
}
int num = (dictionaryOfDictionary.Value.ContainsKey(_targetSteamID) ? dictionaryOfDictionary.Value[_targetSteamID] : 0);
int num2 = (_preUpgradeStats.ContainsKey(dictionaryOfDictionary.Key) ? _preUpgradeStats[dictionaryOfDictionary.Key] : 0);
if (num > num2)
{
int num3 = num - num2;
string key = dictionaryOfDictionary.Key;
Plugin.Log.LogInfo((object)$"Detected upgrade: {key} (+{num3}) for {_targetSteamID}");
if (VanillaKeys.Contains(key))
{
string command = key.Substring("playerUpgrade".Length);
DistributeVanillaUpgrade(component, command, num3);
}
else if (!Configuration.EnableCustomUpgradeSyncing.Value)
{
Plugin.Log.LogInfo((object)("Custom Upgrade Syncing is disabled. Skipping: " + key));
}
else
{
DistributeCustomUpgrade(component, key, num);
}
}
}
_targetSteamID = null;
_targetViewID = -1;
_preUpgradeStats.Clear();
}
private static void DistributeVanillaUpgrade(PhotonView punView, string command, int amount)
{
foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
{
if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null)
{
continue;
}
if (item.photonView.ViewID == _targetViewID)
{
Plugin.Log.LogInfo((object)("Skipping original upgrader: " + command + " for " + _targetSteamID));
continue;
}
string text = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(item);
if (!string.IsNullOrEmpty(text))
{
punView.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { text, command, amount });
Plugin.Log.LogInfo((object)("Synced Vanilla: " + command + " for " + text));
}
}
}
private static void DistributeCustomUpgrade(PhotonView punView, string dictionaryKey, int totalValue)
{
foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
{
if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null)
{
continue;
}
if (item.photonView.ViewID == _targetViewID)
{
Plugin.Log.LogInfo((object)("Skipping original upgrader: " + dictionaryKey + " for " + _targetSteamID));
continue;
}
string text = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(item);
if (!string.IsNullOrEmpty(text))
{
punView.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { dictionaryKey, text, totalValue });
Plugin.Log.LogInfo((object)("Synced Custom: " + dictionaryKey + " for " + text));
}
}
}
}
[HarmonyPatch(typeof(StatsManager), "Start")]
public class StatsManagerInitPatch
{
[HarmonyPostfix]
public static void Postfix(StatsManager __instance)
{
SharedUpgradesPatch.VanillaKeys.Clear();
HashSet<string> hashSet = (from f in typeof(StatsManager).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
select f.Name).ToHashSet();
foreach (string key in __instance.dictionaryOfDictionaries.Keys)
{
if (key.StartsWith("playerUpgrade") && hashSet.Contains(key))
{
SharedUpgradesPatch.VanillaKeys.Add(key);
}
}
Plugin.Log.LogInfo((object)$"Auto-discovered {SharedUpgradesPatch.VanillaKeys.Count} vanilla upgrade keys.");
}
}
}
namespace BetterTeamUpgrades.Config
{
internal class Configuration
{
public static ConfigEntry<bool> EnableSharedUpgradesPatch;
public static ConfigEntry<bool> EnableLateJoinPlayerUpdateSyncPatch;
public static ConfigEntry<bool> EnableCustomUpgradeSyncing;
public static void Init(ConfigFile config)
{
EnableSharedUpgradesPatch = config.Bind<bool>("Upgrade Sync Settings", "EnableSharedUpgrades", true, "Enables Shared Upgrades for all supported Upgrades");
EnableLateJoinPlayerUpdateSyncPatch = config.Bind<bool>("Late Join Settings", "EnableLateJoinPlayerUpgradeSync", false, "Enables Upgrade Sync for Late Joining Players");
EnableCustomUpgradeSyncing = config.Bind<bool>("Extra Sync Settings", "EnableCustomUpgradeSyncing", true, "Enables Custom Upgrade Syncing for Modded Upgrades (may cause issues with some mods)");
}
}
}