using System; using System.Collections; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Zichen-HealthExchange")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+5431d17fbaba3009862f5717951e046f72b02bf3")] [assembly: AssemblyProduct("Zichen-HealthExchange")] [assembly: AssemblyTitle("Zichen-HealthExchange")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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; } } } [BepInPlugin("zichen.healthexchange", "A.Health Exchange", "1.0.0")] public sealed class WzcHealthExchangePlugin : BaseUnityPlugin { public const string PluginGuid = "zichen.healthexchange"; public const string PluginName = "A.Health Exchange"; public const string PluginVersion = "1.0.0"; private const string InfoSection = "模组信息"; private const string ExchangeSection = "A.血量交换"; private const string ModeDrain = "吸取队友血量"; private const string ModeBalance = "平均分血量"; private const string ModeOfficial = "传输队友血量"; private const string ModeDisabled = "禁用传输"; private static readonly FieldInfo PlayerHealthGrabStaticGrabObjectField = typeof(PlayerHealthGrab).GetField("staticGrabObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerHealthGrabPhysColliderField = typeof(PlayerHealthGrab).GetField("physCollider", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerHealthGrabColliderActiveField = typeof(PlayerHealthGrab).GetField("colliderActive", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerHealthGrabGrabbingTimerField = typeof(PlayerHealthGrab).GetField("grabbingTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerHealthGrabHideLerpField = typeof(PlayerHealthGrab).GetField("hideLerp", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo StaticGrabObjectPlayerGrabbingField = typeof(StaticGrabObject).GetField("playerGrabbing", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerHealthHealthField = typeof(PlayerHealth).GetField("health", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerHealthMaxHealthField = typeof(PlayerHealth).GetField("maxHealth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarIsTumblingField = typeof(PlayerAvatar).GetField("isTumbling", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarIsDisabledField = typeof(PlayerAvatar).GetField("isDisabled", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RunManagerLevelIsShopField = typeof(RunManager).GetField("levelIsShop", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private Harmony harmony; private ConfigEntry moduleNameInfo; private ConfigEntry moduleVersionInfo; private ConfigEntry contactInfo; private ConfigEntry featureEnabled; private ConfigEntry exchangeMode; private ConfigEntry transferRateMultiplier; public static WzcHealthExchangePlugin Instance { get; private set; } private void Awake() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown Instance = this; BindConfig(); harmony = new Harmony("zichen.healthexchange.patch"); harmony.Patch((MethodBase)AccessTools.Method(typeof(PlayerHealthGrab), "Update", (Type[])null, (Type[])null), new HarmonyMethod(typeof(WzcHealthExchangePlugin), "PlayerHealthGrabUpdatePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"zichen-health-exchange loaded."); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; if (Instance == this) { Instance = null; } } private void BindConfig() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Expected O, but got Unknown //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Expected O, but got Unknown moduleNameInfo = ((BaseUnityPlugin)this).Config.Bind("模组信息", "模组名称", "Zichen_血量交换", new ConfigDescription("当前模组的中文名称。此处仅用于显示,不影响功能。", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 1000, CustomDrawer = DrawInfo, ReadOnly = true } })); moduleNameInfo.Value = "Health Exchange / 血量交换"; moduleVersionInfo = ((BaseUnityPlugin)this).Config.Bind("模组信息", "模组版本号", "1.0.0", new ConfigDescription("当前模组版本号。此处仅用于显示,不影响功能。", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 990, CustomDrawer = DrawInfo, ReadOnly = true } })); moduleVersionInfo.Value = "1.0.0"; contactInfo = ((BaseUnityPlugin)this).Config.Bind("模组信息", "REPO交流QQ群", "824639225", new ConfigDescription("REPO 游戏交流、BUG 反馈、优化建议、功能请求请加 QQ 群。此处仅用于显示,不影响功能。", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 980, CustomDrawer = DrawInfo, ReadOnly = true } })); contactInfo.Value = "824639225"; featureEnabled = ((BaseUnityPlugin)this).Config.Bind("A.血量交换", "启用", true, ConfigDescriptionWithOrder("只需要主机安装即可生效。用于修改抓住队友后的血量交换逻辑。", 900)); exchangeMode = ((BaseUnityPlugin)this).Config.Bind("A.血量交换", "血量交换模式", "吸取队友血量", new ConfigDescription("选择抓住队友后使用哪种血量交换方式。", (AcceptableValueBase)(object)new AcceptableValueList(new string[4] { "吸取队友血量", "平均分血量", "传输队友血量", "禁用传输" }), new object[1] { new ConfigurationManagerAttributes { Order = 890 } })); transferRateMultiplier = ((BaseUnityPlugin)this).Config.Bind("A.血量交换", "血量传输倍率", "1", new ConfigDescription("按官方单次 10 血量为 1 倍进行放大。例如 5 倍时,单次最多传输 50 血量,实际仍以 10 为单位传输。只能选择 1 到 10 倍。", (AcceptableValueBase)(object)new AcceptableValueList(new string[10] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }), new object[1] { new ConfigurationManagerAttributes { Order = 880 } })); } private void DrawInfo(ConfigEntryBase entry) { GUILayout.Label(entry.BoxedValue?.ToString() ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) }); } private static ConfigDescription ConfigDescriptionWithOrder(string description, int order) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown return new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = order } }); } private bool IsFeatureEnabled() { if (featureEnabled != null) { return featureEnabled.Value; } return false; } private int GetConfiguredTransferAmount() { int result = 1; if (transferRateMultiplier != null && !string.IsNullOrEmpty(transferRateMultiplier.Value)) { int.TryParse(transferRateMultiplier.Value, out result); } return Mathf.Clamp(result, 1, 10) * 10; } private string GetMode() { if (exchangeMode != null) { return exchangeMode.Value; } return "传输队友血量"; } private static bool PlayerHealthGrabUpdatePrefix(PlayerHealthGrab __instance) { WzcHealthExchangePlugin instance = Instance; if ((Object)(object)instance == (Object)null || (Object)(object)__instance == (Object)null) { return true; } if (!instance.IsFeatureEnabled()) { return true; } instance.RunCustomHealthGrabUpdate(__instance); return false; } private void RunCustomHealthGrabUpdate(PlayerHealthGrab instance) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)instance.playerAvatar == (Object)null) { return; } float num = GetFieldValue(PlayerHealthGrabHideLerpField, instance, 0f); bool fieldValue = GetFieldValue(PlayerAvatarIsTumblingField, instance.playerAvatar, fallback: false); bool flag = (Object)(object)RunManager.instance != (Object)null && GetFieldValue(RunManagerLevelIsShopField, RunManager.instance, fallback: false); if (fieldValue || SemiFunc.RunIsShop() || flag || SemiFunc.RunIsArena()) { if (num < 1f) { num += Time.deltaTime * 5f; num = Mathf.Clamp(num, 0f, 1f); instance.hideTransform.localScale = new Vector3(1f, instance.hideCurve.Evaluate(num), 1f); if (num >= 1f) { ((Component)instance.hideTransform).gameObject.SetActive(false); } SetFieldValue(PlayerHealthGrabHideLerpField, instance, num); } } else if (num > 0f) { if (!((Component)instance.hideTransform).gameObject.activeSelf) { ((Component)instance.hideTransform).gameObject.SetActive(true); } num -= Time.deltaTime * 2f; num = Mathf.Clamp(num, 0f, 1f); instance.hideTransform.localScale = new Vector3(1f, instance.hideCurve.Evaluate(num), 1f); SetFieldValue(PlayerHealthGrabHideLerpField, instance, num); } bool flag2 = !GetFieldValue(PlayerAvatarIsDisabledField, instance.playerAvatar, fallback: false) && num <= 0f; bool flag3 = GetFieldValue(PlayerHealthGrabColliderActiveField, instance, fallback: true); if (flag3 != flag2) { flag3 = flag2; SetFieldValue(PlayerHealthGrabColliderActiveField, instance, flag3); object? obj = PlayerHealthGrabPhysColliderField?.GetValue(instance); Collider val = (Collider)((obj is Collider) ? obj : null); if ((Object)(object)val != (Object)null) { val.enabled = flag3; } } ((Component)instance).transform.position = instance.followTransform.position; ((Component)instance).transform.rotation = instance.followTransform.rotation; if (!flag3 || (GameManager.Multiplayer() && !PhotonNetwork.IsMasterClient)) { return; } object? obj2 = PlayerHealthGrabStaticGrabObjectField?.GetValue(instance); StaticGrabObject val2 = (StaticGrabObject)((obj2 is StaticGrabObject) ? obj2 : null); if ((Object)(object)val2 == (Object)null) { return; } if (!(StaticGrabObjectPlayerGrabbingField?.GetValue(val2) is IList list) || list.Count == 0) { SetFieldValue(PlayerHealthGrabGrabbingTimerField, instance, 0f); return; } float num2 = GetFieldValue(PlayerHealthGrabGrabbingTimerField, instance, 0f) + Time.deltaTime; if (num2 >= 1f) { for (int i = 0; i < list.Count; i++) { object? obj3 = list[i]; PhysGrabber val3 = (PhysGrabber)((obj3 is PhysGrabber) ? obj3 : null); if (!((Object)(object)val3?.playerAvatar == (Object)null)) { ProcessExchange(instance.playerAvatar, val3.playerAvatar); } } num2 = 0f; } SetFieldValue(PlayerHealthGrabGrabbingTimerField, instance, num2); } private void ProcessExchange(PlayerAvatar targetAvatar, PlayerAvatar sourceAvatar) { if ((Object)(object)targetAvatar == (Object)null || (Object)(object)sourceAvatar == (Object)null || (Object)(object)targetAvatar.playerHealth == (Object)null || (Object)(object)sourceAvatar.playerHealth == (Object)null) { return; } int configuredTransferAmount = GetConfiguredTransferAmount(); string mode = GetMode(); if (string.Equals(mode, "禁用传输", StringComparison.Ordinal)) { return; } if (string.Equals(mode, "吸取队友血量", StringComparison.Ordinal)) { TransferHealth(targetAvatar, sourceAvatar, configuredTransferAmount, notifyDonor: false); } else if (string.Equals(mode, "平均分血量", StringComparison.Ordinal)) { int health = GetHealth(targetAvatar.playerHealth); int health2 = GetHealth(sourceAvatar.playerHealth); int num = NormalizeHealthForBalance(health); int num2 = NormalizeHealthForBalance(health2); if (num == num2) { return; } int balanceTransferAmount = GetBalanceTransferAmount(Mathf.Abs(num2 - num), configuredTransferAmount); if (balanceTransferAmount >= 10) { if (num2 > num) { TransferHealth(sourceAvatar, targetAvatar, balanceTransferAmount, notifyDonor: true); } else if (num > num2) { TransferHealth(targetAvatar, sourceAvatar, balanceTransferAmount, notifyDonor: false); } } } else { TransferHealth(sourceAvatar, targetAvatar, configuredTransferAmount, notifyDonor: true); } } private static void TransferHealth(PlayerAvatar donorAvatar, PlayerAvatar receiverAvatar, int desiredAmount, bool notifyDonor) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)donorAvatar == (Object)null || (Object)(object)receiverAvatar == (Object)null || (Object)(object)donorAvatar.playerHealth == (Object)null || (Object)(object)receiverAvatar.playerHealth == (Object)null) { return; } int health = GetHealth(donorAvatar.playerHealth); int health2 = GetHealth(receiverAvatar.playerHealth); int maxHealth = GetMaxHealth(receiverAvatar.playerHealth); if (health <= 1 || health2 >= maxHealth) { return; } int num = NormalizeTransferAmount(Math.Min(desiredAmount, Math.Min(maxHealth - health2, health - 1))); if (num >= 10) { receiverAvatar.playerHealth.HealOther(num, true); donorAvatar.playerHealth.HurtOther(num, Vector3.zero, false, -1, false); if (notifyDonor) { donorAvatar.HealedOther(); } } } private static int GetHealth(PlayerHealth playerHealth) { return GetFieldValue(PlayerHealthHealthField, playerHealth, 0); } private static int GetMaxHealth(PlayerHealth playerHealth) { return GetFieldValue(PlayerHealthMaxHealthField, playerHealth, 100); } private static int GetBalanceTransferAmount(int healthDifference, int maxTransferAmount) { int val = healthDifference / 2; return NormalizeTransferAmount(Math.Min(maxTransferAmount, val)); } private static int NormalizeTransferAmount(int amount) { if (amount < 10) { return 0; } return amount / 10 * 10; } private static int NormalizeHealthForBalance(int health) { if (health < 10) { return health; } return health / 10 * 10; } private static T GetFieldValue(FieldInfo field, object instance, T fallback) { try { if (field != null) { object value = field.GetValue(instance); if (value is T) { return (T)value; } } } catch { } return fallback; } private static void SetFieldValue(FieldInfo field, object instance, T value) { try { field?.SetValue(instance, value); } catch { } } } internal sealed class ConfigurationManagerAttributes { public bool? ShowRangeAsPercent; public Action CustomDrawer; public bool? Browsable; public string Category; public object DefaultValue; public bool? HideDefaultButton; public bool? HideSettingName; public string Description; public string DispName; public int? Order; public bool? ReadOnly; public bool? IsAdvanced; }