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.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 LoggingLevel { get; private set; } public static ConfigEntry ShowWatermark { get; private set; } public static ConfigEntry EnableUpgradeSharing { get; private set; } public static ConfigEntry UpgradeShareChance { get; private set; } public static ConfigEntry WholeTeamChance { get; private set; } public static ConfigEntry EnableModdedUpgradeSharing { get; private set; } public static ConfigEntry EnableLateJoinUpgradeSync { get; private set; } public static ConfigEntry UpgradePriceScaling { get; private set; } public static ConfigEntry EnableUpgradeHeal { get; private set; } public static ConfigEntry EnableShareNotification { get; private set; } public static ConfigEntry EnableHealthSharing { get; private set; } public static ConfigEntry EnableDeathLiability { get; private set; } public static ConfigEntry DeathDamagePercent { get; private set; } public static ConfigEntry ExcludeSafeZones { get; private set; } public static ConfigEntry 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("General", "LoggingLevel", "Info", new ConfigDescription("Verbosity level of mod logs.", (AcceptableValueBase)(object)new AcceptableValueList(new string[4] { "None", "Info", "Debug", "Verbose" }), Array.Empty())); ShowWatermark = config.Bind("General", "ShowWatermark", true, "Show a subtle watermark UI indicating the Shared mod is loaded."); EnableUpgradeSharing = config.Bind("Upgrades", "EnableUpgradeSharing", true, "Enable sharing player stat upgrades with the team."); UpgradeShareChance = config.Bind("Upgrades", "UpgradeShareChance", 100, new ConfigDescription("The percentage chance that purchased upgrades are shared (0-100).", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); WholeTeamChance = config.Bind("Upgrades", "WholeTeamChance", true, "If true, a single roll is performed for the team. If false, each player rolls independently."); EnableModdedUpgradeSharing = config.Bind("Upgrades", "EnableModdedUpgradeSharing", true, "Enable sharing of modded/custom upgrades (including those from REPOLib)."); EnableLateJoinUpgradeSync = config.Bind("Upgrades", "EnableLateJoinUpgradeSync", true, "Synchronize existing team upgrades to players who join the lobby late."); UpgradePriceScaling = config.Bind("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(0f, 2f), Array.Empty())); EnableUpgradeHeal = config.Bind("Upgrades", "EnableUpgradeHeal", true, "Automatically heal players when sharing health capacity upgrades."); EnableShareNotification = config.Bind("Upgrades", "EnableShareNotification", true, "Play visual and audio cues when upgrades are shared."); EnableHealthSharing = config.Bind("Health", "EnableHealthSharing", true, "When a health pack is used to heal a player, heal all other players by the same amount."); EnableDeathLiability = config.Bind("Damage", "EnableDeathLiability", true, "When a player dies, all other players take damage as a penalty."); DeathDamagePercent = config.Bind("Damage", "DeathDamagePercent", 10, new ConfigDescription("Percentage of max health damage survivors take on team member death (0-100). 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); ExcludeSafeZones = config.Bind("Damage", "ExcludeSafeZones", true, "Do not deal liability damage to players inside the truck or extraction point rooms."); EnableBatterySharing = config.Bind("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 _pendingSync = new HashSet(); 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)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 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> GetDictionaryOfDictionaries(this StatsManager manager) { return (Dictionary>)AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries").GetValue(manager); } public static Dictionary GetRunStats(this StatsManager manager) { return (Dictionary)AccessTools.Field(typeof(StatsManager), "runStats").GetValue(manager); } public static Dictionary GetItemsPurchased(this StatsManager manager) { return (Dictionary)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 GetCrystals(this ChargingStation station) { return (List)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 { 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 _vanillaUpgrades = new HashSet(); private readonly HashSet _moddedUpgrades = new HashSet(); private static readonly RegistryService _instance = new RegistryService(); public static RegistryService Instance => _instance; public IReadOnlyCollection VanillaUpgrades => _vanillaUpgrades; public IReadOnlyCollection 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 GetModdedUpgradeKeys() { HashSet hashSet = new HashSet(); 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 SnapshotPlayerStats(string steamID) { if (string.IsNullOrEmpty(steamID) || (Object)(object)StatsManager.instance == (Object)null) { return new Dictionary(); } return (from kvp in StatsManager.instance.GetDictionaryOfDictionaries() where RegistryService.Instance.IsRegistered(kvp.Key) select kvp).ToDictionary((KeyValuePair> kvp) => kvp.Key, (KeyValuePair> kvp) => kvp.Value.GetValueOrDefault(steamID, 0)); } public static Dictionary SnapshotTeamMaxLevels(string? excludeSteamID = null) { Dictionary dictionary = new Dictionary(); if ((Object)(object)StatsManager.instance == (Object)null) { return dictionary; } foreach (KeyValuePair> item in from k in StatsManager.instance.GetDictionaryOfDictionaries() where RegistryService.Instance.IsRegistered(k.Key) select k) { IEnumerable 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(); 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 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 teamSnapshot) { if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null) { yield break; } PhotonView punView = ((Component)PunManager.instance).GetComponent(); 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 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 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 SavedBatteryState = new Dictionary(); private static readonly Dictionary BatteryDebt = new Dictionary(); 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(); } 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(); 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(); } private static int GetMaxCrystals() { if ((Object)(object)ChargingStation.instance == (Object)null) { return 10; } return Traverse.Create((object)ChargingStation.instance).Field("maxCrystals").GetValue(); } 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 value2 = Traverse.Create((object)__instance).Field("maxCrystals").GetValue(); 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 MarkedBySolidarityUntil = new Dictionary(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(); 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(); 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 list = new List(); foreach (KeyValuePair 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 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(); val.AddComponent(); 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> 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 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)); } } } }