Decompiled source of HealthSiphon v1.0.2

Zichen-HealthSiphon-1.0.2.dll

Decompiled 7 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using TMPro;
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-HealthSiphon-1.0.2")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+3274c5d2a08bb59f56ece67a09765db191f3e1bc")]
[assembly: AssemblyProduct("Zichen-HealthSiphon-1.0.2")]
[assembly: AssemblyTitle("Zichen-HealthSiphon-1.0.2")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 Zichen_HealthSiphon
{
	public enum HealthSiphonMode
	{
		吸取队友血量,
		平均分血量,
		传输队友血量,
		禁用传输
	}
	[BepInPlugin("zichen.healthsiphon", "Health Siphon", "1.0.2")]
	public sealed class ZichenHealthSiphonPlugin : BaseUnityPlugin
	{
		internal sealed class CfgI18N
		{
			public string SectionCN;

			public string SectionEN;

			public string KeyCN;

			public string KeyEN;

			public string DisplayEN;

			public string DescCN;

			public string DescEN;

			public ConfigurationManagerAttributes Attrs;

			public string ValueEN;

			public string ValueCN;

			public ConfigEntry<string> ReadOnlyEntry;

			public ConfigEntryBase Entry;

			public bool ChineseOnly;
		}

		public enum DisplayLanguage
		{
			中文,
			English
		}

		internal sealed class LiveLabel
		{
			public WeakReference<TextMeshProUGUI> Tmp;

			public CfgI18N I18N;

			public bool IsSection;
		}

		internal sealed class LiveValue
		{
			public WeakReference<Object> InputField;

			public CfgI18N I18N;
		}

		internal sealed class LiveSliderOptions
		{
			public WeakReference<Object> Slider;

			public CfgI18N I18N;

			public string[] OptionsCN;

			public string[] OptionsEN;
		}

		internal sealed class LiveToggleButtons
		{
			public WeakReference<TextMeshProUGUI> LeftTmp;

			public WeakReference<TextMeshProUGUI> RightTmp;
		}

		public const string PluginGuid = "zichen.healthsiphon";

		public const string PluginName = "Health Siphon";

		public const string PluginVersion = "1.0.2";

		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;

		public ConfigEntry<DisplayLanguage> displayLanguage;

		private ConfigEntry<string> moduleNameInfo;

		private ConfigEntry<string> moduleVersionInfo;

		private ConfigEntry<bool> featureEnabled;

		private ConfigEntry<bool> lethalTransferEnabled;

		private ConfigEntry<HealthSiphonMode> siphonMode;

		private ConfigEntry<string> transferRateMultiplier;

		internal static readonly Dictionary<string, CfgI18N> _cfgByEnSection = new Dictionary<string, CfgI18N>(StringComparer.Ordinal);

		internal static readonly Dictionary<string, CfgI18N> _cfgByEnKey = new Dictionary<string, CfgI18N>(StringComparer.Ordinal);

		internal static readonly Dictionary<string, List<CfgI18N>> _cfgByKeyOnly = new Dictionary<string, List<CfgI18N>>(StringComparer.Ordinal);

		private static readonly List<CfgI18N> _cfgI18Ns = new List<CfgI18N>(64);

		private static readonly string[] _chineseModKeywords = new string[6] { "chinese", "简体", "繁體", "繁体", "汉化", "中文" };

		private static readonly string[] _chineseEnvironmentPluginGuids = new string[1] { "gravydevsupreme.xunity.autotranslator" };

		private static readonly List<LiveLabel> _liveLabels = new List<LiveLabel>(64);

		private static readonly List<LiveValue> _liveValues = new List<LiveValue>(8);

		private static readonly List<LiveSliderOptions> _liveSliderOptions = new List<LiveSliderOptions>(8);

		private static readonly List<LiveToggleButtons> _liveToggleButtons = new List<LiveToggleButtons>(8);

		private static readonly Dictionary<Type, FieldInfo> _labelTmpFieldCache = new Dictionary<Type, FieldInfo>(8);

		private static FieldInfo _inputStringSystemField;

		private static MethodInfo _inputStringSystemSetValue;

		private static readonly Dictionary<string, int> _pendingKeyMatchIndex = new Dictionary<string, int>(StringComparer.Ordinal);

		private static readonly Dictionary<string, string> _enumNameCNtoEN = new Dictionary<string, string>
		{
			["中文"] = "Chinese",
			["吸取队友血量"] = "Drain",
			["平均分血量"] = "Balance",
			["传输队友血量"] = "Transfer",
			["禁用传输"] = "Disabled"
		};

		private static readonly Dictionary<string, string> _enumNameENtoCN = new Dictionary<string, string>
		{
			["English"] = "英语",
			["Chinese"] = "中文"
		};

		public static ZichenHealthSiphonPlugin Instance { get; private set; }

		private void Awake()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			DetachFromManager();
			Instance = this;
			ResetConfigIfVersionChanged();
			BindConfig();
			harmony = new Harmony("zichen.healthsiphon.patch");
			harmony.Patch((MethodBase)AccessTools.Method(typeof(PlayerHealthGrab), "Update", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ZichenHealthSiphonPlugin), "PlayerHealthGrabUpdatePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(StaticGrabObject), "OnDisable", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ZichenHealthSiphonPlugin), "StaticGrabObjectOnDisablePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			TryHookREPOConfig(harmony);
			if (displayLanguage != null)
			{
				displayLanguage.SettingChanged += delegate
				{
					RefreshAllI18N();
				};
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"zichen-health-siphon loaded.");
		}

		private void DetachFromManager()
		{
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			harmony = null;
			if (Instance == this)
			{
				Instance = null;
			}
		}

		private void BindConfig()
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			DisplayLanguage displayLanguage = DetectDefaultLanguage();
			ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes
			{
				Order = 1100
			};
			CfgI18N cfgI18N = NewI18N("A.Mod Info", "A.模组信息", "Health Siphon Language", "语言", "Switch display language.", "切换显示语言。", configurationManagerAttributes, readOnly: false);
			this.displayLanguage = ((BaseUnityPlugin)this).Config.Bind<DisplayLanguage>("A.Mod Info", "Health Siphon Language", displayLanguage, new ConfigDescription(cfgI18N.DescEN, (AcceptableValueBase)null, new object[1] { configurationManagerAttributes }));
			cfgI18N.Entry = (ConfigEntryBase)(object)this.displayLanguage;
			moduleNameInfo = BindReadOnlyI18N("A.Mod Info", "A.模组信息", "HealthSiphonModName", "模组名称", "Health Siphon", "生命虹吸", 1000, null, "Mod Name");
			moduleVersionInfo = BindReadOnlyI18N("A.Mod Info", "A.模组信息", "HealthSiphonModVersion", "模组版本号", "1.0.2", "1.0.2", 990, null, "Mod Version");
			featureEnabled = BindI18N("B.Global Settings", "B.全局设置", "Mod Enabled", "模组启用", "Disable to turn off the whole mod.", "关闭后整个模组全部功能失效。", defaultVal: true, null, 900, null, "Mod Enabled");
			siphonMode = BindI18N("C.Health Siphon", "C.生命虹吸", "Siphon Mode", "生命虹吸模式", "Choose how health is exchanged after grabbing a teammate.", "选择抓住队友后使用哪种血量交换方式。", HealthSiphonMode.吸取队友血量, null, 800, null, "Siphon Mode");
			transferRateMultiplier = BindI18N("C.Health Siphon", "C.生命虹吸", "Transfer Rate Multiplier", "血量传输倍率", "Multiply the official single transfer amount of 10. For example, at 5x, up to 50 HP can be transferred at once. Only 1 to 10 can be selected.", "按官方单次 10 血量为 1 倍进行放大。例如 5 倍时,单次最多传输 50 血量,实际仍以 10 为单位传输。只能选择 1 到 10 倍。", "1", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[10] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }), 790, null, "Transfer Rate Multiplier");
			lethalTransferEnabled = BindI18N("C.Health Siphon", "C.生命虹吸", "Lethal Transfer", "致命传输", "If enabled, transferring or draining health can reduce player's health to 0 and cause death. If disabled, players will always keep at least 1 HP.", "启用时,传输或吸取血量可以使玩家血量降为 0 并导致死亡。禁用时,玩家最少保留 1 滴血。", defaultVal: true, null, 600, null, "Lethal Transfer");
		}

		private void ResetConfigIfVersionChanged()
		{
			try
			{
				string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
				if (File.Exists(configFilePath))
				{
					string text = ReadConfigPluginVersion(configFilePath);
					if (!(text == "1.0.2"))
					{
						ResetConfigFileToDefaults(configFilePath);
						((BaseUnityPlugin)this).Logger.LogWarning((object)(string.IsNullOrWhiteSpace(text) ? "Old config without Mod Version, reset to defaults." : ("Config version " + text + " -> 1.0.2, reset to defaults.")));
					}
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to reset config by version: " + ex.Message));
			}
		}

		private static string ReadConfigPluginVersion(string configPath)
		{
			if (!File.Exists(configPath))
			{
				return null;
			}
			string input = File.ReadAllText(configPath);
			Match match = Regex.Match(input, "(?m)^HealthSiphonModVersion\\s*=\\s*(.+?)\\s*$");
			if (!match.Success)
			{
				match = Regex.Match(input, "(?m)^Mod Version\\s*=\\s*(.+?)\\s*$");
			}
			if (!match.Success)
			{
				match = Regex.Match(input, "(?m)^模组版本号\\s*=\\s*(.+?)\\s*$");
			}
			if (!match.Success)
			{
				return null;
			}
			return match.Groups[1].Value.Trim();
		}

		private void ResetConfigFileToDefaults(string configPath)
		{
			((BaseUnityPlugin)this).Config.Clear();
			if (File.Exists(configPath))
			{
				File.Delete(configPath);
			}
			((BaseUnityPlugin)this).Config.Reload();
		}

		private bool IsFeatureEnabled()
		{
			if (featureEnabled != null)
			{
				return featureEnabled.Value;
			}
			return false;
		}

		public bool IsLethalTransferEnabled()
		{
			if (lethalTransferEnabled != null)
			{
				return lethalTransferEnabled.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 HealthSiphonMode GetMode()
		{
			if (siphonMode != null)
			{
				return siphonMode.Value;
			}
			return HealthSiphonMode.传输队友血量;
		}

		private static bool PlayerHealthGrabUpdatePrefix(PlayerHealthGrab __instance)
		{
			ZichenHealthSiphonPlugin 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 static void StaticGrabObjectOnDisablePrefix(StaticGrabObject __instance)
		{
			if ((Object)(object)__instance == (Object)null || __instance.playerGrabbing == null)
			{
				return;
			}
			try
			{
				foreach (PhysGrabber item in new List<PhysGrabber>(__instance.playerGrabbing))
				{
					if (!((Object)(object)item == (Object)null))
					{
						if (item.isLocal)
						{
							item.ReleaseObject(-1, 0.1f);
						}
						else
						{
							item.grabbed = false;
						}
					}
				}
			}
			catch (Exception arg)
			{
				ZichenHealthSiphonPlugin instance = Instance;
				if (instance != null)
				{
					ManualLogSource logger = ((BaseUnityPlugin)instance).Logger;
					if (logger != null)
					{
						logger.LogError((object)$"Error in StaticGrabObjectOnDisablePrefix: {arg}");
					}
				}
			}
		}

		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 { Count: not 0 } list)
			{
				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))
						{
							ProcessSiphon(instance.playerAvatar, val3.playerAvatar);
						}
					}
					num2 = 0f;
				}
				SetFieldValue(PlayerHealthGrabGrabbingTimerField, instance, num2);
			}
			else
			{
				SetFieldValue(PlayerHealthGrabGrabbingTimerField, instance, 0f);
			}
		}

		private void ProcessSiphon(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();
			switch (GetMode())
			{
			case HealthSiphonMode.禁用传输:
				break;
			case HealthSiphonMode.吸取队友血量:
				TransferHealth(targetAvatar, sourceAvatar, configuredTransferAmount, notifyDonor: false);
				break;
			case HealthSiphonMode.平均分血量:
			{
				int health = GetHealth(targetAvatar.playerHealth);
				int health2 = GetHealth(sourceAvatar.playerHealth);
				int num = NormalizeHealthForBalance(health);
				int num2 = NormalizeHealthForBalance(health2);
				if (num == num2)
				{
					break;
				}
				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);
					}
				}
				break;
			}
			default:
				TransferHealth(sourceAvatar, targetAvatar, configuredTransferAmount, notifyDonor: true);
				break;
			}
		}

		private static void TransferHealth(PlayerAvatar donorAvatar, PlayerAvatar receiverAvatar, int desiredAmount, bool notifyDonor)
		{
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: 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 (health2 >= maxHealth)
			{
				return;
			}
			if ((Object)(object)Instance != (Object)null && Instance.IsLethalTransferEnabled())
			{
				if (health <= 0)
				{
					return;
				}
				if (health <= 10)
				{
					int num = health;
					receiverAvatar.playerHealth.HealOther(num, true);
					donorAvatar.playerHealth.HurtOther(num, Vector3.zero, false, -1, false);
					if (notifyDonor)
					{
						donorAvatar.HealedOther();
					}
					return;
				}
				int num2 = NormalizeTransferAmount(Math.Min(desiredAmount, Math.Min(maxHealth - health2, health)));
				if (num2 >= 10)
				{
					receiverAvatar.playerHealth.HealOther(num2, true);
					donorAvatar.playerHealth.HurtOther(num2, Vector3.zero, false, -1, false);
					if (notifyDonor)
					{
						donorAvatar.HealedOther();
					}
				}
			}
			else
			{
				if (health <= 1)
				{
					return;
				}
				int num3 = NormalizeTransferAmount(Math.Min(desiredAmount, Math.Min(maxHealth - health2, health - 1)));
				if (num3 >= 10)
				{
					receiverAvatar.playerHealth.HealOther(num3, true);
					donorAvatar.playerHealth.HurtOther(num3, 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<T>(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<T>(FieldInfo field, object instance, T value)
		{
			try
			{
				field?.SetValue(instance, value);
			}
			catch
			{
			}
		}

		public static bool UseChinese()
		{
			ZichenHealthSiphonPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null || instance.displayLanguage == null)
			{
				return false;
			}
			return instance.displayLanguage.Value == DisplayLanguage.中文;
		}

		private ConfigEntry<T> BindI18N<T>(string sectEN, string sectCN, string keyEN, string keyCN, string descEN, string descCN, T defaultVal, AcceptableValueBase acceptableValues = null, int? order = null, Action<ConfigEntryBase> customDrawer = null, string displayEN = null)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes();
			if (order.HasValue)
			{
				configurationManagerAttributes.Order = order.Value;
			}
			if (customDrawer != null)
			{
				configurationManagerAttributes.CustomDrawer = customDrawer;
			}
			CfgI18N cfgI18N = NewI18N(sectEN, sectCN, keyEN, keyCN, descEN, descCN, configurationManagerAttributes, readOnly: false);
			cfgI18N.DisplayEN = displayEN;
			ApplyI18N(cfgI18N);
			return (ConfigEntry<T>)(object)(cfgI18N.Entry = (ConfigEntryBase)(object)((BaseUnityPlugin)this).Config.Bind<T>(sectEN, keyEN, defaultVal, new ConfigDescription(descEN, acceptableValues, new object[1] { configurationManagerAttributes })));
		}

		private ConfigEntry<string> BindReadOnlyI18N(string sectEN, string sectCN, string keyEN, string keyCN, string valueEN, string valueCN, int? order = null, Action<ConfigEntryBase> customDrawer = null, string displayEN = null)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes
			{
				ReadOnly = true
			};
			if (order.HasValue)
			{
				configurationManagerAttributes.Order = order.Value;
			}
			if (customDrawer != null)
			{
				configurationManagerAttributes.CustomDrawer = customDrawer;
			}
			CfgI18N cfgI18N = NewI18N(sectEN, sectCN, keyEN, keyCN, "", "", configurationManagerAttributes, readOnly: true);
			cfgI18N.ValueEN = valueEN;
			cfgI18N.ValueCN = valueCN;
			cfgI18N.DisplayEN = displayEN;
			ApplyI18N(cfgI18N);
			string text = (UseChinese() ? valueCN : valueEN);
			cfgI18N.ReadOnlyEntry = ((BaseUnityPlugin)this).Config.Bind<string>(sectEN, keyEN, text, new ConfigDescription("", (AcceptableValueBase)null, new object[1] { configurationManagerAttributes }));
			cfgI18N.Entry = (ConfigEntryBase)(object)cfgI18N.ReadOnlyEntry;
			if (cfgI18N.ReadOnlyEntry.Value != text)
			{
				cfgI18N.ReadOnlyEntry.Value = text;
			}
			return cfgI18N.ReadOnlyEntry;
		}

		private static CfgI18N NewI18N(string sectEN, string sectCN, string keyEN, string keyCN, string descEN, string descCN, ConfigurationManagerAttributes attrs, bool readOnly)
		{
			CfgI18N cfgI18N = new CfgI18N
			{
				SectionEN = sectEN,
				SectionCN = sectCN,
				KeyEN = keyEN,
				KeyCN = keyCN,
				DescEN = descEN,
				DescCN = descCN,
				Attrs = attrs
			};
			ApplyI18N(cfgI18N);
			_cfgI18Ns.Add(cfgI18N);
			_cfgByEnSection[sectEN] = cfgI18N;
			_cfgByEnKey[sectEN + "\0" + keyEN] = cfgI18N;
			if (!_cfgByKeyOnly.TryGetValue(keyEN, out var value))
			{
				value = new List<CfgI18N>(2);
				_cfgByKeyOnly[keyEN] = value;
			}
			value.Add(cfgI18N);
			if (!string.IsNullOrEmpty(keyCN))
			{
				if (!_cfgByKeyOnly.TryGetValue(keyCN, out var value2))
				{
					value2 = new List<CfgI18N>(2);
					_cfgByKeyOnly[keyCN] = value2;
				}
				if (!value2.Contains(cfgI18N))
				{
					value2.Add(cfgI18N);
				}
			}
			string text = FixNamingForLookup(keyEN);
			if (!string.Equals(text, keyEN, StringComparison.Ordinal))
			{
				if (!_cfgByKeyOnly.TryGetValue(text, out var value3))
				{
					value3 = new List<CfgI18N>(2);
					_cfgByKeyOnly[text] = value3;
				}
				if (!value3.Contains(cfgI18N))
				{
					value3.Add(cfgI18N);
				}
			}
			return cfgI18N;
		}

		private static void ApplyI18N(CfgI18N i18n)
		{
			if (i18n?.Attrs == null)
			{
				return;
			}
			bool flag = UseChinese();
			i18n.Attrs.Category = (flag ? i18n.SectionCN : i18n.SectionEN);
			i18n.Attrs.DispName = (flag ? i18n.KeyCN : (i18n.DisplayEN ?? i18n.KeyEN));
			i18n.Attrs.Description = (flag ? i18n.DescCN : i18n.DescEN);
			if (i18n.ReadOnlyEntry != null)
			{
				string text = (flag ? i18n.ValueCN : i18n.ValueEN);
				if (i18n.ReadOnlyEntry.Value != text)
				{
					i18n.ReadOnlyEntry.Value = text;
				}
			}
		}

		private static void RefreshAllI18N()
		{
			for (int i = 0; i < _cfgI18Ns.Count; i++)
			{
				ApplyI18N(_cfgI18Ns[i]);
			}
			RefreshLiveLabels();
		}

		private static string FixNamingForLookup(string input)
		{
			if (string.IsNullOrEmpty(input))
			{
				return input;
			}
			input = Regex.Replace(input, "([a-z])([A-Z])", "$1 $2");
			input = Regex.Replace(input, "([A-Z])([A-Z][a-z])", "$1 $2");
			input = Regex.Replace(input, "\\s+", " ");
			input = Regex.Replace(input, "([A-Z]\\.)\\s([A-Z]\\.)", "$1$2");
			return input.Trim();
		}

		private static DisplayLanguage DetectDefaultLanguage()
		{
			try
			{
				foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
				{
					string text = pluginInfo.Key ?? string.Empty;
					PluginInfo value = pluginInfo.Value;
					object obj;
					if (value == null)
					{
						obj = null;
					}
					else
					{
						BepInPlugin metadata = value.Metadata;
						obj = ((metadata != null) ? metadata.Name : null);
					}
					if (obj == null)
					{
						obj = string.Empty;
					}
					string text2 = (string)obj;
					string[] chineseModKeywords = _chineseModKeywords;
					foreach (string value2 in chineseModKeywords)
					{
						if (text.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0)
						{
							return DisplayLanguage.中文;
						}
					}
					chineseModKeywords = _chineseEnvironmentPluginGuids;
					foreach (string b in chineseModKeywords)
					{
						if (string.Equals(text, b, StringComparison.OrdinalIgnoreCase))
						{
							return DisplayLanguage.中文;
						}
					}
				}
			}
			catch
			{
			}
			return DisplayLanguage.English;
		}

		internal static void TryHookREPOConfig(Harmony h)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			try
			{
				Type type = AccessTools.TypeByName("MenuLib.MenuAPI");
				if (type == null)
				{
					LogInfo("MenuLib 未安装,跳过 REPOConfig 双语劫持。");
					return;
				}
				HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(REPOConfigCreateHook), "Postfix", (Type[])null, (Type[])null));
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal) { "CreateREPOLabel", "CreateREPOToggle", "CreateREPOSlider", "CreateREPOInputField", "CreateREPOButton" };
				int num = 0;
				foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
				{
					if (!hashSet.Contains(declaredMethod.Name))
					{
						continue;
					}
					ParameterInfo[] parameters = declaredMethod.GetParameters();
					if (parameters.Length != 0 && !(parameters[0].ParameterType != typeof(string)))
					{
						try
						{
							h.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
							num++;
						}
						catch
						{
						}
					}
				}
				if (num > 0)
				{
					LogInfo($"REPOConfig 双语显示已挂载({num} 个 UI 创建方法)。");
				}
				else
				{
					LogInfo("REPOConfig 没找到可劫持 of UI 方法。");
				}
			}
			catch (Exception ex)
			{
				LogInfo("REPOConfig 劫持失败:" + ex.Message);
			}
		}

		private static void LogInfo(string msg)
		{
			if ((Object)(object)Instance != (Object)null && ((BaseUnityPlugin)Instance).Logger != null)
			{
				((BaseUnityPlugin)Instance).Logger.LogInfo((object)msg);
			}
		}

		private static bool IsOwnEntry(CfgI18N i18n)
		{
			if (i18n == null || i18n.Entry == null || (Object)(object)Instance == (Object)null)
			{
				return false;
			}
			return i18n.Entry.ConfigFile == ((BaseUnityPlugin)Instance).Config;
		}

		private static bool IsRenderingOurMod()
		{
			return true;
		}

		internal static void RegisterLiveLabelIfMatched(string label, object uiElement)
		{
			if (string.IsNullOrEmpty(label) || uiElement == null)
			{
				return;
			}
			string text = label.Trim().Replace("▼", "").Replace("▶", "")
				.Replace(" ", "");
			CfgI18N cfgI18N = null;
			for (int i = 0; i < _cfgI18Ns.Count; i++)
			{
				CfgI18N cfgI18N2 = _cfgI18Ns[i];
				string value = cfgI18N2.SectionEN?.Replace(" ", "");
				string value2 = cfgI18N2.SectionCN?.Replace(" ", "");
				if ((!string.IsNullOrEmpty(value) && text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) || (!string.IsNullOrEmpty(value2) && text.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0))
				{
					cfgI18N = cfgI18N2;
					break;
				}
			}
			if (cfgI18N != null)
			{
				RegisterOneLiveLabel(uiElement, cfgI18N, isSection: true);
				return;
			}
			string key = label.Trim();
			if (!_cfgByKeyOnly.TryGetValue(key, out var value3) || value3 == null || value3.Count == 0)
			{
				return;
			}
			if (value3.Count == 1)
			{
				RegisterOneLiveLabel(uiElement, value3[0], isSection: false);
				return;
			}
			if (!_pendingKeyMatchIndex.TryGetValue(key, out var value4))
			{
				value4 = 0;
			}
			if (value4 >= value3.Count)
			{
				value4 = 0;
			}
			RegisterOneLiveLabel(uiElement, value3[value4], isSection: false);
			_pendingKeyMatchIndex[key] = value4 + 1;
		}

		private static void RegisterOneLiveLabel(object uiElement, CfgI18N i18n, bool isSection)
		{
			if (!IsOwnEntry(i18n) || !IsRenderingOurMod())
			{
				return;
			}
			TextMeshProUGUI labelTmp = GetLabelTmp(uiElement);
			if ((Object)(object)labelTmp == (Object)null)
			{
				return;
			}
			bool flag = UseChinese();
			string text = ((!isSection) ? (flag ? i18n.KeyCN : (i18n.DisplayEN ?? i18n.KeyEN)) : (flag ? i18n.SectionCN : i18n.SectionEN));
			if (!string.IsNullOrEmpty(text))
			{
				try
				{
					((TMP_Text)labelTmp).text = text;
				}
				catch
				{
				}
			}
			_liveLabels.Add(new LiveLabel
			{
				Tmp = new WeakReference<TextMeshProUGUI>(labelTmp),
				I18N = i18n,
				IsSection = isSection
			});
			if (!isSection && i18n.ReadOnlyEntry != null)
			{
				Object val = (Object)((uiElement is Object) ? uiElement : null);
				if (val != null && AccessTools.Field(uiElement.GetType(), "inputStringSystem") != null)
				{
					_liveValues.Add(new LiveValue
					{
						InputField = new WeakReference<Object>(val),
						I18N = i18n
					});
					TrySetInputFieldValue(val, flag ? i18n.ValueCN : i18n.ValueEN);
				}
			}
			if (!isSection && i18n.Entry != null && i18n.Entry.SettingType.IsSubclassOf(typeof(Enum)))
			{
				Object val2 = (Object)((uiElement is Object) ? uiElement : null);
				if (val2 != null)
				{
					PropertyInfo propertyInfo = AccessTools.Property(((object)val2).GetType(), "stringOptions");
					if (propertyInfo != null)
					{
						string[] names = Enum.GetNames(i18n.Entry.SettingType);
						string[] array = BuildEnumOptionsCN(names);
						string[] array2 = BuildEnumOptionsEN(names);
						_liveSliderOptions.Add(new LiveSliderOptions
						{
							Slider = new WeakReference<Object>(val2),
							I18N = i18n,
							OptionsCN = array,
							OptionsEN = array2
						});
						TrySetSliderOptions(val2, propertyInfo, flag ? array : array2);
					}
				}
			}
			Type type = uiElement.GetType();
			FieldInfo fieldInfo = AccessTools.Field(type, "leftButtonTMP");
			FieldInfo fieldInfo2 = AccessTools.Field(type, "rightButtonTMP");
			if (fieldInfo != null && fieldInfo2 != null)
			{
				object? value = fieldInfo.GetValue(uiElement);
				TextMeshProUGUI val3 = (TextMeshProUGUI)((value is TextMeshProUGUI) ? value : null);
				object? value2 = fieldInfo2.GetValue(uiElement);
				TextMeshProUGUI val4 = (TextMeshProUGUI)((value2 is TextMeshProUGUI) ? value2 : null);
				if ((Object)(object)val3 != (Object)null && (Object)(object)val4 != (Object)null)
				{
					_liveToggleButtons.Add(new LiveToggleButtons
					{
						LeftTmp = new WeakReference<TextMeshProUGUI>(val3),
						RightTmp = new WeakReference<TextMeshProUGUI>(val4)
					});
					ApplyToggleButtonText(val3, val4, flag);
				}
			}
		}

		private static TextMeshProUGUI GetLabelTmp(object uiElement)
		{
			try
			{
				Type type = uiElement.GetType();
				if (!_labelTmpFieldCache.TryGetValue(type, out var value))
				{
					value = AccessTools.Field(type, "labelTMP");
					_labelTmpFieldCache[type] = value;
				}
				object? obj = value?.GetValue(uiElement);
				return (TextMeshProUGUI)((obj is TextMeshProUGUI) ? obj : null);
			}
			catch
			{
				return null;
			}
		}

		internal static void RefreshLiveLabels()
		{
			bool flag = UseChinese();
			for (int num = _liveLabels.Count - 1; num >= 0; num--)
			{
				LiveLabel liveLabel = _liveLabels[num];
				if (!liveLabel.Tmp.TryGetTarget(out var target) || (Object)(object)target == (Object)null || (Object)(object)((Component)target).gameObject == (Object)null)
				{
					_liveLabels.RemoveAt(num);
				}
				else
				{
					string text = ((!liveLabel.IsSection) ? (flag ? liveLabel.I18N.KeyCN : (liveLabel.I18N.DisplayEN ?? liveLabel.I18N.KeyEN)) : (flag ? liveLabel.I18N.SectionCN : liveLabel.I18N.SectionEN));
					try
					{
						((TMP_Text)target).text = text;
					}
					catch
					{
						_liveLabels.RemoveAt(num);
					}
				}
			}
			for (int num2 = _liveValues.Count - 1; num2 >= 0; num2--)
			{
				LiveValue liveValue = _liveValues[num2];
				if (!liveValue.InputField.TryGetTarget(out var target2) || target2 == (Object)null)
				{
					_liveValues.RemoveAt(num2);
				}
				else
				{
					string newVal = (flag ? liveValue.I18N.ValueCN : liveValue.I18N.ValueEN);
					if (!TrySetInputFieldValue(target2, newVal))
					{
						_liveValues.RemoveAt(num2);
					}
				}
			}
			for (int num3 = _liveSliderOptions.Count - 1; num3 >= 0; num3--)
			{
				LiveSliderOptions liveSliderOptions = _liveSliderOptions[num3];
				if (!liveSliderOptions.Slider.TryGetTarget(out var target3) || target3 == (Object)null)
				{
					_liveSliderOptions.RemoveAt(num3);
				}
				else
				{
					PropertyInfo propertyInfo = AccessTools.Property(((object)target3).GetType(), "stringOptions");
					if (propertyInfo == null)
					{
						_liveSliderOptions.RemoveAt(num3);
					}
					else
					{
						TrySetSliderOptions(target3, propertyInfo, flag ? liveSliderOptions.OptionsCN : liveSliderOptions.OptionsEN);
					}
				}
			}
			for (int num4 = _liveToggleButtons.Count - 1; num4 >= 0; num4--)
			{
				LiveToggleButtons liveToggleButtons = _liveToggleButtons[num4];
				if (!liveToggleButtons.LeftTmp.TryGetTarget(out var target4) || (Object)(object)target4 == (Object)null || !liveToggleButtons.RightTmp.TryGetTarget(out var target5) || (Object)(object)target5 == (Object)null)
				{
					_liveToggleButtons.RemoveAt(num4);
				}
				else
				{
					ApplyToggleButtonText(target4, target5, flag);
				}
			}
			_pendingKeyMatchIndex.Clear();
		}

		private static bool TrySetInputFieldValue(Object ifObj, string newVal)
		{
			try
			{
				Type type = ((object)ifObj).GetType();
				if (_inputStringSystemField == null || _inputStringSystemField.DeclaringType != type)
				{
					_inputStringSystemField = AccessTools.Field(type, "inputStringSystem");
				}
				if (_inputStringSystemField == null)
				{
					return false;
				}
				object value = _inputStringSystemField.GetValue(ifObj);
				if (value == null)
				{
					return false;
				}
				if (_inputStringSystemSetValue == null || _inputStringSystemSetValue.DeclaringType != value.GetType())
				{
					_inputStringSystemSetValue = AccessTools.Method(value.GetType(), "SetValue", new Type[2]
					{
						typeof(string),
						typeof(bool)
					}, (Type[])null);
				}
				if (_inputStringSystemSetValue == null)
				{
					return false;
				}
				_inputStringSystemSetValue.Invoke(value, new object[2]
				{
					newVal ?? string.Empty,
					false
				});
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static string[] BuildEnumOptionsCN(string[] enumNames)
		{
			string[] array = new string[enumNames.Length];
			for (int i = 0; i < enumNames.Length; i++)
			{
				array[i] = (_enumNameENtoCN.TryGetValue(enumNames[i], out var value) ? value : enumNames[i]);
			}
			return array;
		}

		private static string[] BuildEnumOptionsEN(string[] enumNames)
		{
			string[] array = new string[enumNames.Length];
			for (int i = 0; i < enumNames.Length; i++)
			{
				array[i] = (_enumNameCNtoEN.TryGetValue(enumNames[i], out var value) ? value : enumNames[i]);
			}
			return array;
		}

		private static void TrySetSliderOptions(Object sliderObj, PropertyInfo prop, string[] options)
		{
			try
			{
				prop.SetValue(sliderObj, options);
				AccessTools.Method(((object)sliderObj).GetType(), "UpdateBarText", (Type[])null, (Type[])null)?.Invoke(sliderObj, null);
			}
			catch
			{
			}
		}

		private static void ApplyToggleButtonText(TextMeshProUGUI leftTmp, TextMeshProUGUI rightTmp, bool cn)
		{
			try
			{
				((TMP_Text)leftTmp).text = (cn ? "开" : "ON");
				((TMP_Text)rightTmp).text = (cn ? "关" : "OFF");
			}
			catch
			{
			}
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public bool? ShowRangeAsPercent;

		public Action<ConfigEntryBase> 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;
	}
	internal static class REPOConfigCreateHook
	{
		public static void Postfix(object[] __args, object __result)
		{
			try
			{
				if (__args != null && __args.Length != 0 && __result != null && __args[0] is string label)
				{
					ZichenHealthSiphonPlugin.RegisterLiveLabelIfMatched(label, __result);
				}
			}
			catch
			{
			}
		}
	}
}