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 Shared v1.0.2
Shared.dll
Decompiled 18 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; 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 Shared.Patches; using Shared.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: AssemblyCompany("Shared")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Integrated Sharing Mod for REPO (Upgrades, Health, Damage, Battery).")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: AssemblyInformationalVersion("1.0.2+e888876923ee23b6d8201d2e7552f540ec268467")] [assembly: AssemblyProduct("Shared")] [assembly: AssemblyTitle("Shared")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.2.0")] [module: UnverifiableCode] [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 Shared { internal static class Configuration { public static ConfigEntry<string> LoggingLevel { get; private set; } public static ConfigEntry<bool> ShowWatermark { get; private set; } public static ConfigEntry<bool> EnableUpgradeSharing { get; private set; } public static ConfigEntry<int> UpgradeShareChance { get; private set; } public static ConfigEntry<bool> WholeTeamChance { get; private set; } public static ConfigEntry<bool> EnableModdedUpgradeSharing { get; private set; } public static ConfigEntry<bool> EnableLateJoinUpgradeSync { get; private set; } public static ConfigEntry<float> UpgradePriceScaling { get; private set; } public static ConfigEntry<bool> EnableUpgradeHeal { get; private set; } public static ConfigEntry<bool> EnableShareNotification { get; private set; } public static ConfigEntry<bool> EnableHealthSharing { get; private set; } public static ConfigEntry<bool> EnableDeathLiability { get; private set; } public static ConfigEntry<int> DeathDamagePercent { get; private set; } public static ConfigEntry<bool> ExcludeSafeZones { get; private set; } public static ConfigEntry<bool> EnableBatterySharing { get; private set; } public static void Init(ConfigFile config) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Expected O, but got Unknown LoggingLevel = config.Bind<string>("General", "LoggingLevel", "Info", new ConfigDescription("Verbosity level of mod logs.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[4] { "None", "Info", "Debug", "Verbose" }), Array.Empty<object>())); ShowWatermark = config.Bind<bool>("General", "ShowWatermark", true, "Show a subtle watermark UI indicating the Shared mod is loaded."); EnableUpgradeSharing = config.Bind<bool>("Upgrades", "EnableUpgradeSharing", true, "Enable sharing player stat upgrades with the team."); UpgradeShareChance = config.Bind<int>("Upgrades", "UpgradeShareChance", 100, new ConfigDescription("The percentage chance that purchased upgrades are shared (0-100).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); WholeTeamChance = config.Bind<bool>("Upgrades", "WholeTeamChance", true, "If true, a single roll is performed for the team. If false, each player rolls independently."); EnableModdedUpgradeSharing = config.Bind<bool>("Upgrades", "EnableModdedUpgradeSharing", true, "Enable sharing of modded/custom upgrades (including those from REPOLib)."); EnableLateJoinUpgradeSync = config.Bind<bool>("Upgrades", "EnableLateJoinUpgradeSync", true, "Synchronize existing team upgrades to players who join the lobby late."); UpgradePriceScaling = config.Bind<float>("Upgrades", "UpgradePriceScaling", 0.5f, new ConfigDescription("Scale the price of upgrades based on player count: Price = BasePrice * (1 + (PlayerCount - 1) * UpgradePriceScaling). Set to 0 to disable scaling.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); EnableUpgradeHeal = config.Bind<bool>("Upgrades", "EnableUpgradeHeal", true, "Automatically heal players when sharing health capacity upgrades."); EnableShareNotification = config.Bind<bool>("Upgrades", "EnableShareNotification", true, "Play visual and audio cues when upgrades are shared."); EnableHealthSharing = config.Bind<bool>("Health", "EnableHealthSharing", true, "When a health pack is used to heal a player, heal all other players by the same amount."); EnableDeathLiability = config.Bind<bool>("Damage", "EnableDeathLiability", true, "When a player dies, all other players take damage as a penalty."); DeathDamagePercent = config.Bind<int>("Damage", "DeathDamagePercent", 10, new ConfigDescription("Percentage of max health damage survivors take on team member death (0-100). 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); ExcludeSafeZones = config.Bind<bool>("Damage", "ExcludeSafeZones", true, "Do not deal liability damage to players inside the truck or extraction point rooms."); EnableBatterySharing = config.Bind<bool>("Battery", "EnableBatterySharing", true, "Drain charging station power crystals instead of item battery life when using battery-powered items."); } public static bool IsVerbose() { return LoggingLevel.Value == "Verbose"; } public static bool IsDebug() { if (!(LoggingLevel.Value == "Debug")) { return LoggingLevel.Value == "Verbose"; } return true; } public static bool IsInfo() { return LoggingLevel.Value != "None"; } } [BepInPlugin("semimodder-shared", "Shared", "1.0.2")] public class Plugin : BaseUnityPlugin { private Harmony? _harmony; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger; private void Awake() { Instance = this; ((Component)this).transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Configuration.Init(((BaseUnityPlugin)this).Config); Patch(); Log.LogInfo((object)"Shared Mod v1.0.2 loaded successfully!"); } private void Patch() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown if (_harmony == null) { _harmony = new Harmony("semimodder-shared"); } _harmony.PatchAll(); PatchRepoLib(); } private void PatchRepoLib() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_0079: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_00bb: Expected O, but got Unknown if (_harmony == null) { return; } MethodInfo setLevelMethod = RepoLibInterop.SetLevelMethod; MethodInfo applyUpgradeMethod = RepoLibInterop.ApplyUpgradeMethod; if (setLevelMethod == null || applyUpgradeMethod == null) { Log.LogInfo((object)"REPOLib not detected. Skipping custom modded upgrades integration."); return; } try { _harmony.Patch((MethodBase)setLevelMethod, new HarmonyMethod(typeof(RepoLibUpgradePatches).GetMethod("SetLevelPrefix")), new HarmonyMethod(typeof(RepoLibUpgradePatches).GetMethod("SetLevelPostfix")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)applyUpgradeMethod, new HarmonyMethod(typeof(RepoLibUpgradePatches).GetMethod("ApplyUpgradePrefix")), new HarmonyMethod(typeof(RepoLibUpgradePatches).GetMethod("ApplyUpgradePostfix")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Successfully hooked REPOLib PlayerUpgrade.SetLevel and ApplyUpgrade!"); } catch (Exception ex) { Log.LogWarning((object)("Failed to hook REPOLib: " + ex.Message)); } } } internal static class RepoLibUpgradePatches { [ThreadStatic] private static int? _stashedPrior; public static void SetLevelPrefix(object __instance, string steamId) { _stashedPrior = RepoLibInterop.ReadCurrentLevel(__instance, steamId); } public static void SetLevelPostfix() { _stashedPrior = null; } public static void ApplyUpgradePrefix(object __instance, string steamId, out int __state) { if (_stashedPrior.HasValue) { __state = _stashedPrior.Value; _stashedPrior = null; } else { __state = RepoLibInterop.ReadCurrentLevel(__instance, steamId); } } public static void ApplyUpgradePostfix(object __instance, string steamId, int level, int __state) { if (!Configuration.EnableUpgradeSharing.Value || !Configuration.EnableModdedUpgradeSharing.Value || !RepoLibInterop.TryReadUpgradeKey(__instance, out string upgradeKey) || !RegistryService.Instance.IsRegistered(upgradeKey) || RegistryService.Instance.IsVanilla(upgradeKey)) { return; } int num = level - __state; if (num <= 0 || SyncService.IsDistributing) { return; } PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(steamId); if ((Object)(object)val == (Object)null) { return; } if (Configuration.EnableShareNotification.Value) { UpgradePatches.PlayShareEffect(val); } if (SemiFunc.IsMasterClientOrSingleplayer()) { if (Configuration.IsInfo()) { Plugin.Log.LogInfo((object)$"[SharedMod] REPOLib upgrade {upgradeKey} (+{num}) bought by {val.GetPlayerName()}, distributing..."); } SyncService.DistributeUpgrade(upgradeKey, num, steamId, val.photonView.ViewID, val.GetPlayerName()); } } } } namespace Shared.Services { public class NetworkCallbackService : MonoBehaviourPunCallbacks { private readonly HashSet<Player> _pendingSync = new HashSet<Player>(); public static NetworkCallbackService? Instance { get; private set; } private void Awake() { Instance = this; CatchUpExistingPlayers(); } private void CatchUpExistingPlayers() { if (!Configuration.EnableLateJoinUpgradeSync.Value || !Configuration.EnableUpgradeSharing.Value || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { _pendingSync.Add(val); if (Configuration.IsInfo()) { Debug.Log((object)("[SharedMod] Deferred sync: " + val.NickName + " already in room, queued.")); } } } public override void OnJoinedRoom() { //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_003d: Expected O, but got Unknown //IL_003e: Expected O, but got Unknown if (Configuration.IsDebug()) { Debug.Log((object)$"[SharedMod] OnJoinedRoom (isMaster={PhotonNetwork.IsMasterClient})"); } try { if (PhotonNetwork.IsMasterClient) { Hashtable val = new Hashtable(); ((Dictionary<object, object>)val).Add((object)"shared_mod_ver", (object)"1.1.0"); Hashtable val2 = val; PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); if (Configuration.IsDebug()) { Debug.Log((object)"[SharedMod] Set room custom property shared_mod_ver = 1.1.0"); } } } catch (Exception ex) { Debug.LogWarning((object)("[SharedMod] Failed to set room custom properties: " + ex.Message)); } } public override void OnPlayerEnteredRoom(Player newPlayer) { if (Configuration.IsInfo()) { Debug.Log((object)$"[SharedMod] OnPlayerEnteredRoom: {newPlayer.NickName} (master={SemiFunc.IsMasterClientOrSingleplayer()}, sync={Configuration.EnableLateJoinUpgradeSync.Value})"); } if (Configuration.EnableLateJoinUpgradeSync.Value && Configuration.EnableUpgradeSharing.Value && SemiFunc.IsMasterClientOrSingleplayer()) { _pendingSync.Add(newPlayer); } } public override void OnPlayerLeftRoom(Player otherPlayer) { _pendingSync.Remove(otherPlayer); } public static bool IsPlayerPendingSync(Player player) { if ((Object)(object)Instance != (Object)null) { return Instance._pendingSync.Contains(player); } return false; } public IEnumerator LateSyncPlayer(PlayerAvatar avatar, string steamID, Dictionary<string, int> teamSnapshot) { yield return SyncService.ApplyTeamSnapshot(avatar, steamID, teamSnapshot); _pendingSync.Remove(avatar.photonView.Owner); } } public static class ReflectionExtensions { public static string GetPlayerName(this PlayerAvatar avatar) { return (string)AccessTools.Field(typeof(PlayerAvatar), "playerName").GetValue(avatar); } public static string GetSteamID(this PlayerAvatar avatar) { return SemiFunc.PlayerGetSteamID(avatar); } public static bool GetIsLocal(this PlayerAvatar avatar) { return (bool)AccessTools.Field(typeof(PlayerAvatar), "isLocal").GetValue(avatar); } public static bool GetDeadSet(this PlayerAvatar avatar) { return (bool)AccessTools.Field(typeof(PlayerAvatar), "deadSet").GetValue(avatar); } public static bool GetLevelAnimationCompleted(this PlayerAvatar avatar) { return (bool)AccessTools.Field(typeof(PlayerAvatar), "levelAnimationCompleted").GetValue(avatar); } public static int GetHealth(this PlayerHealth ph) { return (int)AccessTools.Field(typeof(PlayerHealth), "health").GetValue(ph); } public static int GetMaxHealth(this PlayerHealth ph) { return (int)AccessTools.Field(typeof(PlayerHealth), "maxHealth").GetValue(ph); } public static Dictionary<string, Dictionary<string, int>> GetDictionaryOfDictionaries(this StatsManager manager) { return (Dictionary<string, Dictionary<string, int>>)AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries").GetValue(manager); } public static Dictionary<string, int> GetRunStats(this StatsManager manager) { return (Dictionary<string, int>)AccessTools.Field(typeof(StatsManager), "runStats").GetValue(manager); } public static Dictionary<string, int> GetItemsPurchased(this StatsManager manager) { return (Dictionary<string, int>)AccessTools.Field(typeof(StatsManager), "itemsPurchased").GetValue(manager); } public static int GetChargeTotal(this ChargingStation station) { return (int)AccessTools.Field(typeof(ChargingStation), "chargeTotal").GetValue(station); } public static void SetChargeTotal(this ChargingStation station, int value) { AccessTools.Field(typeof(ChargingStation), "chargeTotal").SetValue(station, value); } public static List<GameObject> GetCrystals(this ChargingStation station) { return (List<GameObject>)AccessTools.Field(typeof(ChargingStation), "crystals").GetValue(station); } public static void SetChargeFloat(this ChargingStation station, float value) { AccessTools.Field(typeof(ChargingStation), "chargeFloat").SetValue(station, value); } public static void SetChargeInt(this ChargingStation station, int value) { AccessTools.Field(typeof(ChargingStation), "chargeInt").SetValue(station, value); } public static int GetBatteryLifeCountBars(this ItemBattery battery) { return (int)AccessTools.Field(typeof(ItemBattery), "batteryLifeCountBars").GetValue(battery); } public static void SetBatteryLifeCountBars(this ItemBattery battery, int value) { AccessTools.Field(typeof(ItemBattery), "batteryLifeCountBars").SetValue(battery, value); } public static int GetBatteryLifeCountBarsPrev(this ItemBattery battery) { return (int)AccessTools.Field(typeof(ItemBattery), "batteryLifeCountBarsPrev").GetValue(battery); } public static void SetBatteryLifeCountBarsPrev(this ItemBattery battery, int value) { AccessTools.Field(typeof(ItemBattery), "batteryLifeCountBarsPrev").SetValue(battery, value); } public static int GetBatteryLifeInt(this ItemBattery battery) { return (int)AccessTools.Field(typeof(ItemBattery), "batteryLifeInt").GetValue(battery); } public static void SetBatteryLifeInt(this ItemBattery battery, int value) { AccessTools.Field(typeof(ItemBattery), "batteryLifeInt").SetValue(battery, value); } public static int GetCurrentBars(this ItemBattery battery) { return (int)AccessTools.Field(typeof(ItemBattery), "currentBars").GetValue(battery); } public static void SetCurrentBars(this ItemBattery battery, int value) { AccessTools.Field(typeof(ItemBattery), "currentBars").SetValue(battery, value); } public static string GetInstanceName(this ItemAttributes attr) { return (string)AccessTools.Field(typeof(ItemAttributes), "instanceName").GetValue(attr); } public static int GetItemType(this ItemAttributes attr) { return (int)AccessTools.Field(typeof(ItemAttributes), "itemType").GetValue(attr); } public static int GetItemValue(this ItemAttributes attr) { return (int)AccessTools.Field(typeof(ItemAttributes), "value").GetValue(attr); } public static void SetItemValue(this ItemAttributes attr, int val) { AccessTools.Field(typeof(ItemAttributes), "value").SetValue(attr, val); } public static PhotonView GetPhotonView(this ItemAttributes attr) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown return (PhotonView)AccessTools.Field(typeof(ItemAttributes), "photonView").GetValue(attr); } public static ItemToggle GetItemToggle(this ItemUpgrade upgrade) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown return (ItemToggle)AccessTools.Field(typeof(ItemUpgrade), "itemToggle").GetValue(upgrade); } public static ItemAttributes GetItemAttributes(this ItemUpgrade upgrade) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown return (ItemAttributes)AccessTools.Field(typeof(ItemUpgrade), "itemAttributes").GetValue(upgrade); } public static int GetPlayerTogglePhotonID(this ItemToggle toggle) { return (int)AccessTools.Field(typeof(ItemToggle), "playerTogglePhotonID").GetValue(toggle); } public static ItemToggle GetItemToggle(this ItemHealthPack hp) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown return (ItemToggle)AccessTools.Field(typeof(ItemHealthPack), "itemToggle").GetValue(hp); } public static bool GetUsed(this ItemHealthPack hp) { return (bool)AccessTools.Field(typeof(ItemHealthPack), "used").GetValue(hp); } public static bool GetLevelIsShop(this RunManager manager) { return (bool)AccessTools.Field(typeof(RunManager), "levelIsShop").GetValue(manager); } } public sealed class Upgrade : IEquatable<Upgrade> { public string Name { get; } public string CleanName { get { if (!Name.StartsWith("playerUpgrade")) { return Name; } return Name.Substring("playerUpgrade".Length); } } public Upgrade(string name) { Name = name; } 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 RegistryService { private readonly HashSet<Upgrade> _vanillaUpgrades = new HashSet<Upgrade>(); private readonly HashSet<Upgrade> _moddedUpgrades = new HashSet<Upgrade>(); private static readonly RegistryService _instance = new RegistryService(); public static RegistryService Instance => _instance; public IReadOnlyCollection<Upgrade> VanillaUpgrades => _vanillaUpgrades; public IReadOnlyCollection<Upgrade> ModdedUpgrades => _moddedUpgrades; private RegistryService() { } public void DiscoverAndRegister(StatsManager statsManager) { _vanillaUpgrades.Clear(); _moddedUpgrades.Clear(); foreach (string item in statsManager.GetDictionaryOfDictionaries().Keys.Where((string k) => k.StartsWith("playerUpgrade"))) { if (AccessTools.Field(typeof(StatsManager), item) != null) { _vanillaUpgrades.Add(new Upgrade(item)); } else { _moddedUpgrades.Add(new Upgrade(item)); } } foreach (string moddedKey in RepoLibInterop.GetModdedUpgradeKeys()) { if (!_vanillaUpgrades.Any((Upgrade u) => u.Name == moddedKey)) { _moddedUpgrades.Add(new Upgrade(moddedKey)); } } Debug.Log((object)$"[SharedRegistry] Discovered {_vanillaUpgrades.Count} vanilla and {_moddedUpgrades.Count} modded upgrades."); } public bool IsVanilla(string key) { return _vanillaUpgrades.Any((Upgrade u) => u.Name == key); } public bool IsRegistered(string key) { if (!_vanillaUpgrades.Any((Upgrade u) => u.Name == key)) { return _moddedUpgrades.Any((Upgrade u) => u.Name == key); } return true; } } internal static class RepoLibInterop { private const string KeyPrefix = "playerUpgrade"; private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static bool _resolved; private static Type? _playerUpgradeType; private static FieldInfo? _upgradeIdField; private static FieldInfo? _playerDictionaryField; private static MethodInfo? _applyUpgradeMethod; private static MethodInfo? _setLevelMethod; private static FieldInfo? _playerUpgradesDictField; private static PropertyInfo? _playerUpgradesProp; public static MethodInfo? ApplyUpgradeMethod { get { Resolve(); return _applyUpgradeMethod; } } public static MethodInfo? SetLevelMethod { get { Resolve(); return _setLevelMethod; } } public static HashSet<string> GetModdedUpgradeKeys() { HashSet<string> hashSet = new HashSet<string>(); Resolve(); if (_playerUpgradesProp == null || _upgradeIdField == null) { return hashSet; } try { if (!(_playerUpgradesProp.GetValue(null) is IEnumerable enumerable)) { return hashSet; } foreach (object item in enumerable) { if (item != null && _upgradeIdField.GetValue(item) is string text && !string.IsNullOrEmpty(text)) { hashSet.Add("playerUpgrade" + text); } } } catch (Exception ex) { Debug.LogWarning((object)("[RepoLibInterop] reading PlayerUpgrades failed: " + ex.Message)); } return hashSet; } public static bool TryReadUpgradeKey(object playerUpgrade, out string upgradeKey) { upgradeKey = string.Empty; Resolve(); if (_upgradeIdField == null) { return false; } if (!(_upgradeIdField.GetValue(playerUpgrade) is string text) || string.IsNullOrEmpty(text)) { return false; } upgradeKey = "playerUpgrade" + text; return true; } public static int ReadCurrentLevel(object playerUpgrade, string steamId) { Resolve(); if (_playerDictionaryField == null) { return 0; } if (!(_playerDictionaryField.GetValue(playerUpgrade) is IDictionary dictionary)) { return 0; } if (!dictionary.Contains(steamId)) { return 0; } object obj = dictionary[steamId]; if (obj is int) { return (int)obj; } return 0; } public static bool TrySetLevel(string upgradeKey, string steamId, int level) { Resolve(); if (_setLevelMethod == null || _playerUpgradesDictField == null) { return false; } if (!upgradeKey.StartsWith("playerUpgrade")) { return false; } int length = "playerUpgrade".Length; string text = upgradeKey.Substring(length, upgradeKey.Length - length); if (!(_playerUpgradesDictField.GetValue(null) is IDictionary dictionary)) { return false; } if (!dictionary.Contains(text)) { return false; } object obj = dictionary[text]; if (obj == null) { return false; } try { _setLevelMethod.Invoke(obj, new object[2] { steamId, level }); return true; } catch (Exception ex) { Exception ex2 = (ex as TargetInvocationException)?.InnerException ?? ex; Debug.LogWarning((object)$"[RepoLibInterop] SetLevel({text}, {steamId}, {level}) threw {ex2.GetType().Name}: {ex2.Message}"); return false; } } private static void Resolve() { if (_resolved) { return; } _resolved = true; Type type = AccessTools.TypeByName("REPOLib.Modules.Upgrades"); if (!(type == null)) { _playerUpgradeType = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade"); if (!(_playerUpgradeType == null)) { _playerUpgradesProp = type.GetProperty("PlayerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _playerUpgradesDictField = type.GetField("_playerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _upgradeIdField = _playerUpgradeType.GetField("UpgradeId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _playerDictionaryField = _playerUpgradeType.GetField("PlayerDictionary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _applyUpgradeMethod = _playerUpgradeType.GetMethod("ApplyUpgrade", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(int) }, null); _setLevelMethod = _playerUpgradeType.GetMethod("SetLevel", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(int) }, null); } } } } public static class SyncService { private static bool _distributing; public static bool IsDistributing => _distributing; public static Dictionary<string, int> SnapshotPlayerStats(string steamID) { if (string.IsNullOrEmpty(steamID) || (Object)(object)StatsManager.instance == (Object)null) { return new Dictionary<string, int>(); } return (from kvp in StatsManager.instance.GetDictionaryOfDictionaries() where RegistryService.Instance.IsRegistered(kvp.Key) select kvp).ToDictionary((KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Key, (KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Value.GetValueOrDefault(steamID, 0)); } public static Dictionary<string, int> SnapshotTeamMaxLevels(string? excludeSteamID = null) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); if ((Object)(object)StatsManager.instance == (Object)null) { return dictionary; } foreach (KeyValuePair<string, Dictionary<string, int>> item in from k in StatsManager.instance.GetDictionaryOfDictionaries() where RegistryService.Instance.IsRegistered(k.Key) select k) { IEnumerable<int> source = (string.IsNullOrEmpty(excludeSteamID) ? item.Value.Values : (from p in item.Value where p.Key != excludeSteamID select p.Value)); dictionary[item.Key] = source.DefaultIfEmpty(0).Max(); } return dictionary; } public static void DistributeUpgrade(string upgradeKey, int levelDifference, string purchaserSteamID, int purchaserViewID, string purchaserName) { if (!Configuration.EnableUpgradeSharing.Value || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>(); if ((Object)(object)component == (Object)null) { Debug.LogError((object)"[SharedMod] PunManager PhotonView not found, cannot distribute upgrade."); return; } bool flag = RegistryService.Instance.IsVanilla(upgradeKey); if (!flag && !Configuration.EnableModdedUpgradeSharing.Value) { return; } int value = Configuration.UpgradeShareChance.Value; bool value2 = Configuration.WholeTeamChance.Value; if (Configuration.IsVerbose()) { Debug.Log((object)string.Format("[SharedMod] Distributing {0} (+{1}) from {2}. Chance={3}%, Mode={4}", upgradeKey, levelDifference, purchaserName, value, value2 ? "Team" : "Individual")); } if (value2 && !RollChance(value)) { if (Configuration.IsInfo()) { Debug.Log((object)$"[SharedMod] Upgrade share roll failed for team ({value}%)."); } return; } _distributing = true; try { foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null || item.photonView.ViewID == purchaserViewID) { continue; } string steamID = item.GetSteamID(); if (string.IsNullOrEmpty(steamID)) { continue; } int value3 = 0; if (StatsManager.instance.GetDictionaryOfDictionaries().TryGetValue(upgradeKey, out Dictionary<string, int> value4)) { value4.TryGetValue(steamID, out value3); } if (value2 || RollChance(value)) { int num = value3 + levelDifference; if (Configuration.IsInfo()) { Debug.Log((object)$"[SharedMod] Syncing {upgradeKey} (+{levelDifference}) to {item.GetPlayerName()} ({steamID}): {value3} -> {num}"); } if (flag) { string cleanName = new Upgrade(upgradeKey).CleanName; component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, cleanName, levelDifference }); } else if (!RepoLibInterop.TrySetLevel(upgradeKey, steamID, num)) { component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { upgradeKey, steamID, num }); } } } } finally { _distributing = false; } if (!(upgradeKey == "playerUpgradeHealth") || !Configuration.EnableUpgradeHeal.Value) { return; } PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(purchaserSteamID); if ((Object)(object)val != (Object)null) { int maxHealth = val.playerHealth.GetMaxHealth(); int health = val.playerHealth.GetHealth(); int num2 = maxHealth + 20 * levelDifference - health; if (num2 > 0) { val.playerHealth.HealOther(num2, false); } } } public static IEnumerator ApplyTeamSnapshot(PlayerAvatar player, string steamID, Dictionary<string, int> teamSnapshot) { if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null) { yield break; } PhotonView punView = ((Component)PunManager.instance).GetComponent<PhotonView>(); if ((Object)(object)punView == (Object)null) { yield break; } string playerName = player.GetPlayerName(); if (Configuration.IsInfo()) { Debug.Log((object)$"[SharedMod] Starting late join sync for {playerName} with {teamSnapshot.Count} upgrades."); } _distributing = true; try { foreach (KeyValuePair<string, int> item in teamSnapshot) { bool flag = RegistryService.Instance.IsVanilla(item.Key); if (!flag && !Configuration.EnableModdedUpgradeSharing.Value) { continue; } int value = 0; if (StatsManager.instance.GetDictionaryOfDictionaries().TryGetValue(item.Key, out Dictionary<string, int> value2)) { value2.TryGetValue(steamID, out value); } int num = item.Value - value; if (num <= 0) { continue; } if (Configuration.IsDebug()) { Debug.Log((object)$"[SharedMod] LateJoin Sync {item.Key}: level={value}, teamMax={item.Value}, missing={num}"); } if (flag) { string cleanName = new Upgrade(item.Key).CleanName; punView.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, cleanName, num }); } else { int num2 = value + num; if (!RepoLibInterop.TrySetLevel(item.Key, steamID, num2)) { punView.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { item.Key, steamID, num2 }); } } yield return (object)new WaitForSeconds(0.05f); } } finally { _distributing = false; } if (Configuration.IsInfo()) { Debug.Log((object)("[SharedMod] Completed late join sync for " + playerName + ".")); } } private static bool RollChance(int percentage) { if (percentage >= 100) { return true; } if (percentage <= 0) { return false; } return Random.Range(0, 100) < percentage; } } internal class WatermarkService : MonoBehaviour { private GUIStyle? _style; private void OnGUI() { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (!Configuration.ShowWatermark.Value) { return; } try { if (_style == null) { _style = new GUIStyle(GUI.skin.label) { fontSize = 18, alignment = (TextAnchor)6 }; _style.normal.textColor = new Color(1f, 1f, 1f, 0.2f); } GUI.Label(new Rect(10f, (float)Screen.height - 35f, 250f, 30f), "Shared Mod v1.1.0", _style); } catch { } } } } namespace Shared.Patches { [HarmonyPatch] public static class BatteryPatches { public readonly struct BatterySnapshot { public float BatteryLife { get; } public int BatteryLifeInt { get; } public int BatteryLifeCountBars { get; } public int BatteryLifeCountBarsPrev { get; } public int CurrentBars { get; } public BatterySnapshot(float batteryLife, int batteryLifeInt, int batteryLifeCountBars, int batteryLifeCountBarsPrev, int currentBars) { BatteryLife = batteryLife; BatteryLifeInt = batteryLifeInt; BatteryLifeCountBars = batteryLifeCountBars; BatteryLifeCountBarsPrev = batteryLifeCountBarsPrev; CurrentBars = currentBars; } } private const string PowerCrystalItemName = "Item Power Crystal"; private static readonly Dictionary<int, BatterySnapshot> SavedBatteryState = new Dictionary<int, BatterySnapshot>(); private static readonly Dictionary<int, float> BatteryDebt = new Dictionary<int, float>(); private static int? LastSyncedChargeTotal; private static int? LastSyncedCrystalCount; private static readonly MethodInfo? BatteryUpdateBarsMethod = typeof(ItemBattery).GetMethod("BatteryUpdateBars", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo? CooldownField = typeof(ItemMelee).GetField("durabilityLossCooldown", BindingFlags.Instance | BindingFlags.NonPublic); private static ItemBattery? FindBattery(Component component) { if (!((Object)(object)component == (Object)null)) { return component.GetComponent<ItemBattery>(); } return null; } private static BatterySnapshot Capture(ItemBattery itemBattery) { return new BatterySnapshot(itemBattery.batteryLife, itemBattery.GetBatteryLifeInt(), itemBattery.GetBatteryLifeCountBars(), itemBattery.GetBatteryLifeCountBarsPrev(), itemBattery.GetCurrentBars()); } private static void Restore(ItemBattery itemBattery, BatterySnapshot state) { itemBattery.batteryLife = state.BatteryLife; itemBattery.SetBatteryLifeInt(state.BatteryLifeInt); itemBattery.SetCurrentBars(state.CurrentBars); itemBattery.SetBatteryLifeCountBars(state.BatteryLifeCountBars); itemBattery.SetBatteryLifeCountBarsPrev(state.BatteryLifeCountBarsPrev); ItemAttributes component = ((Component)itemBattery).GetComponent<ItemAttributes>(); if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.GetInstanceName())) { SemiFunc.StatSetBattery(component.GetInstanceName(), Mathf.RoundToInt(state.BatteryLife)); } BatteryUpdateBarsMethod?.Invoke(itemBattery, new object[1] { state.BatteryLifeInt }); } private static int GetEnergyPerCrystal() { if ((Object)(object)ChargingStation.instance == (Object)null) { return 10; } return Traverse.Create((object)ChargingStation.instance).Field("energyPerCrystal").GetValue<int>(); } private static int GetMaxCrystals() { if ((Object)(object)ChargingStation.instance == (Object)null) { return 10; } return Traverse.Create((object)ChargingStation.instance).Field("maxCrystals").GetValue<int>(); } private static int GetMaxChargeTotal() { return GetEnergyPerCrystal() * GetMaxCrystals(); } private static int CalculateCrystalCount(int chargeTotal) { int energyPerCrystal = GetEnergyPerCrystal(); int maxCrystals = GetMaxCrystals(); return Mathf.Clamp(Mathf.CeilToInt((float)chargeTotal / (float)energyPerCrystal), 0, maxCrystals); } private static int GetPurchaseBaselineTotal(int rawTotal) { int maxChargeTotal = GetMaxChargeTotal(); int num = Mathf.Clamp(rawTotal, 0, maxChargeTotal); if (!LastSyncedChargeTotal.HasValue) { return num; } return Mathf.Min(num, LastSyncedChargeTotal.Value); } private static int GetPurchaseBaselineCrystalCount(int purchasedAfter) { int num = Mathf.Clamp(purchasedAfter - 1, 0, GetMaxCrystals()); if (!LastSyncedCrystalCount.HasValue) { return num; } return Mathf.Min(num, LastSyncedCrystalCount.Value); } public static void DrainStation(int cost) { if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)ChargingStation.instance != (Object)null && (Object)(object)StatsManager.instance != (Object)null) { if (Configuration.IsDebug()) { Debug.Log((object)$"[SharedMod] Draining station: cost={cost}, current={ChargingStation.instance.GetChargeTotal()}"); } SyncStationState(Mathf.Max(0, ChargingStation.instance.GetChargeTotal() - cost)); } } private static void SyncStationState(int chargeTotal, int? crystalCount = null) { if (!((Object)(object)StatsManager.instance == (Object)null)) { int maxChargeTotal = GetMaxChargeTotal(); int num = Mathf.Clamp(chargeTotal, 0, maxChargeTotal); int num2 = crystalCount ?? CalculateCrystalCount(num); num2 = Mathf.Clamp(num2, 0, GetMaxCrystals()); bool flag = false; if ((Object)(object)ChargingStation.instance != (Object)null) { ChargingStation.instance.SetChargeTotal(num); float value = (float)num / (float)maxChargeTotal; ChargingStation.instance.SetChargeFloat(value); flag = ChargingStation.instance.GetCrystals().Count > num2; } if (flag) { SyncCrystalVisuals(num2); } if ((Object)(object)ChargingStation.instance != (Object)null) { ChargingStation.instance.SetChargeInt(num2); } StatsManager.instance.GetRunStats()["chargingStationChargeTotal"] = num; StatsManager.instance.GetRunStats()["chargingStationCharge"] = num2; StatsManager.instance.GetItemsPurchased()["Item Power Crystal"] = num2; LastSyncedChargeTotal = num; LastSyncedCrystalCount = num2; } } private static void SyncCrystalVisuals(int targetCrystalCount) { if ((Object)(object)ChargingStation.instance == (Object)null || SemiFunc.RunIsShop() || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } int count = ChargingStation.instance.GetCrystals().Count; int num = Mathf.Max(0, count - targetCrystalCount); if (num <= 0) { return; } int value = Mathf.Clamp(targetCrystalCount + num, 0, GetMaxCrystals()); ChargingStation.instance.SetChargeInt(value); StatsManager.instance.GetRunStats()["chargingStationCharge"] = value; StatsManager.instance.GetItemsPurchased()["Item Power Crystal"] = value; MethodInfo method = typeof(ChargingStation).GetMethod("DestroyCrystal", BindingFlags.Instance | BindingFlags.NonPublic); if (method != null) { for (int i = 0; i < num; i++) { method.Invoke(ChargingStation.instance, null); } } } public static void ResetCachedSync() { LastSyncedChargeTotal = null; LastSyncedCrystalCount = null; } [HarmonyPatch(typeof(ItemBattery), "RemoveFullBar")] [HarmonyPrefix] public static bool ItemBattery_RemoveFullBar_Prefix(ItemBattery __instance, int _bars) { if (!Configuration.EnableBatterySharing.Value) { return true; } if (!SemiFunc.IsMasterClientOrSingleplayer()) { return true; } if (SemiFunc.RunIsShop()) { return true; } if ((Object)(object)ChargingStation.instance == (Object)null || (Object)(object)StatsManager.instance == (Object)null) { return true; } int num = Mathf.Max(1, Mathf.RoundToInt((float)_bars * 20f / (float)__instance.batteryBars)); if (ChargingStation.instance.GetChargeTotal() >= num) { DrainStation(num); return false; } return true; } [HarmonyPatch(typeof(ItemBattery), "Update")] [HarmonyPrefix] public static void ItemBattery_Update_Prefix(ItemBattery __instance) { if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && !SemiFunc.RunIsShop()) { SavedBatteryState[((Object)__instance).GetInstanceID()] = Capture(__instance); } } [HarmonyPatch(typeof(ItemBattery), "Update")] [HarmonyPostfix] public static void ItemBattery_Update_Postfix(ItemBattery __instance) { if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)ChargingStation.instance == (Object)null) { return; } int instanceID = ((Object)__instance).GetInstanceID(); if (!SavedBatteryState.TryGetValue(instanceID, out var value)) { return; } float num = value.BatteryLife - __instance.batteryLife; if (num <= 0f) { return; } if (ChargingStation.instance.GetChargeTotal() <= 0) { BatteryDebt.Remove(instanceID); return; } Restore(__instance, value); float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f; if (num2 >= 1f) { int num3 = Mathf.FloorToInt(num2); num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal()); DrainStation(num3); num2 -= (float)num3; } BatteryDebt[instanceID] = num2; } [HarmonyPatch(typeof(ItemBattery), "FixedUpdate")] [HarmonyPrefix] public static void ItemBattery_FixedUpdate_Prefix(ItemBattery __instance) { if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && !SemiFunc.RunIsShop()) { SavedBatteryState[((Object)__instance).GetInstanceID()] = Capture(__instance); } } [HarmonyPatch(typeof(ItemBattery), "FixedUpdate")] [HarmonyPostfix] public static void ItemBattery_FixedUpdate_Postfix(ItemBattery __instance) { if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)ChargingStation.instance == (Object)null) { return; } int instanceID = ((Object)__instance).GetInstanceID(); if (!SavedBatteryState.TryGetValue(instanceID, out var value)) { return; } float num = value.BatteryLife - __instance.batteryLife; if (num <= 0f) { return; } if (ChargingStation.instance.GetChargeTotal() <= 0) { BatteryDebt.Remove(instanceID); return; } Restore(__instance, value); float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f; if (num2 >= 1f) { int num3 = Mathf.FloorToInt(num2); num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal()); DrainStation(num3); num2 -= (float)num3; } BatteryDebt[instanceID] = num2; } [HarmonyPatch(typeof(ItemBattery), "OnDisable")] [HarmonyPostfix] public static void ItemBattery_OnDisable_Postfix(ItemBattery __instance) { int instanceID = ((Object)__instance).GetInstanceID(); SavedBatteryState.Remove(instanceID); BatteryDebt.Remove(instanceID); } [HarmonyPatch(typeof(StatsManager), "LoadGame")] [HarmonyPrefix] public static void StatsManager_LoadGame_Cleanup() { ResetCachedSync(); SavedBatteryState.Clear(); BatteryDebt.Clear(); } [HarmonyPatch(typeof(StatsManager), "ResetAllStats")] [HarmonyPrefix] public static void StatsManager_ResetAllStats_Cleanup() { ResetCachedSync(); SavedBatteryState.Clear(); BatteryDebt.Clear(); } [HarmonyPatch(typeof(ChargingStation), "Start")] [HarmonyPrefix] public static void ChargingStation_Start_Prefix(ChargingStation __instance) { if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)StatsManager.instance != (Object)null) { int value = Traverse.Create((object)__instance).Field("energyPerCrystal").GetValue<int>(); int value2 = Traverse.Create((object)__instance).Field("maxCrystals").GetValue<int>(); int num = value * value2; int num2 = StatsManager.instance.GetRunStats()["chargingStationChargeTotal"]; int num3 = StatsManager.instance.GetItemsPurchased()["Item Power Crystal"]; int num4 = Mathf.Clamp(num2, 0, num); int num5 = Mathf.Clamp(Mathf.CeilToInt((float)num4 / (float)value), 0, value2); int value3 = Mathf.Clamp(Mathf.Max(num3, num5), 0, value2); SyncStationState(num4, value3); } } [HarmonyPatch(typeof(StatsManager), "ItemPurchase")] [HarmonyPostfix] public static void StatsManager_ItemPurchase_Postfix(string itemName) { if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer() && itemName == "Item Power Crystal" && (Object)(object)StatsManager.instance != (Object)null) { int rawTotal = StatsManager.instance.GetRunStats()["chargingStationChargeTotal"]; int purchasedAfter = Mathf.Max(StatsManager.instance.GetItemsPurchased()["Item Power Crystal"], 0); int purchaseBaselineTotal = GetPurchaseBaselineTotal(rawTotal); int energyPerCrystal = GetEnergyPerCrystal(); int purchaseBaselineCrystalCount = GetPurchaseBaselineCrystalCount(purchasedAfter); int num = Mathf.Min(GetMaxCrystals(), purchaseBaselineCrystalCount + 1); int num2 = Mathf.Min(num * energyPerCrystal, GetMaxChargeTotal()); SyncStationState(Mathf.Clamp(purchaseBaselineTotal + energyPerCrystal, 0, num2), num); } } [HarmonyPatch(typeof(ItemMelee), "SwingHitRPC")] [HarmonyPrefix] public static void ItemMelee_SwingHitRPC_Prefix(ItemMelee __instance, ref BatterySnapshot? __state) { __state = null; if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer()) { ItemBattery val = FindBattery((Component)(object)__instance); if ((Object)(object)val != (Object)null) { __state = Capture(val); } } } [HarmonyPatch(typeof(ItemMelee), "SwingHitRPC")] [HarmonyPostfix] public static void ItemMelee_SwingHitRPC_Postfix(ItemMelee __instance, bool _durabilityLoss, BatterySnapshot? __state) { if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || !_durabilityLoss || (Object)(object)ChargingStation.instance == (Object)null || !__state.HasValue) { return; } ItemBattery val = FindBattery((Component)(object)__instance); if ((Object)(object)val == (Object)null || ChargingStation.instance.GetChargeTotal() <= 0 || (float)(CooldownField?.GetValue(__instance) ?? ((object)0f)) <= 0f) { return; } float num = __state.Value.BatteryLife - val.batteryLife; if (!(num <= 0f)) { Restore(val, __state.Value); int instanceID = ((Object)__instance).GetInstanceID(); float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f; if (num2 >= 1f) { int num3 = Mathf.FloorToInt(num2); num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal()); DrainStation(num3); num2 -= (float)num3; } BatteryDebt[instanceID] = num2; } } [HarmonyPatch(typeof(ItemMelee), "EnemyOrPVPSwingHitRPC")] [HarmonyPrefix] public static void ItemMelee_EnemyOrPVPSwingHitRPC_Prefix(ItemMelee __instance, ref BatterySnapshot? __state) { __state = null; if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer()) { ItemBattery val = FindBattery((Component)(object)__instance); if ((Object)(object)val != (Object)null) { __state = Capture(val); } } } [HarmonyPatch(typeof(ItemMelee), "EnemyOrPVPSwingHitRPC")] [HarmonyPostfix] public static void ItemMelee_EnemyOrPVPSwingHitRPC_Postfix(ItemMelee __instance, BatterySnapshot? __state) { if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)ChargingStation.instance == (Object)null || !__state.HasValue) { return; } ItemBattery val = FindBattery((Component)(object)__instance); if ((Object)(object)val == (Object)null || ChargingStation.instance.GetChargeTotal() <= 0) { return; } float num = __state.Value.BatteryLife - val.batteryLife; if (!(num <= 0f)) { Restore(val, __state.Value); int instanceID = ((Object)__instance).GetInstanceID(); float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f; if (num2 >= 1f) { int num3 = Mathf.FloorToInt(num2); num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal()); DrainStation(num3); num2 -= (float)num3; } BatteryDebt[instanceID] = num2; } } [HarmonyPatch(typeof(ItemDroneZeroGravity), "Update")] [HarmonyPrefix] public static void ItemDroneZeroGravity_Update_Prefix(ItemDroneZeroGravity __instance, ref BatterySnapshot? __state) { __state = null; if (Configuration.EnableBatterySharing.Value && SemiFunc.IsMasterClientOrSingleplayer()) { ItemBattery val = FindBattery((Component)(object)__instance); if ((Object)(object)val != (Object)null) { __state = Capture(val); } } } [HarmonyPatch(typeof(ItemDroneZeroGravity), "Update")] [HarmonyPostfix] public static void ItemDroneZeroGravity_Update_Postfix(ItemDroneZeroGravity __instance, BatterySnapshot? __state) { if (!Configuration.EnableBatterySharing.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)ChargingStation.instance == (Object)null || !__state.HasValue) { return; } ItemBattery val = FindBattery((Component)(object)__instance); if ((Object)(object)val == (Object)null || ChargingStation.instance.GetChargeTotal() <= 0) { return; } float num = __state.Value.BatteryLife - val.batteryLife; if (!(num <= 0f)) { Restore(val, __state.Value); int instanceID = ((Object)val).GetInstanceID(); float num2 = BatteryDebt.GetValueOrDefault(instanceID, 0f) + num * 20f / 100f; if (num2 >= 1f) { int num3 = Mathf.FloorToInt(num2); num3 = Mathf.Min(num3, ChargingStation.instance.GetChargeTotal()); DrainStation(num3); num2 -= (float)num3; } BatteryDebt[instanceID] = num2; } } } [HarmonyPatch] public static class DamagePatches { private static readonly Dictionary<int, float> MarkedBySolidarityUntil = new Dictionary<int, float>(16); private static readonly FieldInfo Field_RoomVolumeCheck_inTruck = AccessTools.Field(typeof(RoomVolumeCheck), "inTruck"); private static readonly FieldInfo Field_RoomVolumeCheck_inExtractionPoint = AccessTools.Field(typeof(RoomVolumeCheck), "inExtractionPoint"); [HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")] [HarmonyPostfix] public static void PlayerAvatar_PlayerDeathRPC_Postfix(PlayerAvatar __instance, int enemyIndex) { if (!Configuration.EnableDeathLiability.Value || !GameManager.Multiplayer() || !PhotonNetwork.IsMasterClient) { return; } int num = Mathf.Clamp(Configuration.DeathDamagePercent.Value, 0, 100); if (num <= 0) { return; } if (ConsumeSolidarityMark(__instance)) { if (Configuration.IsDebug()) { Debug.Log((object)("[SharedMod] " + __instance.GetPlayerName() + " died of solidarity damage. Preventing loop.")); } return; } MonoBehaviour component = ((Component)__instance).GetComponent<MonoBehaviour>(); if (component != null) { component.StartCoroutine(ApplyDamageToSurvivorsNextFrame(num)); } } private static IEnumerator ApplyDamageToSurvivorsNextFrame(int percent) { yield return null; if (!PhotonNetwork.IsMasterClient) { yield break; } PurgeExpiredMarks(); PlayerAvatar[] array = Object.FindObjectsOfType<PlayerAvatar>(); if (array == null || array.Length == 0) { yield break; } PlayerAvatar[] array2 = array; foreach (PlayerAvatar val in array2) { if ((Object)(object)val == (Object)null || (Object)(object)val.playerHealth == (Object)null) { continue; } bool deadSet = val.GetDeadSet(); int health = val.playerHealth.GetHealth(); if (deadSet || health <= 0) { continue; } if (Configuration.ExcludeSafeZones.Value && IsInSafeZone(val)) { if (Configuration.IsDebug()) { Debug.Log((object)("[SharedMod] Excluding " + val.GetPlayerName() + " from death liability (in safe zone).")); } continue; } int num = val.playerHealth.GetMaxHealth(); if (num <= 0) { num = 100; } int num2 = Mathf.RoundToInt((float)num * ((float)percent / 100f)); num2 = Mathf.Clamp(num2, 1, num); MarkSolidarityTarget(val); if (Configuration.IsInfo()) { Debug.Log((object)$"[SharedMod] Applying {num2} death liability damage to {val.GetPlayerName()}"); } try { val.playerHealth.HurtOther(num2, Vector3.zero, false, -1, false); } catch (Exception ex) { Debug.LogError((object)("[SharedMod] Failed to hurt survivor " + val.GetPlayerName() + ": " + ex.Message)); } } } private static bool IsInSafeZone(PlayerAvatar avatar) { RoomVolumeCheck roomVolumeCheck = avatar.RoomVolumeCheck; if ((Object)(object)roomVolumeCheck == (Object)null) { return false; } try { roomVolumeCheck.CheckSet(); } catch { } bool num = (bool)(Field_RoomVolumeCheck_inTruck?.GetValue(roomVolumeCheck) ?? ((object)false)); bool flag = (bool)(Field_RoomVolumeCheck_inExtractionPoint?.GetValue(roomVolumeCheck) ?? ((object)false)); return num || flag; } private static void MarkSolidarityTarget(PlayerAvatar avatar) { if ((Object)(object)avatar != (Object)null && (Object)(object)avatar.photonView != (Object)null) { int viewID = avatar.photonView.ViewID; if (viewID > 0) { MarkedBySolidarityUntil[viewID] = Time.time + 3f; } } } private static bool ConsumeSolidarityMark(PlayerAvatar avatar) { if ((Object)(object)avatar == (Object)null || (Object)(object)avatar.photonView == (Object)null) { return false; } int viewID = avatar.photonView.ViewID; if (viewID <= 0) { return false; } float time = Time.time; if (MarkedBySolidarityUntil.TryGetValue(viewID, out var value)) { MarkedBySolidarityUntil.Remove(viewID); return value >= time; } return false; } private static void PurgeExpiredMarks() { if (MarkedBySolidarityUntil.Count == 0) { return; } float time = Time.time; List<int> list = new List<int>(); foreach (KeyValuePair<int, float> item in MarkedBySolidarityUntil) { if (item.Value < time) { list.Add(item.Key); } } foreach (int item2 in list) { MarkedBySolidarityUntil.Remove(item2); } } } [HarmonyPatch] public static class HealthPatches { private static bool _healAllFlag; [HarmonyPatch(typeof(ItemHealthPack), "Update")] [HarmonyPrefix] public static void ItemHealthPack_Update_Prefix(ItemHealthPack __instance) { if (!Configuration.EnableHealthSharing.Value || SemiFunc.RunIsShop() || RunManager.instance.GetLevelIsShop()) { return; } ItemToggle itemToggle = __instance.GetItemToggle(); if (SemiFunc.IsMasterClientOrSingleplayer() && !((Object)(object)itemToggle == (Object)null) && itemToggle.toggleState && !__instance.GetUsed()) { PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(itemToggle.GetPlayerTogglePhotonID()); if (!((Object)(object)val == (Object)null) && val.playerHealth.GetHealth() < val.playerHealth.GetMaxHealth()) { _healAllFlag = true; } } } [HarmonyPatch(typeof(PlayerHealth), "HealOther")] [HarmonyPostfix] public static void PlayerHealth_HealOther_Postfix(PlayerHealth __instance, int healAmount, bool effect) { if (!_healAllFlag) { return; } _healAllFlag = false; if (Configuration.IsInfo()) { Debug.Log((object)$"[SharedMod] Health Pack used. Sharing {healAmount} HP with the entire team."); } foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if ((Object)(object)item != (Object)null && (Object)(object)item.playerHealth != (Object)(object)__instance) { item.playerHealth.HealOther(healAmount, effect); } } } } [HarmonyPatch] public static class UpgradePatches { public struct UpgradeContext { public string SteamID; public int ViewID; public string PlayerName; public Dictionary<string, int> PreUpgradeStats; public string? ItemName; } [HarmonyPatch(typeof(StatsManager), "Start")] [HarmonyPostfix] public static void StatsManager_Start_Postfix(StatsManager __instance) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown if (Configuration.IsDebug()) { Debug.Log((object)"[SharedMod] StatsManager.Start: initializing upgrade registry."); } RegistryService.Instance.DiscoverAndRegister(__instance); if ((Object)(object)NetworkCallbackService.Instance == (Object)null) { GameObject val = new GameObject("SharedMod_NetworkCallbacks"); val.AddComponent<NetworkCallbackService>(); val.AddComponent<WatermarkService>(); Object.DontDestroyOnLoad((Object)val); } } [HarmonyPatch(typeof(StatsManager), "LoadGame")] [HarmonyPostfix] public static void StatsManager_LoadGame_Postfix(StatsManager __instance) { if (Configuration.IsDebug()) { Debug.Log((object)"[SharedMod] StatsManager.LoadGame: refreshing upgrade registry."); } RegistryService.Instance.DiscoverAndRegister(__instance); } [HarmonyPatch(typeof(StatsManager), "RunStartStats")] [HarmonyPostfix] [HarmonyAfter(new string[] { "REPOLib" })] public static void StatsManager_RunStartStats_Postfix(StatsManager __instance) { if (Configuration.IsDebug()) { Debug.Log((object)"[SharedMod] StatsManager.RunStartStats: refreshing upgrade registry."); } RegistryService.Instance.DiscoverAndRegister(__instance); } [HarmonyPatch(typeof(ItemAttributes), "GetValue")] [HarmonyPostfix] public static void ItemAttributes_GetValue_Postfix(ItemAttributes __instance) { if (Configuration.UpgradePriceScaling.Value <= 0f || __instance.GetItemType() != 3) { return; } float value = Configuration.UpgradePriceScaling.Value; int count = SemiFunc.PlayerGetAll().Count; if (count > 1) { float num = 1f + (float)(count - 1) * value; int num2 = (int)Math.Round((float)__instance.GetItemValue() * num); if (num2 < 0) { num2 = 0; } __instance.SetItemValue(num2); PhotonView photonView = __instance.GetPhotonView(); if (GameManager.Multiplayer() && PhotonNetwork.IsMasterClient && (Object)(object)photonView != (Object)null) { photonView.RPC("GetValueRPC", (RpcTarget)1, new object[1] { num2 }); } } } [HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")] [HarmonyPrefix] public static void ItemUpgrade_PlayerUpgrade_Prefix(ItemUpgrade __instance, out UpgradeContext? __state) { __state = null; if (!Configuration.EnableUpgradeSharing.Value || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } ItemToggle itemToggle = __instance.GetItemToggle(); if ((Object)(object)itemToggle == (Object)null || !itemToggle.toggleState) { return; } int playerTogglePhotonID = itemToggle.GetPlayerTogglePhotonID(); PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(playerTogglePhotonID); if (!((Object)(object)val == (Object)null)) { string steamID = val.GetSteamID(); if (!string.IsNullOrEmpty(steamID)) { ItemAttributes itemAttributes = __instance.GetItemAttributes(); string itemName = (((Object)(object)itemAttributes != (Object)null && (Object)(object)itemAttributes.item != (Object)null) ? ((Object)itemAttributes.item).name : null); __state = new UpgradeContext { SteamID = steamID, ViewID = playerTogglePhotonID, PlayerName = val.GetPlayerName(), PreUpgradeStats = SyncService.SnapshotPlayerStats(steamID), ItemName = itemName }; } } } [HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")] [HarmonyPostfix] public static void ItemUpgrade_PlayerUpgrade_Postfix(ItemUpgrade __instance, UpgradeContext? __state) { if (!SemiFunc.IsMasterClientOrSingleplayer() || !__state.HasValue) { return; } string steamID = __state.Value.SteamID; int viewID = __state.Value.ViewID; string playerName = __state.Value.PlayerName; bool flag = false; foreach (KeyValuePair<string, Dictionary<string, int>> item in from kvp in StatsManager.instance.GetDictionaryOfDictionaries() where RegistryService.Instance.IsRegistered(kvp.Key) select kvp) { int valueOrDefault = item.Value.GetValueOrDefault(steamID, 0); int valueOrDefault2 = __state.Value.PreUpgradeStats.GetValueOrDefault(item.Key, 0); if (valueOrDefault > valueOrDefault2) { int num = valueOrDefault - valueOrDefault2; flag = true; if (Configuration.IsInfo()) { Debug.Log((object)$"[SharedMod] {playerName} bought upgrade {item.Key} (+{num}), distributing..."); } SyncService.DistributeUpgrade(item.Key, num, steamID, viewID, playerName); } } if (flag || __state.Value.ItemName == null || !Configuration.EnableModdedUpgradeSharing.Value) { return; } string text = __state.Value.ItemName.Replace(" ", ""); foreach (Upgrade moddedUpgrade in RegistryService.Instance.ModdedUpgrades) { string value = moddedUpgrade.CleanName.Replace(" ", ""); if (text.EndsWith(value, StringComparison.OrdinalIgnoreCase)) { if (Configuration.IsInfo()) { Debug.Log((object)("[SharedMod] " + playerName + " bought modded upgrade " + moddedUpgrade.Name + " matched by item name, distributing...")); } SyncService.DistributeUpgrade(moddedUpgrade.Name, 1, steamID, viewID, playerName); break; } } } [HarmonyPatch(typeof(PlayerTumble), "SetupDone")] [HarmonyPostfix] public static void PlayerTumble_SetupDone_Postfix(PlayerTumble __instance) { if (SemiFunc.IsMasterClientOrSingleplayer() && !string.IsNullOrEmpty(__instance.playerAvatar.GetSteamID()) && NetworkCallbackService.IsPlayerPendingSync(__instance.playerAvatar.photonView.Owner) && Configuration.EnableLateJoinUpgradeSync.Value && Configuration.EnableUpgradeSharing.Value) { if (Configuration.IsInfo()) { Debug.Log((object)("[SharedMod] PlayerTumble.SetupDone for " + __instance.playerAvatar.GetPlayerName() + " (" + __instance.playerAvatar.GetSteamID() + "), triggering sync.")); } Dictionary<string, int> teamSnapshot = SyncService.SnapshotTeamMaxLevels(__instance.playerAvatar.GetSteamID()); if ((Object)(object)NetworkCallbackService.Instance != (Object)null) { ((MonoBehaviour)NetworkCallbackService.Instance).StartCoroutine(NetworkCallbackService.Instance.LateSyncPlayer(__instance.playerAvatar, __instance.playerAvatar.GetSteamID(), teamSnapshot)); } } } [HarmonyPatch(typeof(PunManager), "TesterUpgradeCommandRPC")] [HarmonyPostfix] public static void PunManager_TesterUpgradeCommandRPC_Postfix(string _steamID, string upgradeName, int upgradeNum) { PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(_steamID); if ((Object)(object)val == (Object)null) { return; } if (Configuration.EnableShareNotification.Value) { PlayShareEffect(val); } if (upgradeName == "Health" && SemiFunc.IsMasterClientOrSingleplayer() && Configuration.EnableUpgradeHeal.Value) { int maxHealth = val.playerHealth.GetMaxHealth(); int health = val.playerHealth.GetHealth(); int num = maxHealth + 20 * upgradeNum - health; if (num > 0) { val.playerHealth.HealOther(num, false); } } } public static void PlayShareEffect(PlayerAvatar player) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) try { if (player.GetIsLocal()) { if ((Object)(object)StatsUI.instance != (Object)null) { StatsUI.instance.Fetch(); StatsUI.instance.ShowStats(); } if ((Object)(object)CameraGlitch.Instance != (Object)null) { CameraGlitch.Instance.PlayUpgrade(); } } else if ((Object)(object)GameDirector.instance != (Object)null && (Object)(object)GameDirector.instance.CameraImpact != (Object)null) { GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)player).transform.position, 0.2f); } if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient) { player.playerHealth.MaterialEffectOverride((Effect)0); } } catch (Exception ex) { Debug.LogWarning((object)("[SharedMod] PlayShareEffect failed: " + ex.Message)); } } } }