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 Rickys Shared Upgrades v1.1.7
BepInEx/plugins/RickysSharedUpgrades/RickysSharedUpgrades.dll
Decompiled 2 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("RickysSharedUpgrades")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.7.0")] [assembly: AssemblyInformationalVersion("1.1.7")] [assembly: AssemblyProduct("RickysSharedUpgrades")] [assembly: AssemblyTitle("RickysSharedUpgrades")] [assembly: AssemblyVersion("1.1.7.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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] [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 RickysSharedUpgrades { [BepInPlugin("com.rickyssharedupgrades.repo", "Ricky's Shared Upgrades", "1.1.7")] [BepInProcess("REPO.exe")] public sealed class Plugin : BaseUnityPlugin { private sealed class JoiningPlayer { internal readonly int Actor; internal PlayerAvatar Avatar; internal bool BaselineSent; internal bool Ready; internal float ReadyAfter; internal JoiningPlayer(int actor, PlayerAvatar avatar) { Actor = actor; Avatar = avatar; } } internal sealed class Consumption { internal readonly Consumption? Parent; internal readonly HashSet<string> Recipients; internal readonly List<Gain> Gains = new List<Gain>(); internal Consumption(Consumption? parent, HashSet<string> recipients) { Parent = parent; Recipients = recipients; } } internal sealed class Gain { internal readonly UpgradeKind Kind; internal readonly string Source; internal readonly int Amount; internal readonly int Before; internal Gain(UpgradeKind kind, string source, int before, int amount) { Kind = kind; Source = source; Before = before; Amount = amount; } } private sealed class Delivery { internal readonly UpgradeKind Kind; internal readonly string SteamId; internal Delivery(UpgradeKind kind, string id) { Kind = kind; SteamId = id; } } internal readonly struct Capture { internal readonly Consumption? Consumption; internal readonly UpgradeKind? Kind; internal readonly string SteamId; internal readonly int Before; internal Capture(Consumption scope, UpgradeKind kind, string id, int before) { Consumption = scope; Kind = kind; SteamId = id; Before = before; } } public const string PluginGuid = "com.rickyssharedupgrades.repo"; public const string PluginName = "Ricky's Shared Upgrades"; public const string PluginVersion = "1.1.7"; internal static Plugin? Instance; private Harmony? harmony; private ConfigEntry<bool> sharingEnabled; private ConfigEntry<bool> logShares; private FieldInfo? consumedField; private FieldInfo? tumbleField; private FieldInfo? healthReadyField; private Consumption? currentConsumption; private readonly Queue<Delivery> pending = new Queue<Delivery>(); private readonly HashSet<(UpgradeKind, string)> scheduled = new HashSet<(UpgradeKind, string)>(); private readonly UpgradeTargets targets = new UpgradeTargets(); private readonly Dictionary<string, JoiningPlayer> players = new Dictionary<string, JoiningPlayer>(StringComparer.Ordinal); private Room? session; private int dispatchDepth; private float nextDispatch; private float nextErrorLog; private float nextPlayerScan; private bool HasAuthority { get { if (sharingEnabled.Value && Object.op_Implicit((Object)(object)GameManager.instance) && GameManager.Multiplayer()) { return PhotonNetwork.IsMasterClient; } return false; } } private void Awake() { //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Expected O, but got Unknown Instance = this; sharingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Share player upgrades and catch up late joiners when hosting."); logShares = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "LogSharedUpgrades", false, "Log shared upgrade deliveries."); try { consumedField = AccessTools.Field(typeof(ItemUpgrade), "upgradeDone"); tumbleField = AccessTools.Field(typeof(PlayerAvatar), "tumble"); healthReadyField = AccessTools.Field(typeof(PlayerHealth), "healthSet"); if (consumedField?.FieldType != typeof(bool) || tumbleField?.FieldType != typeof(PlayerTumble) || healthReadyField?.FieldType != typeof(bool)) { throw new MissingFieldException("The game changed its upgrade components."); } MethodInfo method = typeof(PunManager).GetMethod("TesterUpgradeCommandRPC"); if (method == null || !Attribute.IsDefined(method, typeof(PunRPC))) { throw new MissingMethodException("The game's native upgrade RPC is unavailable."); } UpgradeKind[] all = UpgradeKind.All; for (int i = 0; i < all.Length; i++) { all[i].GetMethod(); } harmony = new Harmony("com.rickyssharedupgrades.repo"); harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded for R.E.P.O. {2}. Host only; {3} upgrade types.", "Ricky's Shared Upgrades", "1.1.7", Application.version, UpgradeKind.All.Length)); } catch (Exception arg) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } ((BaseUnityPlugin)this).Logger.LogError((object)$"Upgrade sharing disabled: {arg}"); sharingEnabled.Value = false; } } private void EnsureSession() { if (session != PhotonNetwork.CurrentRoom) { session = PhotonNetwork.CurrentRoom; ResetProgress(); } } internal void ResetProgress() { pending.Clear(); scheduled.Clear(); targets.Clear(); players.Clear(); nextPlayerScan = 0f; nextDispatch = 0f; } private bool AvatarReady(PlayerAvatar avatar) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown if (Object.op_Implicit((Object)(object)LevelGenerator.Instance) && LevelGenerator.Instance.Generated && Object.op_Implicit((Object)(object)StatsManager.instance) && StatsManager.instance.statsSynced && Object.op_Implicit((Object)(object)avatar.playerHealth) && Object.op_Implicit((Object)(object)avatar.physGrabber) && Object.op_Implicit((Object)(PlayerTumble)tumbleField.GetValue(avatar))) { return (bool)healthReadyField.GetValue(avatar.playerHealth); } return false; } internal void ScanPlayers() { if (!HasAuthority || !Object.op_Implicit((Object)(object)GameDirector.instance) || !Object.op_Implicit((Object)(object)StatsManager.instance) || PhotonNetwork.CurrentRoom == null) { return; } EnsureSession(); Room currentRoom = PhotonNetwork.CurrentRoom; List<string> list = new List<string>(); foreach (KeyValuePair<string, JoiningPlayer> player in players) { if (!currentRoom.Players.ContainsKey(player.Value.Actor)) { list.Add(player.Key); } } foreach (string item in list) { players.Remove(item); } List<JoiningPlayer> list2 = new List<JoiningPlayer>(); foreach (PlayerAvatar item2 in SemiFunc.PlayerGetList()) { if (!Object.op_Implicit((Object)(object)item2) || !Object.op_Implicit((Object)(object)item2.photonView)) { continue; } string text = SemiFunc.PlayerGetSteamID(item2); int ownerActorNr = item2.photonView.OwnerActorNr; if (string.IsNullOrEmpty(text) || ownerActorNr <= 0 || !currentRoom.Players.ContainsKey(ownerActorNr)) { continue; } if (!players.TryGetValue(text, out JoiningPlayer value) || value.Actor != ownerActorNr) { value = (players[text] = new JoiningPlayer(ownerActorNr, item2)); UpgradeKind[] all = UpgradeKind.All; foreach (UpgradeKind upgradeKind in all) { int level = upgradeKind.GetLevel(StatsManager.instance, text); int crewLevel = targets.CrewLevel(upgradeKind.RpcName, upgradeKind.GetCrewLevel(StatsManager.instance)); targets.CatchUp(text, upgradeKind.RpcName, level, crewLevel); Schedule(upgradeKind, text); } } if ((Object)(object)value.Avatar != (Object)(object)item2) { value.Avatar = item2; value.Ready = (value.BaselineSent = false); } if (!AvatarReady(item2)) { value.Ready = (value.BaselineSent = false); } else if (!value.BaselineSent) { if (!list2.Contains(value)) { list2.Add(value); } } else if (Time.unscaledTime >= value.ReadyAfter) { value.Ready = true; } } if (list2.Count == 0 || !Object.op_Implicit((Object)(object)PunManager.instance)) { return; } PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>(); if (!Object.op_Implicit((Object)(object)component) || component.ViewID == 0) { return; } try { PunManager.instance.SyncAllDictionaries(); foreach (JoiningPlayer item3 in list2) { item3.BaselineSent = true; item3.ReadyAfter = Time.unscaledTime + 2f; } } catch (Exception exception) { LogDeliveryError("Unable to synchronize joining players.", exception); } } private void Schedule(UpgradeKind kind, string id) { if (targets.Missing(id, kind.RpcName, kind.GetLevel(StatsManager.instance, id)) > 0 && scheduled.Add((kind, id))) { pending.Enqueue(new Delivery(kind, id)); } } internal Consumption? BeginConsumption(ItemUpgrade item) { ItemToggle val = default(ItemToggle); if (!HasAuthority || dispatchDepth != 0 || !Object.op_Implicit((Object)(object)GameDirector.instance) || !Object.op_Implicit((Object)(object)StatsManager.instance) || !Object.op_Implicit((Object)(object)item) || !item.isPlayerUpgrade || (bool)consumedField.GetValue(item) || !((Component)item).TryGetComponent<ItemToggle>(ref val) || !val.toggleState) { return null; } EnsureSession(); ScanPlayers(); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (PlayerAvatar item2 in SemiFunc.PlayerGetList()) { if (Object.op_Implicit((Object)(object)item2)) { string text = SemiFunc.PlayerGetSteamID(item2); if (!string.IsNullOrEmpty(text)) { hashSet.Add(text); } } } foreach (KeyValuePair<string, JoiningPlayer> player in players) { Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && currentRoom.Players.ContainsKey(player.Value.Actor)) { hashSet.Add(player.Key); } } return currentConsumption = new Consumption(currentConsumption, hashSet); } internal void EndConsumption(Consumption? scope) { if (scope == null) { return; } currentConsumption = scope.Parent; if (!HasAuthority) { return; } foreach (Gain gain in scope.Gains) { scope.Recipients.Add(gain.Source); foreach (string recipient in scope.Recipients) { int before = ((recipient == gain.Source) ? gain.Before : gain.Kind.GetLevel(StatsManager.instance, recipient)); targets.Add(recipient, gain.Kind.RpcName, before, gain.Amount); Schedule(gain.Kind, recipient); } } if (scope.Gains.Count != 0) { nextDispatch = Mathf.Max(nextDispatch, Time.unscaledTime + 0.15f); } } internal Capture BeforeUpgrade(MethodBase method, string steamId) { UpgradeKind upgradeKind = UpgradeKind.FromMethod(method.Name); if (!HasAuthority || dispatchDepth != 0 || currentConsumption == null || upgradeKind == null || !Object.op_Implicit((Object)(object)StatsManager.instance) || string.IsNullOrEmpty(steamId)) { return default(Capture); } return new Capture(currentConsumption, upgradeKind, steamId, upgradeKind.GetLevel(StatsManager.instance, steamId)); } internal void AfterUpgrade(Capture capture) { if (capture.Consumption != null && capture.Kind != null && Object.op_Implicit((Object)(object)StatsManager.instance)) { long num = (long)capture.Kind.GetLevel(StatsManager.instance, capture.SteamId) - (long)capture.Before; if (num > 0 && num <= int.MaxValue) { capture.Consumption.Gains.Add(new Gain(capture.Kind, capture.SteamId, capture.Before, (int)num)); } } } private void Update() { if (!HasAuthority) { ResetProgress(); return; } EnsureSession(); if (Time.unscaledTime >= nextPlayerScan) { ScanPlayers(); nextPlayerScan = Time.unscaledTime + 0.5f; } if (pending.Count != 0 && !(Time.unscaledTime < nextDispatch)) { ProcessPending(); } } internal void ProcessPending() { if (!HasAuthority) { ResetProgress(); return; } EnsureSession(); if (!Object.op_Implicit((Object)(object)PunManager.instance) || !Object.op_Implicit((Object)(object)GameDirector.instance) || !Object.op_Implicit((Object)(object)StatsManager.instance)) { return; } PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>(); if (!Object.op_Implicit((Object)(object)component) || component.ViewID == 0) { return; } int count = pending.Count; int num = 0; while (count-- > 0 && num < 4) { Delivery delivery = pending.Dequeue(); if (!players.TryGetValue(delivery.SteamId, out JoiningPlayer value) || PhotonNetwork.CurrentRoom == null || !PhotonNetwork.CurrentRoom.Players.ContainsKey(value.Actor)) { scheduled.Remove((delivery.Kind, delivery.SteamId)); continue; } PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(delivery.SteamId); if (!Object.op_Implicit((Object)(object)val) || (Object)(object)value.Avatar != (Object)(object)val || !value.Ready || !AvatarReady(val)) { pending.Enqueue(delivery); continue; } int level = delivery.Kind.GetLevel(StatsManager.instance, delivery.SteamId); int num2 = targets.Missing(delivery.SteamId, delivery.Kind.RpcName, level); if (num2 == 0) { scheduled.Remove((delivery.Kind, delivery.SteamId)); continue; } try { dispatchDepth++; component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { delivery.SteamId, delivery.Kind.RpcName, num2 }); num++; if (targets.Missing(delivery.SteamId, delivery.Kind.RpcName, delivery.Kind.GetLevel(StatsManager.instance, delivery.SteamId)) > 0) { pending.Enqueue(delivery); if (Time.unscaledTime >= nextErrorLog) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"The game's upgrade receiver is not ready; delivery will be retried."); nextErrorLog = Time.unscaledTime + 15f; } } else { scheduled.Remove((delivery.Kind, delivery.SteamId)); if (logShares.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Shared {delivery.Kind.RpcName} +{num2}."); } } } catch (Exception exception) { if (targets.Missing(delivery.SteamId, delivery.Kind.RpcName, delivery.Kind.GetLevel(StatsManager.instance, delivery.SteamId)) > 0) { pending.Enqueue(delivery); } else { scheduled.Remove((delivery.Kind, delivery.SteamId)); } LogDeliveryError("Unable to share an upgrade; ready players will be retried.", exception); } finally { dispatchDepth--; } } nextDispatch = Time.unscaledTime + 0.05f; } private void LogDeliveryError(string message, Exception exception) { if (!(Time.unscaledTime < nextErrorLog)) { ((BaseUnityPlugin)this).Logger.LogError((object)$"{message} {exception}"); nextErrorLog = Time.unscaledTime + 15f; } } private void OnDestroy() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } ResetProgress(); currentConsumption = null; if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } } [HarmonyPatch(typeof(StatsManager), "ResetAllStats")] internal static class ResetRunPatch { [HarmonyPrefix] private static void Prefix() { Plugin.Instance?.ResetProgress(); } } [HarmonyPatch(typeof(StatsManager), "LoadGame")] internal static class LoadRunPatch { [HarmonyPrefix] private static void Prefix() { Plugin.Instance?.ResetProgress(); } } [HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")] internal static class ConsumptionPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ItemUpgrade __instance, out Plugin.Consumption? __state) { __state = Plugin.Instance?.BeginConsumption(__instance); } [HarmonyFinalizer] private static void Finalizer(Plugin.Consumption? __state) { Plugin.Instance?.EndConsumption(__state); } } [HarmonyPatch] internal static class AppliedUpgradePatch { private static IEnumerable<MethodBase> TargetMethods() { UpgradeKind[] all = UpgradeKind.All; foreach (UpgradeKind upgradeKind in all) { yield return upgradeKind.GetMethod(); } } [HarmonyPrefix] private static void Prefix(MethodBase __originalMethod, string __0, out Plugin.Capture __state) { __state = Plugin.Instance?.BeforeUpgrade(__originalMethod, __0) ?? default(Plugin.Capture); } [HarmonyPostfix] private static void Postfix(Plugin.Capture __state) { Plugin.Instance?.AfterUpgrade(__state); } } internal sealed class UpgradeKind { internal readonly string MethodName; internal readonly string RpcName; private readonly Func<StatsManager, Dictionary<string, int>> levels; internal static readonly UpgradeKind[] All = new UpgradeKind[13] { new UpgradeKind("UpgradePlayerHealth", "Health", (StatsManager s) => s.playerUpgradeHealth), new UpgradeKind("UpgradePlayerEnergy", "Stamina", (StatsManager s) => s.playerUpgradeStamina), new UpgradeKind("UpgradePlayerExtraJump", "ExtraJump", (StatsManager s) => s.playerUpgradeExtraJump), new UpgradeKind("UpgradePlayerTumbleLaunch", "Launch", (StatsManager s) => s.playerUpgradeLaunch), new UpgradeKind("UpgradePlayerTumbleClimb", "TumbleClimb", (StatsManager s) => s.playerUpgradeTumbleClimb), new UpgradeKind("UpgradeDeathHeadBattery", "DeathHeadBattery", (StatsManager s) => s.playerUpgradeDeathHeadBattery), new UpgradeKind("UpgradeMapPlayerCount", "MapPlayerCount", (StatsManager s) => s.playerUpgradeMapPlayerCount), new UpgradeKind("UpgradePlayerTumbleWings", "TumbleWings", (StatsManager s) => s.playerUpgradeTumbleWings), new UpgradeKind("UpgradePlayerSprintSpeed", "Speed", (StatsManager s) => s.playerUpgradeSpeed), new UpgradeKind("UpgradePlayerCrouchRest", "CrouchRest", (StatsManager s) => s.playerUpgradeCrouchRest), new UpgradeKind("UpgradePlayerGrabStrength", "Strength", (StatsManager s) => s.playerUpgradeStrength), new UpgradeKind("UpgradePlayerThrowStrength", "Throw", (StatsManager s) => s.playerUpgradeThrow), new UpgradeKind("UpgradePlayerGrabRange", "Range", (StatsManager s) => s.playerUpgradeRange) }; private UpgradeKind(string method, string rpc, Func<StatsManager, Dictionary<string, int>> levels) { MethodName = method; RpcName = rpc; this.levels = levels; } internal int GetLevel(StatsManager stats, string steamId) { if (!levels(stats).TryGetValue(steamId, out var value)) { return 0; } return value; } internal int GetCrewLevel(StatsManager stats) { int num = 0; foreach (int value in levels(stats).Values) { num = Math.Max(num, value); } return num; } internal MethodInfo GetMethod() { return typeof(PunManager).GetMethod(MethodName, new Type[2] { typeof(string), typeof(int) }) ?? throw new MissingMethodException(typeof(PunManager).FullName, MethodName); } internal static UpgradeKind? FromMethod(string name) { UpgradeKind[] all = All; foreach (UpgradeKind upgradeKind in all) { if (upgradeKind.MethodName == name) { return upgradeKind; } } return null; } } internal sealed class UpgradeTargets { private readonly Dictionary<string, Dictionary<string, int>> players = new Dictionary<string, Dictionary<string, int>>(StringComparer.Ordinal); internal int Get(string steamId, string kind, int current) { if (players.TryGetValue(steamId, out Dictionary<string, int> value) && value.TryGetValue(kind, out var value2)) { return Math.Max(current, value2); } return Math.Max(0, current); } internal int CrewLevel(string kind, int savedLevel) { int num = Math.Max(0, savedLevel); foreach (Dictionary<string, int> value2 in players.Values) { if (value2.TryGetValue(kind, out var value)) { num = Math.Max(num, value); } } return num; } internal int CatchUp(string steamId, string kind, int current, int crewLevel) { return Set(steamId, kind, Math.Max(Get(steamId, kind, current), crewLevel)); } internal int Add(string steamId, string kind, int before, int amount) { return Set(steamId, kind, (int)Math.Min(2147483647L, (long)Get(steamId, kind, before) + (long)Math.Max(0, amount))); } internal int Missing(string steamId, string kind, int current) { return (int)Math.Min(2147483647L, Math.Max(0L, (long)Get(steamId, kind, current) - (long)current)); } private int Set(string steamId, string kind, int value) { if (!players.TryGetValue(steamId, out Dictionary<string, int> value2)) { players.Add(steamId, value2 = new Dictionary<string, int>(StringComparer.Ordinal)); } return value2[kind] = value; } internal void Clear() { players.Clear(); } } }