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 HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using YoneRai12.HostSkillManager.Models; using YoneRai12.HostSkillManager.Patches; using YoneRai12.HostSkillManager.Services; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("YoneRai12.HostSkillManager")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("YoneRai12.HostSkillManager")] [assembly: AssemblyTitle("YoneRai12.HostSkillManager")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.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 YoneRai12.HostSkillManager { [BepInPlugin("YoneRai12.HostSkillManager", "Host Skill Manager", "1.0.0")] public sealed class HostSkillManager : BaseUnityPlugin { internal const string PluginGuid = "YoneRai12.HostSkillManager"; internal const string PluginName = "Host Skill Manager"; internal const string PluginVersion = "1.0.0"; internal const string UpgradePrefix = "playerUpgrade"; internal static readonly HashSet VanillaUpgradeKeys = new HashSet(StringComparer.Ordinal) { "playerUpgradeCrouchRest", "playerUpgradeDeathHeadBattery", "playerUpgradeExtraJump", "playerUpgradeHealth", "playerUpgradeLaunch", "playerUpgradeMapPlayerCount", "playerUpgradeRange", "playerUpgradeSpeed", "playerUpgradeStamina", "playerUpgradeStrength", "playerUpgradeThrow", "playerUpgradeTumbleClimb", "playerUpgradeTumbleWings" }; private readonly Dictionary skillPolicies = new Dictionary(StringComparer.Ordinal); private readonly Dictionary playerPolicies = new Dictionary(StringComparer.Ordinal); private readonly HashSet seenPlayers = new HashSet(StringComparer.Ordinal); private readonly Random random = new Random(); private Harmony harmony; private ConfigEntry managerEnabled; private ConfigEntry purchaseSharingEnabled; private ConfigEntry syncNewPlayers; private ConfigEntry manageModdedUpgrades; private ConfigEntry realtimeConfigApply; private ConfigEntry verboseLogging; private int applicationDepth; private bool applyRequested = true; private bool repoLibPatched; private float nextScanTime; private float nextEnforcementTime; internal static HostSkillManager Instance { get; private set; } = null; internal bool IsApplyingPolicy => applicationDepth > 0; private void Awake() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown Instance = this; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; BindGeneralConfig(); foreach (string item in VanillaUpgradeKeys.OrderBy(SkillDisplayName)) { BindSkillPolicy(item); } harmony = new Harmony("YoneRai12.HostSkillManager"); harmony.PatchAll(Assembly.GetExecutingAssembly()); ((Component)this).gameObject.AddComponent(); TryPatchRepoLib(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Host Skill Manager v1.0.0 loaded. This manager acts only on the host."); } private void Update() { if (Time.unscaledTime < nextScanTime) { return; } nextScanTime = Time.unscaledTime + 0.5f; TryPatchRepoLib(); if (!managerEnabled.Value || !SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null) { return; } foreach (string item in UpgradeBridge.DiscoverUpgradeKeys()) { if (UpgradeBridge.IsVanilla(item) || manageModdedUpgrades.Value) { BindSkillPolicy(item); } } List currentPlayers = GetCurrentPlayers(); foreach (PlayerAvatar item2 in currentPlayers) { if (!string.IsNullOrWhiteSpace(item2.steamID)) { bool num = seenPlayers.Add(item2.steamID); BindPlayerPolicy(item2.steamID, item2.playerName); if (num) { ApplyPlayerPolicy(item2, isNewPlayer: true, currentPlayers); } } } if (applyRequested && realtimeConfigApply.Value) { applyRequested = false; ApplyAllFixedTargets(currentPlayers); } if (Time.unscaledTime >= nextEnforcementTime) { nextEnforcementTime = Time.unscaledTime + 3f; ApplyAllFixedTargets(currentPlayers); } } internal PurchaseSnapshot? CaptureVanillaPurchase(ItemUpgrade upgrade) { if (!CanDistributePurchase()) { return null; } ItemToggle itemToggle = upgrade.itemToggle; if (itemToggle == null || !itemToggle.toggleState) { return null; } PlayerAvatar val = SemiFunc.PlayerAvatarGetFromPhotonID(itemToggle.playerTogglePhotonID); if (!Object.op_Implicit((Object)(object)val) || string.IsNullOrWhiteSpace(val.steamID)) { return null; } return new PurchaseSnapshot(val.steamID, itemToggle.playerTogglePhotonID, UpgradeBridge.SnapshotVanilla(val.steamID)); } internal void FinishVanillaPurchase(PurchaseSnapshot snapshot) { if (!CanDistributePurchase()) { return; } foreach (string vanillaUpgradeKey in VanillaUpgradeKeys) { int value; int num = (snapshot.LevelsBefore.TryGetValue(vanillaUpgradeKey, out value) ? value : 0); int num2 = UpgradeBridge.ReadLevel(vanillaUpgradeKey, snapshot.SteamId); if (num2 > num) { HandlePurchasedIncrease(snapshot.SteamId, vanillaUpgradeKey, num2 - num); } } } internal void HandlePurchasedIncrease(string buyerSteamId, string upgradeKey, int purchasedLevels) { if (purchasedLevels <= 0 || !CanDistributePurchase() || (!UpgradeBridge.IsVanilla(upgradeKey) && !manageModdedUpgrades.Value)) { return; } SkillPolicy skillPolicy = BindSkillPolicy(upgradeKey); if (!skillPolicy.SharePurchases.Value || skillPolicy.ShareAmount.Value <= 0) { return; } List currentPlayers = GetCurrentPlayers(); int num = 0; foreach (PlayerAvatar item in currentPlayers) { if (!Object.op_Implicit((Object)(object)item) || string.IsNullOrWhiteSpace(item.steamID) || item.steamID == buyerSteamId) { continue; } BindPlayerPolicy(item.steamID, item.playerName); if (GetFixedTarget(item.steamID, skillPolicy).HasValue || (skillPolicy.ShareChance.Value < 100 && random.Next(0, 100) >= skillPolicy.ShareChance.Value)) { continue; } int num2 = UpgradeBridge.ReadLevel(upgradeKey, item.steamID); int num3 = Math.Min(100, num2 + purchasedLevels * skillPolicy.ShareAmount.Value); if (num3 == num2) { continue; } applicationDepth++; try { if (UpgradeBridge.SetExactLevel(upgradeKey, item.steamID, num3)) { num++; } } finally { applicationDepth--; } } LogDebug($"共有: {SkillDisplayName(upgradeKey)} +{purchasedLevels}, 配布先 {num}人"); } internal void ResetRoomState() { seenPlayers.Clear(); applyRequested = true; } internal void LogWarning(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); } private bool CanDistributePurchase() { if (managerEnabled.Value && purchaseSharingEnabled.Value && !IsApplyingPolicy && SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)StatsManager.instance != (Object)null) { return (Object)(object)PunManager.instance != (Object)null; } return false; } private void BindGeneralConfig() { managerEnabled = Bind("00 全般", "管理MODを有効", defaultValue: true, "ホストのスキル共有・人物別管理を有効にします。クライアントにはこのMODは不要です。"); purchaseSharingEnabled = Bind("00 全般", "購入スキルを共有", defaultValue: true, "誰かが購入したスキルを他のメンバーへ配布します。各スキルの項目で量と確率を変更できます。"); syncNewPlayers = Bind("00 全般", "新規参加者をチーム最大値へ同期", defaultValue: true, "途中参加者に、固定値が指定されていないスキルのチーム内最大レベルを適用します。"); manageModdedUpgrades = Bind("00 全般", "追加MODスキルも管理", defaultValue: true, "REPOLibに登録されたSelfReviveなどの追加スキルも同じ画面へ自動追加します。"); realtimeConfigApply = Bind("00 全般", "設定変更をリアルタイム反映", defaultValue: true, "REPOConfigで変更した全体・人物別の固定レベルをプレイ中すぐ反映します。"); verboseLogging = Bind("00 全般", "詳細ログ", defaultValue: false, "共有・固定値の処理内容をBepInExログへ詳しく記録します。"); Watch(managerEnabled); Watch(purchaseSharingEnabled); Watch(syncNewPlayers); Watch(manageModdedUpgrades); Watch(realtimeConfigApply); } private SkillPolicy BindSkillPolicy(string upgradeKey) { if (skillPolicies.TryGetValue(upgradeKey, out SkillPolicy value)) { return value; } string text = SkillDisplayName(upgradeKey); string section = (UpgradeBridge.IsVanilla(upgradeKey) ? "10 標準スキル" : "20 追加スキル") + " - " + text; SkillPolicy skillPolicy = new SkillPolicy(upgradeKey, Bind(section, "01 購入共有", defaultValue: true, text + "を購入した時、他のメンバーへ共有します。"), Bind(section, "02 1レベル購入時の配布量", 1, "購入者が1レベル得た時に他の各メンバーへ渡す" + text + "の量です。0で配布なし。", 0, 20), Bind(section, "03 共有確率パーセント", 100, "各メンバーへ配布する確率です。100で必ず共有します。", 0, 100), Bind(section, "04 全員を目標レベルへ固定", defaultValue: false, "ONにすると購入共有より下の目標レベルを優先し、全員を同じ値へ合わせます。"), Bind(section, "05 全員の目標レベル", 0, "固定をONにした時の" + text + "レベルです。0なら削除、最大100。", 0, 100)); Watch(skillPolicy.SharePurchases); Watch(skillPolicy.ShareAmount); Watch(skillPolicy.ShareChance); Watch(skillPolicy.ForceTeamTarget); Watch(skillPolicy.TeamTarget); skillPolicies.Add(upgradeKey, skillPolicy); foreach (PlayerPolicy value2 in playerPolicies.Values) { BindPlayerSkillTarget(value2, skillPolicy); } LogDebug("設定へスキルを追加: " + upgradeKey); return skillPolicy; } private PlayerPolicy BindPlayerPolicy(string steamId, string playerName) { if (playerPolicies.TryGetValue(steamId, out PlayerPolicy value)) { if (!string.IsNullOrWhiteSpace(playerName) && value.DisplayName.Value != playerName) { value.DisplayName.Value = playerName; } { foreach (SkillPolicy value2 in skillPolicies.Values) { BindPlayerSkillTarget(value, value2); } return value; } } string section = "30 人物別 - " + steamId; ConfigEntry displayName = Bind(section, "00 表示名", playerName ?? string.Empty, "このSteam IDで最後に確認したプレイヤー名です。識別用です。"); ConfigEntry val = Bind(section, "01 この人物の個別設定を有効", defaultValue: true, "各スキルの値が-1以外の時、その人物だけに個別目標を適用します。"); PlayerPolicy playerPolicy = new PlayerPolicy(steamId, displayName, val); playerPolicies.Add(steamId, playerPolicy); Watch(val); foreach (SkillPolicy value3 in skillPolicies.Values) { BindPlayerSkillTarget(playerPolicy, value3); } LogDebug("設定へ人物を追加: " + playerName + " (" + steamId + ")"); return playerPolicy; } private void BindPlayerSkillTarget(PlayerPolicy player, SkillPolicy skill) { if (!player.Targets.ContainsKey(skill.Key)) { string section = "30 人物別 - " + player.SteamId; ConfigEntry val = Bind(section, SkillDisplayName(skill.Key) + " 目標レベル", -1, "-1は個別指定なし(全体設定を使用)、0はその人物から削除、1以上はその人物だけの目標レベルです。", -1, 100); player.Targets.Add(skill.Key, val); Watch(val); } } private void ApplyAllFixedTargets(IReadOnlyList players) { if (!managerEnabled.Value || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } foreach (PlayerAvatar player in players) { ApplyPlayerPolicy(player, isNewPlayer: false, players); } } private void ApplyPlayerPolicy(PlayerAvatar player, bool isNewPlayer, IReadOnlyList players) { if (!Object.op_Implicit((Object)(object)player) || string.IsNullOrWhiteSpace(player.steamID)) { return; } BindPlayerPolicy(player.steamID, player.playerName); foreach (SkillPolicy value in skillPolicies.Values) { if (!UpgradeBridge.IsVanilla(value.Key) && !manageModdedUpgrades.Value) { continue; } int? num = GetFixedTarget(player.steamID, value); if (!num.HasValue && isNewPlayer && syncNewPlayers.Value && value.SharePurchases.Value) { int num2 = 0; foreach (PlayerAvatar player2 in players) { if (Object.op_Implicit((Object)(object)player2) && !string.IsNullOrWhiteSpace(player2.steamID)) { num2 = Math.Max(num2, UpgradeBridge.ReadLevel(value.Key, player2.steamID)); } } if (num2 > UpgradeBridge.ReadLevel(value.Key, player.steamID)) { num = num2; } } if (!num.HasValue) { continue; } applicationDepth++; try { if (!UpgradeBridge.SetExactLevel(value.Key, player.steamID, num.Value)) { LogDebug($"適用待ち: {player.playerName} / {SkillDisplayName(value.Key)} = {num.Value}"); } } finally { applicationDepth--; } } } private int? GetFixedTarget(string steamId, SkillPolicy skill) { if (playerPolicies.TryGetValue(steamId, out PlayerPolicy value) && value.Enabled.Value && value.Targets.TryGetValue(skill.Key, out ConfigEntry value2) && value2.Value >= 0) { return value2.Value; } if (!skill.ForceTeamTarget.Value) { return null; } return skill.TeamTarget.Value; } private List GetCurrentPlayers() { try { return (from player in SemiFunc.PlayerGetAll() where (Object)(object)player != (Object)null && !string.IsNullOrWhiteSpace(player.steamID) select player).ToList(); } catch { return new List(); } } private void TryPatchRepoLib() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_0061: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_009b: Expected O, but got Unknown if (!repoLibPatched) { MethodInfo setLevelMethod = UpgradeBridge.SetLevelMethod; MethodInfo applyUpgradeMethod = UpgradeBridge.ApplyUpgradeMethod; if (!(setLevelMethod == null) && !(applyUpgradeMethod == null)) { harmony.Patch((MethodBase)setLevelMethod, new HarmonyMethod(typeof(RepoLibUpgradeHooks), "SetLevelPrefix", (Type[])null), new HarmonyMethod(typeof(RepoLibUpgradeHooks), "SetLevelPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)applyUpgradeMethod, new HarmonyMethod(typeof(RepoLibUpgradeHooks), "ApplyUpgradePrefix", (Type[])null), new HarmonyMethod(typeof(RepoLibUpgradeHooks), "ApplyUpgradePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); repoLibPatched = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"REPOLib upgrade synchronization detected and connected."); } } } private ConfigEntry Bind(string section, string key, T defaultValue, string description) { return ((BaseUnityPlugin)this).Config.Bind(section, key, defaultValue, description); } private ConfigEntry Bind(string section, string key, int defaultValue, string description, int minimum, int maximum) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown return ((BaseUnityPlugin)this).Config.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(minimum, maximum), Array.Empty())); } private void Watch(ConfigEntry entry) { entry.SettingChanged += delegate { applyRequested = true; }; } private void LogDebug(string message) { if (verboseLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } } internal static string SkillDisplayName(string upgradeKey) { string text; if (!upgradeKey.StartsWith("playerUpgrade", StringComparison.Ordinal)) { text = upgradeKey; } else { string text2 = upgradeKey; int length = "playerUpgrade".Length; text = text2.Substring(length, text2.Length - length); } string text3 = text; return text3 switch { "CrouchRest" => "しゃがみ回復", "DeathHeadBattery" => "デスヘッド電池", "ExtraJump" => "追加ジャンプ", "Health" => "体力", "Launch" => "タンブル射出", "MapPlayerCount" => "マップ表示人数", "Range" => "掴み距離", "Speed" => "移動速度", "Stamina" => "スタミナ", "Strength" => "掴み力", "Throw" => "投擲力", "TumbleClimb" => "タンブル登攀", "TumbleWings" => "タンブル飛行", "SelfRevive" => "自己蘇生", _ => text3, }; } } internal sealed class HostSkillNetworkCallbacks : MonoBehaviourPunCallbacks { public override void OnJoinedRoom() { HostSkillManager.Instance?.ResetRoomState(); } public override void OnLeftRoom() { HostSkillManager.Instance?.ResetRoomState(); } public override void OnMasterClientSwitched(Player newMasterClient) { HostSkillManager.Instance?.ResetRoomState(); } } } namespace YoneRai12.HostSkillManager.Services { internal static class UpgradeBridge { private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static Type? playerUpgradeType; private static FieldInfo? upgradeIdField; private static FieldInfo? playerDictionaryField; private static FieldInfo? playerUpgradesField; private static PropertyInfo? playerUpgradesProperty; private static MethodInfo? setLevelMethod; private static MethodInfo? applyUpgradeMethod; internal static MethodInfo? SetLevelMethod { get { ResolveRepoLib(); return setLevelMethod; } } internal static MethodInfo? ApplyUpgradeMethod { get { ResolveRepoLib(); return applyUpgradeMethod; } } internal static bool IsVanilla(string upgradeKey) { return HostSkillManager.VanillaUpgradeKeys.Contains(upgradeKey); } internal static HashSet DiscoverUpgradeKeys() { HashSet hashSet = new HashSet(HostSkillManager.VanillaUpgradeKeys, StringComparer.Ordinal); if (StatsManager.instance?.dictionaryOfDictionaries != null) { foreach (string key in StatsManager.instance.dictionaryOfDictionaries.Keys) { if (key.StartsWith("playerUpgrade", StringComparison.Ordinal)) { hashSet.Add(key); } } } ResolveRepoLib(); foreach (string item in EnumerateRepoUpgradeIds()) { hashSet.Add("playerUpgrade" + item); } return hashSet; } internal static int ReadLevel(string upgradeKey, string steamId) { if (!IsVanilla(upgradeKey) && TryGetRepoUpgrade(upgradeKey, out object playerUpgrade)) { return ReadRepoLevel(playerUpgrade, steamId); } if (StatsManager.instance?.dictionaryOfDictionaries != null && StatsManager.instance.dictionaryOfDictionaries.TryGetValue(upgradeKey, out var value) && value.TryGetValue(steamId, out var value2)) { return value2; } return 0; } internal static Dictionary SnapshotVanilla(string steamId) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (string vanillaUpgradeKey in HostSkillManager.VanillaUpgradeKeys) { dictionary[vanillaUpgradeKey] = ReadLevel(vanillaUpgradeKey, steamId); } return dictionary; } internal static bool SetExactLevel(string upgradeKey, string steamId, int requestedLevel) { int num = Math.Max(0, Math.Min(100, requestedLevel)); int num2 = ReadLevel(upgradeKey, steamId); if (num2 == num) { return true; } if (IsVanilla(upgradeKey)) { if ((Object)(object)PunManager.instance == (Object)null) { return false; } PhotonView component = ((Component)PunManager.instance).GetComponent(); if ((Object)(object)component == (Object)null) { return false; } object[] obj = new object[3] { steamId, null, null }; int length = "playerUpgrade".Length; obj[1] = upgradeKey.Substring(length, upgradeKey.Length - length); obj[2] = num - num2; component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, obj); return true; } if (!TryGetRepoUpgrade(upgradeKey, out object playerUpgrade) || setLevelMethod == null) { return false; } try { setLevelMethod.Invoke(playerUpgrade, new object[2] { steamId, num }); return true; } catch (Exception ex) { Exception ex2 = ((ex is TargetInvocationException ex3 && ex.InnerException != null) ? ex3.InnerException : ex); HostSkillManager.Instance.LogWarning("REPOLib SetLevel failed: " + upgradeKey + ", " + steamId + ", " + ex2.GetType().Name + ": " + ex2.Message); return false; } } internal static bool TryGetUpgradeKey(object playerUpgrade, out string upgradeKey) { upgradeKey = string.Empty; ResolveRepoLib(); if (!(upgradeIdField?.GetValue(playerUpgrade) is string text) || string.IsNullOrWhiteSpace(text)) { return false; } upgradeKey = "playerUpgrade" + text; return true; } internal static int ReadRepoLevel(object playerUpgrade, string steamId) { ResolveRepoLib(); 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; } private static void ResolveRepoLib() { if (!(playerUpgradeType != null) || !(setLevelMethod != null)) { Type type = AccessTools.TypeByName("REPOLib.Modules.Upgrades"); Type type2 = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade"); if (!(type == null) && !(type2 == null)) { playerUpgradeType = type2; upgradeIdField = playerUpgradeType.GetField("UpgradeId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); playerDictionaryField = playerUpgradeType.GetField("PlayerDictionary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); playerUpgradesField = type.GetField("_playerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); playerUpgradesProperty = type.GetProperty("PlayerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); setLevelMethod = playerUpgradeType.GetMethod("SetLevel", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(int) }, null); applyUpgradeMethod = playerUpgradeType.GetMethod("ApplyUpgrade", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(int) }, null); } } } private static IEnumerable EnumerateRepoUpgradeIds() { object obj = playerUpgradesProperty?.GetValue(null) ?? playerUpgradesField?.GetValue(null); if (obj is IDictionary dictionary) { foreach (DictionaryEntry item in dictionary) { if (item.Key is string text && !string.IsNullOrWhiteSpace(text)) { yield return text; } } } else { if (!(obj is IEnumerable enumerable) || upgradeIdField == null) { yield break; } foreach (object item2 in enumerable) { if (item2 != null && upgradeIdField.GetValue(item2) is string text2 && !string.IsNullOrWhiteSpace(text2)) { yield return text2; } } } } private static bool TryGetRepoUpgrade(string upgradeKey, out object? playerUpgrade) { playerUpgrade = null; ResolveRepoLib(); if (!upgradeKey.StartsWith("playerUpgrade", StringComparison.Ordinal)) { return false; } int length = "playerUpgrade".Length; string key = upgradeKey.Substring(length, upgradeKey.Length - length); if (!(playerUpgradesField?.GetValue(null) is IDictionary dictionary) || !dictionary.Contains(key)) { return false; } playerUpgrade = dictionary[key]; return playerUpgrade != null; } } } namespace YoneRai12.HostSkillManager.Patches { internal static class RepoLibUpgradeHooks { [ThreadStatic] private static int? stashedPreviousLevel; public static void SetLevelPrefix(object __instance, string steamId) { stashedPreviousLevel = UpgradeBridge.ReadRepoLevel(__instance, steamId); } public static void SetLevelPostfix() { stashedPreviousLevel = null; } public static void ApplyUpgradePrefix(object __instance, string steamId, out int __state) { if (stashedPreviousLevel.HasValue) { __state = stashedPreviousLevel.Value; stashedPreviousLevel = null; } else { __state = UpgradeBridge.ReadRepoLevel(__instance, steamId); } } public static void ApplyUpgradePostfix(object __instance, string steamId, int level, int __state) { if (level > __state && UpgradeBridge.TryGetUpgradeKey(__instance, out string upgradeKey)) { HostSkillManager.Instance?.HandlePurchasedIncrease(steamId, upgradeKey, level - __state); } } } [HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")] internal static class VanillaPurchasePatch { [HarmonyPrefix] private static void Prefix(ItemUpgrade __instance, out PurchaseSnapshot? __state) { __state = HostSkillManager.Instance?.CaptureVanillaPurchase(__instance); } [HarmonyPostfix] private static void Postfix(PurchaseSnapshot? __state) { if (__state != null) { HostSkillManager.Instance?.FinishVanillaPurchase(__state); } } } } namespace YoneRai12.HostSkillManager.Models { internal sealed class PlayerPolicy { internal string SteamId { get; } internal ConfigEntry DisplayName { get; } internal ConfigEntry Enabled { get; } internal Dictionary> Targets { get; } = new Dictionary>(); internal PlayerPolicy(string steamId, ConfigEntry displayName, ConfigEntry enabled) { SteamId = steamId; DisplayName = displayName; Enabled = enabled; } } internal sealed class PurchaseSnapshot { internal string SteamId { get; } internal int ViewId { get; } internal Dictionary LevelsBefore { get; } internal PurchaseSnapshot(string steamId, int viewId, Dictionary levelsBefore) { SteamId = steamId; ViewId = viewId; LevelsBefore = levelsBefore; } } internal sealed class SkillPolicy { internal string Key { get; } internal string CleanName { get { if (!Key.StartsWith("playerUpgrade")) { return Key; } string key = Key; int length = "playerUpgrade".Length; return key.Substring(length, key.Length - length); } } internal ConfigEntry SharePurchases { get; } internal ConfigEntry ShareAmount { get; } internal ConfigEntry ShareChance { get; } internal ConfigEntry ForceTeamTarget { get; } internal ConfigEntry TeamTarget { get; } internal SkillPolicy(string key, ConfigEntry sharePurchases, ConfigEntry shareAmount, ConfigEntry shareChance, ConfigEntry forceTeamTarget, ConfigEntry teamTarget) { Key = key; SharePurchases = sharePurchases; ShareAmount = shareAmount; ShareChance = shareChance; ForceTeamTarget = forceTeamTarget; TeamTarget = teamTarget; } } }