Decompiled source of FateBound v0.5.2

plugins/com.quaree.fatebound.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;
using UnityEngine.Rendering;

[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("com.quaree.fatebound")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.2")]
[assembly: AssemblyInformationalVersion("0.5.2")]
[assembly: AssemblyProduct("com.quaree.fatebound")]
[assembly: AssemblyTitle("com.quaree.fatebound")]
[assembly: AssemblyVersion("0.5.2.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;
		}
	}
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace FateBound
{
	public enum FateDifficulty
	{
		Easy,
		Normal,
		Hell,
		Custom
	}
	public enum FateChainTopology
	{
		Ring,
		NearestWeb,
		AllPairs
	}
	[BepInPlugin("com.quaree.fatebound", "命运绑定", "0.5.2")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.quaree.fatebound";

		public const string PluginName = "命运绑定";

		public const string PluginVersion = "0.5.2";

		public static Plugin Instance;

		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<bool> ToggleByHost;

		internal static bool ForceSyncNow;

		internal static ConfigFile PluginConfig;

		private static string cfgLastText;

		private static float cfgPollAt;

		private static FateDifficulty cfgLastDifficulty;

		private static bool cfgWatched;

		public static ConfigEntry<KeyCode> ToggleKey;

		public static ConfigEntry<FateDifficulty> Difficulty;

		public static ConfigEntry<float> MaxDistance;

		public static ConfigEntry<float> GraceSeconds;

		public static ConfigEntry<float> PoisonPerSecond;

		public static ConfigEntry<float> ShareRatio;

		public static ConfigEntry<bool> ShareHunger;

		public static ConfigEntry<bool> ShareInjury;

		public static ConfigEntry<bool> SharePoison;

		public static ConfigEntry<bool> DrawChain;

		public static ConfigEntry<float> ChainWidth;

		public static ConfigEntry<bool> RequireTwoPlayers;

		public static ConfigEntry<bool> DisableInAirport;

		public static ConfigEntry<bool> NotifyShared;

		public static ConfigEntry<KeyCode> DebugKey;

		internal static ConfigEntry<bool> SelfCheckPanel;

		internal static ConfigEntry<int> FarChainInterval;

		internal static ConfigEntry<int> MeshInterval;

		internal static ConfigEntry<int> PhysicsSlice;

		internal static ConfigEntry<float> FarPhysicsDistance;

		internal static ConfigEntry<float> FarSolidDistance;

		internal static ConfigEntry<bool> DiagToFile;

		internal static ConfigEntry<bool> HardTether;

		internal static ConfigEntry<float> HardLimitScale;

		internal static ConfigEntry<float> HardPullForce;

		internal static ConfigEntry<bool> TetherHardPull;

		internal static ConfigEntry<float> TetherPullSpeed;

		internal static ConfigEntry<float> TetherMaxSpeed;

		internal static ConfigEntry<bool> LooseCollect;

		public static ConfigEntry<bool> ChainTautFeedback;

		public static ConfigEntry<string> ChainColor;

		public static ConfigEntry<KeyCode> ChainColorCycleKey;

		public static bool DebugRequested;

		public static ConfigEntry<bool> ChainPhysics;

		public static ConfigEntry<float> ChainSpacing;

		public static ConfigEntry<float> ChainThickness;

		public static ConfigEntry<float> ChainSlack;

		public static ConfigEntry<float> ChainMaxSag;

		public static ConfigEntry<float> ChainHideRadius;

		public static ConfigEntry<int> ChainMaxPoints;

		public static ConfigEntry<FateChainTopology> ChainTopology;

		public static ConfigEntry<bool> ChainLinkTexture;

		public static ConfigEntry<float> ChainTextureScale;

		public static ConfigEntry<bool> SolidChainLinks;

		public static ConfigEntry<float> LinkDiameter;

		public static ConfigEntry<float> LinkWire;

		public static ConfigEntry<bool> ChainMemory;

		public static ConfigEntry<bool> SpringPull;

		public static ConfigEntry<float> PullDamping;

		public static ConfigEntry<bool> CampfireShuffle;

		public static ConfigEntry<bool> SyncFromHost;

		public static ConfigEntry<float> SyncIntervalSeconds;

		public static ConfigEntry<bool> EnableTug;

		public static ConfigEntry<float> TugStartDistance;

		public static ConfigEntry<float> ChainTautRatio;

		public static ConfigEntry<float> MaxPullForce;

		public static ConfigEntry<float> MaxDragForce;

		public static ConfigEntry<float> DragSpeedDiff;

		public static ConfigEntry<float> VerticalPullScale;

		public static ConfigEntry<float> DragRampSeconds;

		public static ConfigEntry<bool> EnablePull;

		public static ConfigEntry<KeyCode> PullKey;

		public static ConfigEntry<float> PullForce;

		public static ConfigEntry<float> PullStaminaPerSecond;

		public static ConfigEntry<float> PullMinDrop;

		public static ConfigEntry<float> PullMaxDistance;

		public static ConfigEntry<float> PullMinStamina;

		public static ConfigEntry<bool> PullNotify;

		public static ConfigEntry<bool> PullAimRequired;

		public static ConfigEntry<float> PullAimAngle;

		public static ConfigEntry<bool> AimHighlight;

		public static ConfigEntry<bool> PullUseReel;

		public static ConfigEntry<float> PullReelSpeed;

		public static ConfigEntry<float> PullReelMinLength;

		public static ConfigEntry<float> PullRopeStiffness;

		public static ConfigEntry<float> PullHaulStiffness;

		public static ConfigEntry<float> PullRopeDamping;

		public static ConfigEntry<float> PullReelMaxForce;

		public static ConfigEntry<float> PullStaminaPerMeter;

		public static ConfigEntry<float> PullHoldAfterRelease;

		public static ConfigEntry<float> PullLedgePush;

		public static ConfigEntry<float> PullMantlePush;

		public static ConfigEntry<float> PullMantleStep;

		public static ConfigEntry<float> PullClimbHaul;

		public static ConfigEntry<bool> PullRescue;

		public static ConfigEntry<bool> PullRescueWeb;

		public static ConfigEntry<bool> PullRecoverFall;

		public static ConfigEntry<float> PullHelperFalloff;

		public static ConfigEntry<float> PullForceCap;

		public static ConfigEntry<float> BraceResist;

		public static ConfigEntry<bool> PullFlatDrag;

		public static ConfigEntry<float> PullRecoil;

		private static bool hostDrawChain;

		private static bool hostEnabled;

		private static bool hostEnabledKnown;

		private static bool hostVisualsKnown;

		internal static readonly string[][] ChainColorPresets = new string[8][]
		{
			new string[3] { "#C08A4A", "古铜", "Antique Bronze" },
			new string[3] { "#9AA3AD", "钢银", "Steel Silver" },
			new string[3] { "#E0C36B", "暗金", "Dark Gold" },
			new string[3] { "#B22222", "深红", "Deep Crimson" },
			new string[3] { "#3CB371", "翠绿", "Emerald" },
			new string[3] { "#5B8BD4", "湖蓝", "Lake Blue" },
			new string[3] { "#8A6FD1", "紫晶", "Amethyst" },
			new string[3] { "#2E2E2E", "玄铁黑", "Iron Black" }
		};

		private static readonly Color ChainColorFallback = new Color(0.753f, 0.541f, 0.29f);

		private static string colorCacheKey;

		private static Color colorCacheVal = ChainColorFallback;

		private const int SYNC_MAGIC = 1178753841;

		internal static ConfigEntry<float>[] SyncFloat;

		internal static ConfigEntry<bool>[] SyncBool;

		internal static ConfigEntry<int>[] SyncInt;

		private void Awake()
		{
			//IL_0ec7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f15: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Loc.Language = ((BaseUnityPlugin)this).Config.Bind<FateLanguage>("FateBound", "Language", FateLanguage.English, "Language of on-screen text / 界面文字的语言. Default: English.\nEnglish = always English (default) / 始终英文(默认)。\nChinese = 始终中文 / always Chinese.\nAuto = 跟随系统语言(中文系统中文,其余英文)/ follow the system language.\n只影响通知栏、自检面板、调试读数;配置文件本身仍是中文注释。\nOnly affects notifications, the F10 panel and the debug readout — the config file itself stays Chinese.");
			Loc.Refresh();
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "启用", true, "总开关。false 时这个 mod 完全不干预游戏。也可以用下面的快捷键在游戏里切换。");
			ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("命运绑定", "启停快捷键", (KeyCode)290, "切换命运锁链开关的快捷键(只影响你自己的客户端)。\n改成 None 可以彻底关掉快捷键。默认 F9 —— 原来那个 Chained 模组用的是 F10,\n两个都装的话 F9 / F10 各管一个,不会打架。");
			Difficulty = ((BaseUnityPlugin)this).Config.Bind<FateDifficulty>("命运绑定", "难度", FateDifficulty.Normal, "难度档位:\nEasy   = 宽松:50 米 / 中毒 0.02 / 共享伤害 0.6\nNormal = 标准:30 米 / 中毒 0.035 / 共享伤害 1.0\nHell   = 硬核:15 米 / 中毒 0.07 / 共享伤害 1.0\nCustom = 自定义:不动下面那三项,完全按你自己填的值来。\n注意:选前三档时,每次启动都会用档位数值覆盖「最大锁链距离 / 中毒速度 / 共享伤害比例」。\n所以如果你想手动调这三项,请把难度设成 Custom,否则下次进游戏就被改回去。\n改完配置文件需要重启游戏才生效。");
			MaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定", "最大锁链距离", 30f, "命运锁链的最大距离(米)。超过这个距离,掉队的人才会开始累积中毒。");
			GraceSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定", "超距宽限秒数", 5f, "掉队后先给几秒宽限,超过这个时间才开始累积中毒。防止短暂分开就被罚。");
			PoisonPerSecond = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定", "中毒速度", 0.035f, "掉队时每秒累积的中毒量。注意中毒是 0~1 的量纲:0.035 = 每秒 3.5%,\n大约 29 秒累积满(游戏自身的中毒自然衰减还会再拖慢它)。\n嫌慢就调大,嫌狠就调小(例如 0.015)。");
			ShareRatio = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定", "共享伤害比例", 1f, "共享强度 0~1。1 = 完全共享(那份恶化增量整份补给全队),0 = 不共享。\n注意共享只传播「恶化」:一个人变饿会把全队一起带饿,\n但谁吃东西只回自己的饥饿值(PEAK 只允许改自己的角色,没法替别人加状态)。");
			ShareHunger = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "共享饥饿", true, "共享饱食度:一个人饿,全队一起饿。");
			ShareInjury = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "共享受伤", true, "共享伤害:游戏里产生的受伤(摔伤等)全队同担。");
			SharePoison = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "共享中毒", false, "是否也共享中毒。默认 false —— 掉队的中毒只由掉队的那个人承担,其余人不受影响。");
			DrawChain = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "绘制链条", true, "在玩家之间画出命运锁链的连线。");
			ChainWidth = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定", "链条粗细", 0.13f, "锁链的渲染宽度(米)。这是链条看起来像不像「一根真链条」的关键 ——\n0.08 细得像一条白线,0.13 才有链条的实体感。想要更粗就继续加,\n但它同时决定了贴图的花纹密集程度,配合下面的「贴图密度」一起调。");
			RequireTwoPlayers = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "需两人以上", true, "少于 2 名真人玩家时,命运锁链不生效(保证必须联机才能玩)。");
			DisableInAirport = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "大厅内不生效", true, "在机场(游戏大厅)阶段完全不生效:不画锁链、不共享状态、不掉队惩罚、不施力。\n改成 false 则大厅里也会绑上锁链。");
			NotifyShared = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "共享时弹提示", true, "共享状态真正生效时,在游戏内弹一条提示(例:命运锁链:队友受伤,你被分摊 -12%)。\n默认开。关掉就回到以前那种「悄无声息」。\n这是每台机器自己的显示偏好,不参与主机同步。");
			ToggleByHost = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "启停由房主统一控制", true, "开启后,「启用」这个开关由房主统一控制:\n房主按 F9 → 全队一起开 / 一起关;客机按 F9 无效(会提示是房主在控制)。\n这是为了防止有人为了跑快点,自己把锁链一关就溜了。\n关掉它 = 回到「每个玩家各管各的」。默认开。\n说明:这挡的是「手滑和赖皮」,不是反作弊 —— 铁了心改配置文件的人一样能绕过去。");
			DebugKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("命运绑定", "调试读数键", (KeyCode)291, "按这个键打印一份调试读数:全队每个人的受伤/饥饿/中毒数值、以及和你的距离。\n会同时弹在游戏内通知栏,并追加写入 BepInEx\\fatebound-debug.txt。\n用途:确认「共享到底有没有生效」。填 None 可关闭。不参与主机同步。\n【v0.4.3 起】按 F10 除了写这份读数,还会开关左上角那块常驻自检面板 ——\n常驻面板才能一直看着「链条有没有画、拉人卡在哪一步」。");
			SelfCheckPanel = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "自检面板", false, "在屏幕左上角常驻一块自检面板(按 F10 开关)。\n上面实时写着:按键走的是哪条通道、右键有没有被读到、房间几个人、\n本帧画了几条链、我连着谁、以及拉人的每一道判定分别过没过。\n一个人也能用它确认模组到底跑到哪一步了 —— 不用等第二个队友。\n这是每台机器自己的显示偏好,不参与主机同步。");
			HardTether = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.硬限制", "硬性拉回", true, "超过硬限就把正在远离的那个人拽回来 —— 真的跑不掉。\n和上面的「拖拽」区别:拖拽是软性的(力度封顶、要几秒才渐满),\n同队里有人一路狂奔时,软拖拽追不上他;这一项是硬兜底。\n施力是「谁在远离,谁自己的机器就把自己拽回来」,所以躲不掉。\n刻意保留成可关:真需要爬长绳、跳远台的时候,硬拉回会觉得碍事。");
			HardLimitScale = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.硬限制", "硬限倍数", 1f, "硬限 = 最大锁链距离 × 这个倍数。1.0 = 一超过锁链距离就硬拽。\n想给一点缓冲就填 1.2、1.5。");
			LooseCollect = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定", "宽松采集队友", false, "【排障用】采集可连对象时,默认会剔掉三类角色:机器人(isBot)、灵魂态(Ghost)、未激活的。\n如果面板上「可连角色」永远只有 1 人、可游戏里明明有好几个队友,\n那就是这三道剔除误伤了 —— 把它打开即可全部收进来。\n正常联机时保持关闭即可;它只是给「队友收不进来」留的一个后门。");
			HardPullForce = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.硬限制", "硬拉回力度", 520f, "硬拉回的力度(米/秒²量级)。越大越像一堵墙。\n520 大约是「跑起来明显被拽住、但不会被甩飞」。\n嫌不够硬就往上加,嫌人被打得乱晃就往下调。");
			TetherHardPull = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.硬限制", "距离硬回拽", true, "超过硬限时,直接把你平移回队友那边 —— 不经过力的平衡,按住方向键也顶不回来。\n这是「跑再远也断不了」的最终保险:上面那些拉力都是施加在 ragdoll 刚体上的,\n而角色移动由玩家自己的主控每帧重算并摆放,外力会被吃掉一大半 ——\n所以人一跑远,光靠力就拽不住了,这一项才是真正兜底的那个。\n关掉则只剩纯施力模式(手感更软,但跑得快的人可能跑得掉)。");
			TetherPullSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.硬限制", "回拽基础速度", 1.4f, "刚超过硬限时的回收速度(米/秒)。超得越多收得越快:\n超 1 米约 2.8 m/s,超 5 米约 8.4 m/s,很快就逼近下面的上限。\nPEAK 里全力跑大约 7~9 m/s,所以这个值配合超距加成,一旦被拉开 5 米以上就跑不掉了。\n觉得「一超限就被硬拖、手感僵」就调小到 0.6;觉得还是拉不住就调到 2~3。");
			TetherMaxSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.硬限制", "回拽速度上限", 14f, "回拽最快多少米/秒。**必须高于玩家的奔跑速度**,否则永远追不上一个一路狂奔的人 ——\n这也是为什么它默认 14 而不是 8。\n调太高会出现「人被瞬间吸回去」的观感,调低则可能追不上跑得快的队友。");
			FarChainInterval = ((BaseUnityPlugin)this).Config.Bind<int>("命运绑定.性能", "远处链条刷新间隔", 3, "【性能】不是我参与的链条,物理求解几帧算一次。1 = 每帧(最卡)。\n3 人以上时会同时存在最多 4 条链,其中只有 1~2 条挂在自己身上 ——\n把不挂在自己身上的那些降频,是最省的一件事,画面几乎看不出差别。\n填 1 可关掉这项优化(回到最费的模式,用于排查)。");
			MeshInterval = ((BaseUnityPlugin)this).Config.Bind<int>("命运绑定.性能", "链环网格刷新间隔", 2, "【性能】链环的立体模型几帧重建一次。1 = 每帧(最卡)。\n链环网格每帧重建是这个模组里最费的一件事:一条 56 节的链\n每帧要重算几千个顶点。2 帧重建一次,肉眼基本看不出差别。\n填 1 可关掉这项优化。");
			PhysicsSlice = ((BaseUnityPlugin)this).Config.Bind<int>("命运绑定.性能", "碰撞分摊帧数", 0, "【性能 · 人多时最关键的一项】链条的碰撞检测(贴墙推出、地面探针)分摊到几帧里做。\n0 = 自动(按当前链数决定:1~2 条链全量、3~4 条一半、5 条以上三分之一)——\n人少时一点画质都不掉,人多时每帧射线量按比例下降。\n填 1 = 每帧全量(最卡,排查用);填 2~4 = 强制分摊。\n分摊只影响碰撞,不影响链条的形状和拉力,画面上几乎看不出区别。");
			FarPhysicsDistance = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.性能", "远链简化距离", 14f, "【性能】离自己超过这个距离(米)的链,物理算得粗一些:\n跳过「点在几何体内部」的长射线判定、跳过深埋自救、跳过渲染点兜底推出。\n这三样都是防穿模的,而十几米外的链子穿不穿根本看不见 —— 但它们恰恰最贵。\n0 = 不简化(所有链一样精细,最费)。");
			FarSolidDistance = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.性能", "立体链环距离", 18f, "【性能】离自己超过这个距离(米)的链改用**贴图线**画,不再重建立体链环网格。\n链环网格是一顶点一顶点拼出来的(一条长链上万个顶点),是渲染侧最大的一笔开销;\n远处那条细链子换成线,视觉上几乎看不出,帧数却明显回升。\n0 = 不降级(所有链都用立体链环)。");
			DiagToFile = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.性能", "诊断写日志文件", false, "【性能】自检数据是否每 2 秒写进 BepInEx\\fatebound-diag.txt。\n默认关:那是每 2 秒一次磁盘写入,机械盘 / 杀毒软件拦一刀就是一次卡顿尖刺。\n要排查问题时再打开,排查完记得关掉。屏幕上的 F10 面板不受这一项影响。");
			ChainTautFeedback = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.链条", "绷紧反馈", true, "链条绷紧时用颜色和提示把它显出来:松弛是冷灰白,越紧越烫,绷到一半以上会弹一句提醒。\n颜色曲线和拖拽用的是同一条(6 米起、30 米满),所以「链子一发烫 = 你正在被拽」。\n这是每台机器自己的显示偏好,不参与主机同步。");
			ChainColor = ((BaseUnityPlugin)this).Config.Bind<string>("命运绑定.链条", "链条颜色", "#C08A4A", "链条底色。十六进制色号,可以带 # 也可以不带(写 C08A4A 或 #C08A4A 都行)。\n默认 #C08A4A 是古铜色 —— 铁链本来是暖金属,冷灰白看着发怪。\n几个现成的值:\n  C08A4A 古铜(默认)    9AA3AD 钢银      E0C36B 暗金      B22222 深红\n  3CB371 翠绿            5B8BD4 湖蓝      8A6FD1 紫晶      2E2E2E 玄铁黑\n注意别选太接近红色的底(比如深红),不然绷紧变色就看不出差别了。\n写错了不会崩:解析失败会退回古铜色。也可以在游戏里按「换色快捷键」循环切换。");
			ChainColorCycleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("命运绑定.链条", "换色快捷键", (KeyCode)289, "在游戏里按这个键,按预设色板循环切换链条颜色,并立刻生效、自动存进配置。\n默认 F8。填 None 可关闭。不参与主机同步。");
			ChainPhysics = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.链条", "物理链条", true, "锁链是否做地形求解(会绕开障碍物、不穿模)。关掉则退回两点直线,用于对比排查。\n【v0.2.0 起求解方式变了】不再是质点物理模拟,改成每帧纯几何求解:\n在两端连线上铺一串点 → 松弛成柔顺曲线 → 最后一步把所有点推出障碍物。\n因为「推出障碍物」跑在最后,渲染出去的每个点都刚被推出来过,\n所以在构造上就不可能穿模 —— 不再是「调参调到位就不穿」。");
			ChainSpacing = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.链条", "链环间距", 0.55f, "链节间距(米)。越小越贴合地形,但链节更多、开销更大。");
			ChainThickness = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.链条", "链条厚度", 0.15f, "链节的碰撞球半径(米)。调大更不容易穿进石头缝,还偶尔穿就调到 0.1。\n【注意】实际使用时会硬性钳在 0.4 米以内 —— 半径越大,从障碍物里推出的位移量就越大,\n旧版拉满后一碰到模型整条链条会直接扎进地里,所以这里必须封顶。你填 1.0 也按 0.4 算。");
			ChainSlack = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.链条", "链条松弛量", 0.01f, "链条松弛比例。0 = 完全绷直,越大越松。\n注意:多余长度会带来约 √(3·距离·多余长度/8) 的下垂,所以这个值要小。\n0.12 在跨 8 米时下垂 1.7 米、链条直接垂到地上;0.01 只有 0.5 米,悬在两人之间。\n松弛量另有 0.25 米的绝对上限,避免远距离必然垂地。");
			ChainMaxSag = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.链条", "最大下垂", 1.2f, "最大下垂(米):链节最多只能低于「两人中较低的那个」这么多,超出就被抬回去。\n【v0.4.0】默认从 0.6 提到 1.2 —— 链条现在会「记住」自己挂在哪,垂得多一点才像真铁链。\n嫌链条垂太低就调小;0 = 不限制(链条会自然垂到地形上)。\n这是防止链条趴到地上的兜底 —— 只靠重力+长度约束是压不住的,\n位置约束始终有残余拉伸,跨度 12 米以上实测会整条垂到地面。\n0 = 不限制(链条会自然垂到地形上)。\n【注意】填 0 或填很大时,实际按 2.0 米封顶 —— 否则链条会一路垂进地形里。");
			ChainHideRadius = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.链条", "贴脸隐藏半径", 0.85f, "近摄像机裁剪半径(米)。\n链条起点就在你自己身上,第一人称下摄像机离它只有 0.2 米左右 ——\n那一节会被渲染成一个占满屏幕的白色大楔子,看起来像「下面没有人却连着一条链子」。\n这个半径内的起始链节会被丢掉,画面上的链条从稍远处开始(第一人称绳索的常规做法)。\n0 = 不裁剪,会看到贴脸的大白块。");
			ChainMaxPoints = ((BaseUnityPlugin)this).Config.Bind<int>("命运绑定.链条", "链节基础数量", 56, "正常情况下链条用多少个链节。注意它现在是**下限**,不是上限 ——\n链条长的时候会自动加节,让相邻链节始终保持在「链环间距」左右。\n这正是链子不会断、又不会穿模的原因:链节间距没被拉散,每个链节都在做地形检测。\n硬上限 200 节(约 110 米),再远才会开始拉伸链节间距。\n调大 = 链节更密、更贴合地形,但更费性能。默认 56。");
			ChainTopology = ((BaseUnityPlugin)this).Config.Bind<FateChainTopology>("命运绑定.链条", "链条拓扑", FateChainTopology.NearestWeb, "锁链怎么连:\nRing       = 全队连成一个闭环。顺序按【空间邻近】排 —— 每条链都是挨得近的两个人。\n             每个人都有一条链,每个客户端都画出全部链条,4 人 = 4 条。\nNearestWeb = 每个人都连到离自己最近的那个人,画出全部这些链。\n             链永远最短、人人都被连上,不会出现横跨半张地图的怪链。\nAllPairs   = 两两互连,4 人 = 6 条链。最「万物互联」,也最费性能。\n(v0.1.11 起配对一律看位置,不再按玩家加入房间的顺序 —— 旧版就是因此把地图两端、\n  相隔几百米的两个人连在了一起。)");
			ChainLinkTexture = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.链条", "链环贴图", true, "给锁链套一张链环贴图,让它看起来是一根真正的链条,而不是一条白线。\n贴图是插件自己用代码画出来的(一节一节的金属环、中间镂空、首尾相接),\n不依赖任何外部图片文件 —— 所以这个包拖进 plugins 就能用,不用再下别的东西。\n关掉的话就是一条纯色带子(旧版就是这样,所以看着像木条、像白色楔子)。");
			SolidChainLinks = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.链条", "立体链环", true, "把链条画成**真的一颗颗铁环**,环环相扣、相邻两颗互相垂直 —— 不是贴在一条带子上的图案。\n网格是插件运行时算出来的(用代码生成环面),同样不需要任何外部模型文件。\n关掉则回到「链环贴图」那条带子,两者不会同时出现。");
			LinkDiameter = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.链条", "链环直径", 0.14f, "一颗铁环的外径(米)。默认 0.14 —— 大约一个拳头,远看像条结实的铁链。\n调大手感更粗犷(0.2 以上就很夸张了),调小更细密。范围 0.02 ~ 0.6。");
			LinkWire = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.链条", "链环丝径", 0.035f, "铁环本身那根铁丝有多粗(米)。调粗更结实、更沉;调细更精致。\n上限会被自动压到「链环直径」的 45%,太粗会把中间的孔填死。");
			ChainMemory = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.链条", "链条记忆", true, "【v0.4.0】链条以上一帧的形态为起点接着解,于是它「记得」自己刚才是从哪边绕过石头的。\n这是消掉绕障时左右翻面(抽搐)的根因修复,关掉不会让链子穿模,但会重新开始翻面。\n留这个开关是为了让你能对比两种手感:关掉就是旧版「每帧从直线重算」的行为。");
			SpringPull = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.链条", "弹簧拉力", true, "【v0.4.1】拉力的力-伸长曲线换成「弹簧」:线性 —— 刚开始拉开时只是轻轻带住,越拉越沉。\n关掉则回到旧版的开方曲线(10% 伸长就出 32% 的力,起手很硬、像一根杆子)。\n弹簧曲线是 Chainbound(另一个作者的同类模组)用的那套手感,照搬它的思路。");
			PullDamping = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.链条", "拉力阻尼", 0.35f, "【v0.4.1】把「你拉开的快慢」也算进拉力:猛地扯一下比慢慢挪更吃劲。\n0 = 只看拉开的距离(旧行为);0.35 表示远离速度到 5 米/秒时,额外加三成力。\n调大链条更「黏」、更不容易被猛地挣脱;调小更松。上限自动夹在 0 ~ 2。");
			CampfireShuffle = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.链条", "篝火顺序重排", true, "【v0.4.1】每走完一段路(到篝火、段号推进)就把链条的顺序重排一次 —— \n和 Chainbound 的 ShuffleChainOrder 是同一个意思。只做「就近小幅换位」,\n所以不会出现一条链横跨半张地图。关掉则整个过程顺序都不变。");
			ChainTextureScale = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.链条", "贴图密度", 0.5f, "贴图沿链条方向的重复密度。越小 → 链环越大、越少;越大 → 链环越小、越密。\n0.5 大约是每 0.25 米一节链环。觉得花纹太碎就调到 0.25,觉得太大就调到 1.0。\n这个值在链条被隐藏又重新显示之后才会生效(贴图是 建线时读一次的)。");
			SyncFromHost = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.同步", "跟随房主参数", true, "【主机同步】只由开房间的那个人调参数,其他玩家不用动,会自动同步过去。\n开启后:房主每隔几秒把自己这份配置广播给全房间,客机收到就直接生效。\n快捷键「启停快捷键」和总开关「启用」不参与同步 —— 那两个是每个客户端自己的事,\n不该被房主覆盖(不然你按自己的键切开关会被房主一直掰回去)。\n如果你是客机、就是不想被房主覆盖,把这一项改成 false。");
			SyncIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.同步", "同步间隔秒数", 5f, "房主每隔几秒广播一次参数(秒)。\n调小 → 改参数更快生效;调大 → 网络包更少。中途加入的玩家最多等这么久就拿到房主的参数。");
			TugStartDistance = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拖拽", "拖拽起始距离", 6f, "拉开多少米之后锁链开始拽人(米)。\n这是本项的关键参数:旧版用的是「最大锁链距离」×「绷紧比例」= 30 × 0.75 = 22.5 米,\n正常一起爬图的队伍根本到不了,所以拖拽一次都没触发过。\n调小 = 更容易被拽(例如 4);调大 = 靠得很近也不管你(例如 10)。");
			EnableTug = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.拖拽", "启用拖拽", true, "锁链拖拽:绷紧时前面的人会拉住后面的人;后面的人一直不走,前面的人会越来越吃力。");
			ChainTautRatio = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拖拽", "绷紧比例(已废弃)", 0.75f, "【已废弃,v0.1.11 起不再使用】旧版用它算绷紧起点(「最大锁链距离」× 该值 = 22.5 米,太高了)。\n现在改用上面的「拖拽起始距离」,这一项留着不影响任何行为。");
			MaxPullForce = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拖拽", "最大拉力", 160f, "被拽时施加的最大力。觉得拽不动就调大(例如 320),拽得太猛就调小(例如 80)。");
			MaxDragForce = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拖拽", "最大拖拽力", 160f, "拖着不走的人时,施加给自己的最大阻力(负重感)。");
			VerticalPullScale = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拖拽", "竖直拉力比例", 0.35f, "拉力里竖直(上下)方向占的比例。\n力是按三维方向算的(从我指向队友),所以头上有队友时,\n力里天生带一个向上的分量,全量施加会把你往上吊。\n1.0 = 按原始方向全量拉;0.35 = 竖直压到三成(默认);\n0.0 = 拉力完全落在水平面上,上面的人怎么跑都不会把你往上拽。\n这是本地手感设置、不参与主机同步 —— 它只影响你自己角色的受力。");
			DragSpeedDiff = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拖拽", "拖拽速度差", 0.5f, "速度差超过多少(米/秒)才算「一个在走、一个没走」。");
			DragRampSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拖拽", "拖拽渐满秒数", 4f, "后面的人一直不走时,阻力从 0 涨到满需要几秒。时间越长越能体现「越来越重」。");
			EnablePull = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.拉人", "允许拉队友", true, "按住「拉人快捷键」,把锁链上比你低得够多的队友往上拉 —— 队友掉进坑里爬不上来时用。\n代价是拉的人自己掉体力(PEAK 原生体力,不是饥饿值):体力见底就拉不动,\n松开歇一会儿会自然回复。因为力是往队友那边施的,你自己也会被链条往下带一点 ——\n本模组里那股「负重感」就来自这里。");
			PullKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("命运绑定.拉人", "拉人快捷键", (KeyCode)324, "按住这个键开始拉人(是按住,不是按一下)。\n默认 Mouse1 = 鼠标右键 —— 对着链条按住右键就能拉,不用另记快捷键。\n想换键就填名字:F7 / Mouse0(左键) / E / Q ... 填 None 可以关掉。\n按住时会自动挑「锁链上比你低得最多」的那名队友,不用事先选中谁。");
			PullForce = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人", "拉人上升力", 190f, "往上拉队友的力度。拉不动就调大(例如 300),人被拉得乱飞就调小(例如 120)。\n这个力沿着「你 → 队友」的三维方向施加,竖直分量是满的,\n不受上面「竖直拉力比例」的限制 —— 那一项管的是拖拽阻力,不管拉人。");
			PullStaminaPerSecond = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人", "拉人每秒体力消耗", 0.35f, "【旧模型专用】不勾「收绳模型」时才用这一项:拉人时每秒掉多少体力。\n注意单位:游戏里的体力是 0~1 的小数(不是 0~100)。\n游戏本身每秒自动回 0.2,所以这里要大于 0.2 才算「有代价」——\n0.35 净消耗 0.15/秒 ≈ 连续拉 6~7 秒;调到 0.22 几乎白拉,调到 0.8 只能拽两下。");
			PullMinDrop = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人", "拉人最小高度差", 1.2f, "你对着的这条链,另一头至少要低于你这么多米,才拉得动。\n原因是力的方向固定朝你这边(只会往上提),另一头比你高时这一拉等于把人往下拽。\n不满足时只弹提示、**不会偷偷换成别人** —— 你指着谁就是谁。\n觉得「明明能拉却拉不动」就把它调小(0.6 已经很宽松),或者打开「高度差不够也平拉」。");
			PullMaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人", "拉人最远距离", 18f, "超过这个距离就拉不动了(米)。默认 18 —— 比「拖拽起始距离」6 米宽松,\n因为掉进坑里的队友经常离得很远。");
			PullMinStamina = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人", "拉人最低体力", 0.1f, "体力低于这个值就拉不动了(会提示一次)。防止体力见底还能无限拉。\n单位同样是 0~1 的小数:0.1 = 体力条剩一成。");
			PullNotify = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.拉人", "被拉时弹提示", true, "有人拉你时,你这边弹一条提示。默认开。这是每台机器自己的显示偏好,不参与主机同步。");
			PullAimRequired = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.拉人", "需要对着链条", true, "开启后,**你对着哪条链就拉哪条链另一头的人** —— 目标完全由你的视线决定,\n不会替你自动挑人。多人时这一点很重要:你想拉谁就瞄谁的链。\n不对着任何链子时,右键完全不动作(不扣体力、不弹提示、不影响原版右键)。\n关掉它则退回「离我最近的那条链的另一端」,仍然不按高度挑人。");
			PullAimAngle = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人", "对准角度", 35f, "「对着链条」的容忍角度(度)。数值越大越宽松:\n35 需要瞄得比较准;60 大概朝着那个方向就行;90 等于半个视野内都算对准。\n链条没有碰撞体,判定是按角度算的,所以这里给的是角度不是准心。");
			AimHighlight = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.拉人", "瞄准高亮", true, "【v0.4.1】你视线对着的那条链会亮一档,不用按任何键 —— 于是你随时都知道\n「现在按右键会拉谁」,而不是按下去才发现拉错了人。\n这是本地显示偏好,不参与主机同步。关掉则链子只按绷紧度变色。");
			PullUseReel = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.拉人.收绳", "收绳模型", true, "【v0.4.7】拉人改成「把绳子收短」,而不是「给一个恒定推力」。\n原理:绳子有效长度一格格变短,人和人的距离超过绳长之后,张力弹簧就把人拽过来 ——\n力 = 刚度 × 超出长度 + 阻尼 × 相互拉开的速度。\n好处是「拉不拉得动」由绳长和地形决定,不再靠力的大小硬怼:\n贴墙的人会顺着墙被拖上去,卡在檐口的人会被托一把。\n关掉则退回 v0.3.0 那个恒定推力模型(下面「拉人上升力」那几项才生效)。");
			PullReelSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "收绳速度", 3.5f, "按住时每秒把绳子收短多少米。越大拉得越快、也越像硬拽(默认 3.5,接近慢慢绞盘)。\n队友被拉得乱飞就调小到 2;嫌拉得太慢就调大到 6。");
			PullReelMinLength = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "最短余长", 1.5f, "绳子最短能收到多少米。收到这个长度就不再变短 —— 于是两个人不会贴在一起。\n想把人拉得更近就调小(0.8),不想贴脸就调大(3)。");
			PullRopeStiffness = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "绳子刚度", 70f, "每超出绳长 1 米,给出多大的力。这是整个手感的主旋钮:\n调大 = 绳子像钢丝,绷住就立刻被拽走;调小 = 像弹力绳,慢慢被带过去。\n默认 70,和原版那条 14 米绳子上的 60 是同一个量级。");
			PullHaulStiffness = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "起吊刚度", 150f, "【正在收绳时】用的刚度 —— 比上面那个硬得多。\n效果就是「被起吊」的手感:绳几乎不伸长,人被结实地上提,而不是被弹簧慢慢扯。\n原作者这里用的是 180(普通 60 的 3 倍),本模组按比例取了 150。");
			PullRopeDamping = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "绳子阻尼", 6f, "被拉的人「沿着绳方向往下坠」得越快,额外加多少比例的力。\n这就是铁链那种「黏」的感觉:猛地一坠比慢慢下滑更吃劲,也是防止他被弹回来的关键。\n0 = 只看距离不看速度(会更容易来回弹)。");
			PullReelMaxForce = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "最大拉力", 190f, "单条链最多给多大拉力(封顶)。调大能拉起更重的情形,但调太大容易把人甩飞。\n默认 190 —— 和旧模型的「拉人上升力」同值,换模型后手感落差不会太突兀。");
			PullStaminaPerMeter = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "每米体力", 0.1f, "绳子每收进来 1 米,掉多少体力。\n单位是游戏原生的 0~1(满体力 = 1,不是 100)。0.1 ≈ 满体力能连收 10 米;\n游戏自己每秒回 0.2 体力,所以边拉边回,实际能拉的距离约是这个数的一倍半。\n松着不收、只是绷着保护时**不掉体力**。");
			PullHoldAfterRelease = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "松手后仍绷着秒数", 1.5f, "松开按键之后,绳子还继续保持这么久的张力(不再收短,只是绷着)。\n攀岩里这叫保护 —— 手指松一下不至于把队友直接扔下去。\n调 0 = 一松手立刻断(旧模型的行为);调大更宽容,但「一只手挂着被救」的时间也变长。");
			PullLedgePush = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "檐下推开", 10f, "被拉的人卡在屋檐底下、被绳子往墙里怼的时候,改成沿墙面法线往外推这么大力。\n于是他会先滑出檐底、再被提上来,而不是贴在墙上僵住。0 = 关掉这项修正。");
			PullMantlePush = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "檐口托一把", 25f, "被拉的人卡在檐口正下方、怎么拉都上不去时:探一次檐外的地板,朝那个落脚点推这么大力。\n最多推 2 秒、离目标 0.3 米就收手。0 = 关掉(那就是只能干拉到绳长的位置)。");
			PullMantleStep = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "檐口探测距离", 0.8f, "「檐口托一把」往檐外探多远去找落脚的地板(米)。\n屋顶窄就调小(0.4),檐口厚就调大(1.5)。");
			PullClimbHaul = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "爬墙拖拽", 70f, "被拉的人**正在爬墙**时,额外沿墙拖这么大力 —— 相当于原版那只「帮忙的手」。\n不给他这一点力的话,人在墙上会一直原地蹬腿。0 = 关掉。");
			PullForceCap = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "合拉总力上限", 260f, "【多人】被拉的人身上来自所有拉绳者的**合力**上限。\n两个人同时拉一个队友时力本来会直接相加,叠加起来很容易把人甩飞、撞墙。\n默认 260 ≈ 单人拉满(190)再多一点点,两个人的效果是「更稳」而不是「更猛」。\n调大 = 多人拉更凶;调小 = 人多也温柔。");
			PullHelperFalloff = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "合拉第二人起衰减", 0.6f, "【多人】同一个人被多人同时拉时,从第二个来源起,每个来源只按这个比例计入合力。\n0.6 的意思是:第二个人的 100 只算 60,第三个人在上面再打 6 折。\n这是 Capitan 那套 TeamPull 的做法 —— 帮手很有用,但不会线性翻倍。");
			PullRescue = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.救援", "被拉时自动脱困", true, "【被拉的人】正被人拉着的时候,如果自己被卡住了(卡在石缝、箱角、地形里),自动解除卡住状态。\n没有这一项时,被拉的人只能干耗对方的体力 —— 对方绳子收到最短也没用,因为人根本动不了。\n只在自己被拉的时候生效,不会影响平时被卡住要靠自己挣扎的设计。\n这是本机自己的规则(谁被拉谁决定)。");
			PullRescueWeb = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.救援", "被拉时挣脱蛛网", true, "【被拉的人】被拉期间自动清掉「蛛网」这类束缚状态,一层一层剥。\n蛛网会把人钉在原地,拉也拉不动 —— 但只在有人拉你的时候生效,\n所以平时踩到蛛网还是得自己按挣脱,不会变成无视地形。");
			PullRecoverFall = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.救援", "被拉时自动回正", true, "【被拉的人】被拉期间如果正处于「摔倒 / 爬不起来」的状态,自动站起来恢复操作。\n摔晕的人被拉上来通常还是一摊,抓不了墙、也接不上下一步 —— 这一项让他被拉起的那一刻就能动。\n只在自己被拉的时候触发,平时摔倒还是原本的恢复节奏。");
			BraceResist = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.物理", "站着当桩减伤", 0.55f, "【重写自 StabilityResolver】自己接地、且几乎没在移动时,被链条拖走的力最多减掉这个比例。\n0.55 = 站定不动的人只需要一半的力就能稳住,队友吊在上面时你不会被一起拽下去。\n一旦跑起来、或者离地(掉进坑里、被拉起来),这一项立刻失效 —— 桩只有站着的时候才算桩。\n手上正握着绳(在拉人 / 正在被拉)时效果更强一点。填 0 = 关掉,回到「谁动谁被拽」。");
			PullFlatDrag = ((BaseUnityPlugin)this).Config.Bind<bool>("命运绑定.拉人", "高度差不够也平拉", false, "默认拉人只往上提:如果另一头的人不比你低,按下去只会提示一句,不出力。\n打开这一项之后,高度差不够也照样收绳,但力只保留水平方向 —— 相当于把人往你脚边拖。\n用途:队友在对面平台上够不着、或者卡在略高一点的坡口时,可以先把人拖过来。\n代价是会把人往你这边拽得比较生硬,墙多的地方可能贴墙。默认关。");
			PullRecoil = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人.收绳", "拉人反作用力", 0.15f, "拉别人的时候,自己也承受同方向反过来的这么一份力(比例)。\n0.15 = 你出一百分力,自己会被往回拽十五分 —— 站在崖边救人会有感觉,但不至于被拽下去。\n填 0 = 完全不承受反作用力(隔空提人,自己纹丝不动)。\n这个力会被「站着当桩减伤」抵消一部分,所以站定拉人比边跑边拉稳得多。");
			SyncFloat = new ConfigEntry<float>[33]
			{
				MaxDistance, PoisonPerSecond, GraceSeconds, ShareRatio, ChainWidth, ChainThickness, ChainSpacing, ChainSlack, ChainMaxSag, ChainHideRadius,
				ChainTextureScale, TugStartDistance, MaxPullForce, MaxDragForce, DragSpeedDiff, DragRampSeconds, LinkDiameter, LinkWire, PullDamping, PullReelSpeed,
				PullHaulStiffness, PullRopeStiffness, PullStaminaPerMeter, PullMinDrop, PullMaxDistance, PullMinStamina, PullForceCap, PullHelperFalloff, BraceResist, TetherPullSpeed,
				TetherMaxSpeed, HardLimitScale, HardPullForce
			};
			SyncBool = new ConfigEntry<bool>[21]
			{
				ShareHunger, ShareInjury, SharePoison, DrawChain, ChainPhysics, EnableTug, ChainLinkTexture, SolidChainLinks, ChainMemory, SpringPull,
				CampfireShuffle, RequireTwoPlayers, DisableInAirport, Enabled, ToggleByHost, HardTether, TetherHardPull, PullRescue, PullRescueWeb, PullRecoverFall,
				PullFlatDrag
			};
			SyncInt = new ConfigEntry<int>[1] { ChainMaxPoints };
			PluginConfig = ((BaseUnityPlugin)this).Config;
			MigrateLegacyKeys();
			FixStaminaScale();
			ApplyDifficulty();
			new Harmony("com.quaree.fatebound").PatchAll(typeof(FateBoundPatch));
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} v{1} 已加载,难度={2},切换键={3}", "命运绑定", "0.5.2", Difficulty.Value, ToggleKey.Value));
		}

		internal static void EnforceHostChainToggle()
		{
			try
			{
				if (hostEnabledKnown && PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient && Enabled != null && Enabled.Value != hostEnabled)
				{
					Enabled.Value = hostEnabled;
					FateBoundController.FateNotify(hostEnabled ? Loc.T("锁链启停由房主统一控制 —— 客机改不掉(已恢复:开)", "Chain on/off is host-controlled — clients can't override it (restored: ON)") : Loc.T("锁链启停由房主统一控制 —— 客机改不掉(已恢复:关)", "Chain on/off is host-controlled — clients can't override it (restored: OFF)"));
				}
			}
			catch
			{
			}
		}

		internal static void EnforceHostVisuals()
		{
			if (hostVisualsKnown && PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient && DrawChain != null && DrawChain.Value != hostDrawChain)
			{
				DrawChain.Value = hostDrawChain;
				FateBoundController.FateNotify(Loc.T("链条显示由房主统一控制 —— 客机改不掉", "Chain visibility is host-controlled — clients can't override it"));
			}
		}

		private void Update()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			WatchConfig();
			Loc.Refresh();
			EnforceHostVisuals();
			EnforceHostChainToggle();
			if (ChainColorCycleKey != null && (int)ChainColorCycleKey.Value != 0 && SafeInput.GetKeyDown(ChainColorCycleKey.Value))
			{
				CycleChainColor();
			}
			if (DebugKey != null && (int)DebugKey.Value != 0 && SafeInput.GetKeyDown(DebugKey.Value))
			{
				DebugRequested = true;
			}
			if (ToggleKey == null || Enabled == null)
			{
				return;
			}
			KeyCode value = ToggleKey.Value;
			if ((int)value != 0 && SafeInput.GetKeyDown(value))
			{
				if (ToggleByHost != null && ToggleByHost.Value && PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient)
				{
					FateBoundController.FateNotify(Loc.T("锁链启停由房主统一控制 —— 客机按 F9 无效", "Chain on/off is host-controlled — F9 does nothing for clients"));
					return;
				}
				Enabled.Value = !Enabled.Value;
				ForceSyncNow = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)(Enabled.Value ? "命运绑定:命运锁链已开启" : "命运绑定:命运锁链已关闭"));
			}
		}

		internal static void LogLine(string msg)
		{
			try
			{
				if ((Object)(object)Instance != (Object)null)
				{
					((BaseUnityPlugin)Instance).Logger.LogInfo((object)msg);
				}
			}
			catch
			{
			}
		}

		private static void FixStaminaScale()
		{
			try
			{
				if (PullMinStamina != null && PullMinStamina.Value > 2f)
				{
					float value = Mathf.Clamp(PullMinStamina.Value / 100f, 0.01f, 1f);
					LogLine("「拉人最低体力」按 0~1 量纲换算:" + PullMinStamina.Value + " → " + value);
					PullMinStamina.Value = value;
				}
				if (PullStaminaPerMeter != null && PullStaminaPerMeter.Value > 2f)
				{
					float value2 = Mathf.Clamp(PullStaminaPerMeter.Value / 100f, 0.001f, 1f);
					LogLine("「每米体力」按 0~1 量纲换算:" + PullStaminaPerMeter.Value + " → " + value2);
					PullStaminaPerMeter.Value = value2;
				}
				if (PullStaminaPerSecond != null && PullStaminaPerSecond.Value > 2f)
				{
					float value3 = Mathf.Clamp(PullStaminaPerSecond.Value / 100f, 0.001f, 1f);
					LogLine("「拉人每秒体力消耗」按 0~1 量纲换算:" + PullStaminaPerSecond.Value + " → " + value3);
					PullStaminaPerSecond.Value = value3;
				}
			}
			catch
			{
			}
		}

		internal static void LogWarn(string msg)
		{
			try
			{
				if ((Object)(object)Instance != (Object)null)
				{
					((BaseUnityPlugin)Instance).Logger.LogWarning((object)msg);
				}
			}
			catch
			{
			}
		}

		private void MigrateLegacyKeys()
		{
			int migrated = 0;
			MigrateKey<bool>("FateBound", "Enabled", Enabled, ref migrated);
			MigrateKey<KeyCode>("FateBound", "ToggleKey", ToggleKey, ref migrated);
			MigrateKey<FateDifficulty>("FateBound", "Difficulty", Difficulty, ref migrated);
			MigrateKey<float>("FateBound", "MaxDistance", MaxDistance, ref migrated);
			MigrateKey<float>("FateBound", "GraceSeconds", GraceSeconds, ref migrated);
			MigrateKey<float>("FateBound", "PoisonPerSecond", PoisonPerSecond, ref migrated);
			MigrateKey<float>("FateBound", "ShareRatio", ShareRatio, ref migrated);
			MigrateKey<bool>("FateBound", "ShareHunger", ShareHunger, ref migrated);
			MigrateKey<bool>("FateBound", "ShareInjury", ShareInjury, ref migrated);
			MigrateKey<bool>("FateBound", "SharePoison", SharePoison, ref migrated);
			MigrateKey<bool>("FateBound", "DrawChain", DrawChain, ref migrated);
			MigrateKey<float>("FateBound", "ChainWidth", ChainWidth, ref migrated);
			MigrateKey<bool>("FateBound", "RequireTwoPlayers", RequireTwoPlayers, ref migrated);
			MigrateKey<bool>("FateBound", "DisableInAirport", DisableInAirport, ref migrated);
			MigrateKey<bool>("FateBound", "NotifyShared", NotifyShared, ref migrated);
			MigrateKey<KeyCode>("FateBound", "DebugKey", DebugKey, ref migrated);
			MigrateKey<bool>("FateBound.Chain", "ChainPhysics", ChainPhysics, ref migrated);
			MigrateKey<float>("FateBound.Chain", "ChainSpacing", ChainSpacing, ref migrated);
			MigrateKey<float>("FateBound.Chain", "ChainThickness", ChainThickness, ref migrated);
			MigrateKey<float>("FateBound.Chain", "ChainSlack", ChainSlack, ref migrated);
			MigrateKey<float>("FateBound.Chain", "ChainMaxSag", ChainMaxSag, ref migrated);
			MigrateKey<float>("FateBound.Chain", "ChainHideRadius", ChainHideRadius, ref migrated);
			MigrateKey<int>("FateBound.Chain", "ChainMaxPoints", ChainMaxPoints, ref migrated);
			MigrateKey<int>("命运绑定.链条", "链节上限", ChainMaxPoints, ref migrated);
			MigrateKey<FateChainTopology>("FateBound.Chain", "ChainTopology", ChainTopology, ref migrated);
			MigrateKey<bool>("FateBound.Chain", "ChainLinkTexture", ChainLinkTexture, ref migrated);
			MigrateKey<float>("FateBound.Chain", "ChainTextureScale", ChainTextureScale, ref migrated);
			MigrateKey<bool>("FateBound.Chain", "ChainTautFeedback", ChainTautFeedback, ref migrated);
			MigrateKey<string>("FateBound.Chain", "ChainColor", ChainColor, ref migrated);
			MigrateKey<KeyCode>("FateBound.Chain", "ChainColorCycleKey", ChainColorCycleKey, ref migrated);
			MigrateKey<bool>("FateBound.Sync", "SyncFromHost", SyncFromHost, ref migrated);
			MigrateKey<float>("FateBound.Sync", "SyncIntervalSeconds", SyncIntervalSeconds, ref migrated);
			MigrateKey<bool>("FateBound.Tug", "EnableTug", EnableTug, ref migrated);
			MigrateKey<float>("FateBound.Tug", "TugStartDistance", TugStartDistance, ref migrated);
			MigrateKey<float>("FateBound.Tug", "ChainTautRatio", ChainTautRatio, ref migrated);
			MigrateKey<float>("FateBound.Tug", "MaxPullForce", MaxPullForce, ref migrated);
			MigrateKey<float>("FateBound.Tug", "MaxDragForce", MaxDragForce, ref migrated);
			MigrateKey<float>("FateBound.Tug", "DragSpeedDiff", DragSpeedDiff, ref migrated);
			MigrateKey<float>("FateBound.Tug", "DragRampSeconds", DragRampSeconds, ref migrated);
			if (migrated > 0)
			{
				try
				{
					((BaseUnityPlugin)this).Config.Save();
				}
				catch
				{
				}
				LogLine($"已把 {migrated} 项旧版英文配置迁移到中文键名(旧键已从配置文件里移除)。");
			}
		}

		private void MigrateKey<T>(string oldSection, string oldKey, ConfigEntry<T> target, ref int migrated)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			if (target == null)
			{
				return;
			}
			try
			{
				ConfigDefinition val = new ConfigDefinition(oldSection, oldKey);
				ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(oldSection, oldKey, target.Value, "(旧版英文键名,已迁移到对应的中文键,这一项可以忽略)");
				if (!object.Equals(val2.Value, target.Value))
				{
					target.Value = val2.Value;
					migrated++;
				}
				((BaseUnityPlugin)this).Config.Remove(val);
			}
			catch (Exception ex)
			{
				LogWarn("旧配置迁移失败 " + oldSection + "." + oldKey + ":" + ex.Message);
			}
		}

		internal static Color ChainBaseColor()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			string text = ((ChainColor != null) ? ChainColor.Value : null);
			if (text == colorCacheKey)
			{
				return colorCacheVal;
			}
			if (!TryParseHex(text, out var c))
			{
				c = ChainColorFallback;
			}
			colorCacheKey = text;
			colorCacheVal = c;
			return c;
		}

		internal static bool TryParseHex(string s, out Color c)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			c = Color.white;
			if (string.IsNullOrEmpty(s))
			{
				return false;
			}
			s = s.Trim();
			if (s.StartsWith("#"))
			{
				s = s.Substring(1);
			}
			if (s.Length != 6 && s.Length != 8)
			{
				return false;
			}
			if (!int.TryParse(s.Substring(0, 2), NumberStyles.HexNumber, null, out var result))
			{
				return false;
			}
			if (!int.TryParse(s.Substring(2, 2), NumberStyles.HexNumber, null, out var result2))
			{
				return false;
			}
			if (!int.TryParse(s.Substring(4, 2), NumberStyles.HexNumber, null, out var result3))
			{
				return false;
			}
			c = new Color((float)result / 255f, (float)result2 / 255f, (float)result3 / 255f);
			return true;
		}

		internal static void CycleChainColor()
		{
			if (ChainColor == null)
			{
				return;
			}
			string text = (ChainColor.Value ?? "").Trim().ToUpperInvariant();
			int num = -1;
			for (int i = 0; i < ChainColorPresets.Length; i++)
			{
				if (ChainColorPresets[i][0] == text)
				{
					num = i;
					break;
				}
			}
			int num2 = (num + 1) % ChainColorPresets.Length;
			ChainColor.Value = ChainColorPresets[num2][0];
			try
			{
				if ((Object)(object)Instance != (Object)null)
				{
					((BaseUnityPlugin)Instance).Config.Save();
				}
			}
			catch
			{
			}
			FateBoundController.FateNotify(Loc.T("链条颜色:", "Chain colour: ") + ChainColorPresets[num2][(!Loc.EN) ? 1 : 2] + "(" + ChainColorPresets[num2][0] + ")");
		}

		internal static byte[] BuildSyncBlob()
		{
			byte[] blob = new byte[8 + SyncFloat.Length * 4 + 4 + SyncBool.Length + 4 + SyncInt.Length * 4];
			int o = 0;
			PutI(1178753841);
			PutI(SyncFloat.Length);
			for (int i = 0; i < SyncFloat.Length; i++)
			{
				PutI(BitConverter.ToInt32(BitConverter.GetBytes(SyncFloat[i].Value), 0));
			}
			PutI(SyncBool.Length);
			for (int j = 0; j < SyncBool.Length; j++)
			{
				blob[o++] = (SyncBool[j].Value ? ((byte)1) : ((byte)0));
			}
			PutI(SyncInt.Length);
			for (int k = 0; k < SyncInt.Length; k++)
			{
				PutI(SyncInt[k].Value);
			}
			return blob;
			void PutI(int v)
			{
				blob[o++] = (byte)v;
				blob[o++] = (byte)(v >> 8);
				blob[o++] = (byte)(v >> 16);
				blob[o++] = (byte)(v >> 24);
			}
		}

		internal static bool ApplySyncBlob(byte[] blob)
		{
			if (blob == null || blob.Length < 20)
			{
				return false;
			}
			int o = 0;
			if (ReadI() != 1178753841)
			{
				return false;
			}
			if (ReadI() != SyncFloat.Length)
			{
				return false;
			}
			if (o + SyncFloat.Length * 4 > blob.Length)
			{
				return false;
			}
			for (int i = 0; i < SyncFloat.Length; i++)
			{
				SyncFloat[i].Value = BitConverter.ToSingle(blob, o + i * 4);
			}
			o += SyncFloat.Length * 4;
			if (ReadI() != SyncBool.Length)
			{
				return false;
			}
			if (o + SyncBool.Length > blob.Length)
			{
				return false;
			}
			bool value = Enabled.Value;
			for (int j = 0; j < SyncBool.Length; j++)
			{
				SyncBool[j].Value = blob[o + j] != 0;
			}
			o += SyncBool.Length;
			if (DrawChain != null)
			{
				hostDrawChain = DrawChain.Value;
				hostVisualsKnown = true;
			}
			if (Enabled != null)
			{
				hostEnabled = Enabled.Value;
				hostEnabledKnown = true;
			}
			if (Enabled.Value != value)
			{
				FateBoundController.FateNotify(Enabled.Value ? Loc.T("命运锁链被房主开启了", "The Fate Chain was enabled by the host") : Loc.T("命运锁链被房主关闭了", "The Fate Chain was disabled by the host"));
			}
			if (ReadI() != SyncInt.Length)
			{
				return false;
			}
			if (o + SyncInt.Length * 4 > blob.Length)
			{
				return false;
			}
			for (int k = 0; k < SyncInt.Length; k++)
			{
				SyncInt[k].Value = BitConverter.ToInt32(blob, o + k * 4);
			}
			return true;
			int ReadI()
			{
				int result = blob[o] | (blob[o + 1] << 8) | (blob[o + 2] << 16) | (blob[o + 3] << 24);
				o += 4;
				return result;
			}
		}

		internal static string DifficultyLabel(FateDifficulty d)
		{
			return Loc.Difficulty(d);
		}

		private static void MarkConfigChanged()
		{
			if (!PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient)
			{
				ForceSyncNow = true;
			}
		}

		internal static void WatchConfig()
		{
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < cfgPollAt)
			{
				return;
			}
			cfgPollAt = unscaledTime + 1f;
			try
			{
				if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient && SyncFromHost != null && SyncFromHost.Value)
				{
					cfgWatched = true;
					return;
				}
				if (cfgWatched && Difficulty != null && Difficulty.Value != cfgLastDifficulty)
				{
					cfgLastDifficulty = Difficulty.Value;
					ApplyDifficulty();
					MarkConfigChanged();
					FateBoundController.FateNotify(Loc.T("难度已切换:", "Difficulty changed: ") + DifficultyLabel(Difficulty.Value));
				}
				if (PluginConfig != null && !string.IsNullOrEmpty(PluginConfig.ConfigFilePath) && File.Exists(PluginConfig.ConfigFilePath))
				{
					string text = File.ReadAllText(PluginConfig.ConfigFilePath);
					if (cfgWatched && text != cfgLastText)
					{
						PluginConfig.Reload();
						ApplyDifficulty();
						cfgLastDifficulty = Difficulty.Value;
						MarkConfigChanged();
						FateBoundController.FateNotify(Loc.T("配置已重新载入\u3000难度:", "Config reloaded — difficulty: ") + DifficultyLabel(Difficulty.Value));
						text = File.ReadAllText(PluginConfig.ConfigFilePath);
					}
					cfgLastText = text;
				}
				cfgWatched = true;
			}
			catch (Exception ex)
			{
				cfgWatched = true;
				LogWarn("配置热重载失败:" + ex.Message);
			}
		}

		internal static void ApplyDifficulty()
		{
			if (Difficulty != null && Difficulty.Value != FateDifficulty.Custom)
			{
				switch (Difficulty.Value)
				{
				case FateDifficulty.Easy:
					MaxDistance.Value = 50f;
					PoisonPerSecond.Value = 0.02f;
					ShareRatio.Value = 0.6f;
					break;
				case FateDifficulty.Hell:
					MaxDistance.Value = 15f;
					PoisonPerSecond.Value = 0.07f;
					ShareRatio.Value = 1f;
					break;
				default:
					MaxDistance.Value = 30f;
					PoisonPerSecond.Value = 0.035f;
					ShareRatio.Value = 1f;
					break;
				}
			}
		}
	}
	public enum FateLanguage
	{
		English,
		Chinese,
		Auto
	}
	internal static class Loc
	{
		internal static ConfigEntry<FateLanguage> Language;

		internal static bool EN;

		private static bool sysProbed;

		private static bool sysEnglish = true;

		private static string cfgPath;

		private static float pollAt;

		private static bool fileLangKnown;

		private static FateLanguage fileLang = FateLanguage.English;

		internal static void Refresh()
		{
			WatchFile();
			switch (fileLangKnown ? fileLang : ((Language != null) ? Language.Value : FateLanguage.English))
			{
			case FateLanguage.Chinese:
				EN = false;
				break;
			case FateLanguage.English:
				EN = true;
				break;
			default:
				EN = SystemEnglish();
				break;
			}
		}

		private static void WatchFile()
		{
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < pollAt)
			{
				return;
			}
			pollAt = unscaledTime + 1f;
			try
			{
				if (string.IsNullOrEmpty(cfgPath))
				{
					ConfigFile val = (((Object)(object)Plugin.Instance != (Object)null) ? ((BaseUnityPlugin)Plugin.Instance).Config : null);
					if (val == null || string.IsNullOrEmpty(val.ConfigFilePath))
					{
						return;
					}
					cfgPath = val.ConfigFilePath;
				}
				if (!File.Exists(cfgPath))
				{
					return;
				}
				string[] array = File.ReadAllLines(cfgPath);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length < 9 || text[0] == '#' || !text.StartsWith("Language", StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					int num = text.IndexOf('=');
					if (num >= 0)
					{
						string text2 = text.Substring(num + 1).Trim();
						if (text2.StartsWith("Chinese", StringComparison.OrdinalIgnoreCase))
						{
							fileLang = FateLanguage.Chinese;
							fileLangKnown = true;
						}
						else if (text2.StartsWith("English", StringComparison.OrdinalIgnoreCase))
						{
							fileLang = FateLanguage.English;
							fileLangKnown = true;
						}
						else if (text2.StartsWith("Auto", StringComparison.OrdinalIgnoreCase))
						{
							fileLang = FateLanguage.Auto;
							fileLangKnown = true;
						}
						break;
					}
				}
			}
			catch
			{
			}
		}

		private static bool SystemEnglish()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			if (sysProbed)
			{
				return sysEnglish;
			}
			sysProbed = true;
			try
			{
				SystemLanguage systemLanguage = Application.systemLanguage;
				if ((int)systemLanguage == 6 || systemLanguage - 40 <= 1)
				{
					sysEnglish = false;
				}
				else
				{
					sysEnglish = true;
				}
			}
			catch
			{
				sysEnglish = true;
			}
			return sysEnglish;
		}

		internal static string T(string zh, string en)
		{
			if (!EN)
			{
				return zh;
			}
			return en;
		}

		internal static string Difficulty(FateDifficulty d)
		{
			return d switch
			{
				FateDifficulty.Easy => T("简单", "Easy"), 
				FateDifficulty.Hell => T("地狱", "Hell"), 
				FateDifficulty.Custom => T("自定义", "Custom"), 
				_ => T("普通", "Normal"), 
			};
		}
	}
	[HarmonyPatch(typeof(Character), "Awake")]
	public static class FateBoundPatch
	{
		[HarmonyPostfix]
		public static void AwakePatch(Character __instance)
		{
			if ((Object)(object)((Component)__instance).GetComponent<FateBoundController>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<FateBoundController>();
			}
		}
	}
	public class FateBoundController : MonoBehaviour
	{
		private class ChainLink
		{
			public LineRenderer line;

			public Character lastA;

			public Character lastB;

			public PhysicsChain chain;

			public Character a;

			public Character b;

			public MeshFilter linkFilter;

			public MeshRenderer linkRend;

			public Mesh linkMesh;

			public Material linkMat;

			public float playerDist = float.MaxValue;

			public bool solidDemoted;

			public MaterialPropertyBlock mpb;

			public Color lastColor = new Color(-1f, -1f, -1f, -1f);
		}

		private const int IDX_INJURY = 0;

		private const int IDX_HUNGER = 1;

		private const int IDX_POISON = 3;

		private const float POISON_FLUSH = 0.02f;

		private const float INJURY_ADD_SCALE = 0.125f;

		private Character ch;

		private CharacterAfflictions aff;

		private const int MaxChainCount = 16;

		private const int ChainPointHardCap = 400;

		private readonly List<ChainLink> links = new List<ChainLink>();

		private readonly List<Character> pairBuf = new List<Character>();

		private readonly List<Character> ringOrder = new List<Character>();

		private bool[] ringUsed = new bool[0];

		private Camera cam;

		private readonly List<Character> players = new List<Character>();

		private Character nearest;

		private Character pullTarget;

		private Character aimHint;

		private readonly List<Character> avoidBuf = new List<Character>();

		private int shuffleSeed;

		private readonly List<Character> lastPairsBuf = new List<Character>();

		private object lastSegment;

		private bool segmentKnown;

		private static PropertyInfo segmentProp;

		private static bool segmentPropTried;

		private float nextSegmentCheck;

		private float pullNoticeAt;

		private float pullNoticeAgainAt;

		private bool pullWarnedOut;

		private float nearestDist;

		private Character reelOwner;

		private float ropeLength;

		private float pullHoldLeft;

		private float victimStuckFor;

		private float pullStalledFor;

		private float stalledNoticeAt;

		private bool mantleActive;

		private float mantleFor;

		private Vector3 mantleTarget;

		private float ledgeFor;

		private float heldUntil;

		private float rescueCd;

		private float unfallCd;

		private Vector3 pullForceAccum;

		private Vector3 pullForceAt;

		private bool pullForceAny;

		private Vector3 pendingPullForce;

		private Character pendingPullTarget;

		private bool flatDrag;

		private const float ImpulsePerSecond = 60f;

		private float graceTimer;

		private float poisonAccum;

		private float prevTeamMaxInjury;

		private float prevTeamMaxHunger;

		private float prevTeamMaxPoison;

		private bool sharePrimed;

		private static bool announcedThisSession;

		private bool shareFlagsInit;

		private bool lastShareInjury;

		private bool lastShareHunger;

		private bool lastSharePoison;

		private Vector3[] velBuf = (Vector3[])(object)new Vector3[8];

		private Vector3[] lastPosBuf = (Vector3[])(object)new Vector3[8];

		private bool velPrimed;

		private float dragAccum;

		private static string selfCheckText = null;

		private static float selfCheckAt = -99f;

		private static GUIStyle selfCheckStyle = null;

		private static GUIStyle selfCheckTitle = null;

		private Vector3 lastCenter;

		private bool lastCenterReady;

		private float tetherHoldFor;

		private float tetherLastStep;

		private float tetherWarnAt;

		private float tetherBlockedFor;

		private bool tetherBlockedNow;

		private Vector3 tetherMove;

		private readonly RaycastHit[] tetherHitBuf = (RaycastHit[])(object)new RaycastHit[8];

		private readonly int[] meshCd = new int[64];

		private bool hardWarned;

		private float hardWarnedAt;

		private int lastRawCount;

		private int lastBotCount;

		private int lastGhostCount;

		private int lastInactiveCount;

		private bool soloPullLatch;

		private bool active;

		private int pairsFrame = -1;

		private static Texture2D chainLinkTex;

		private float syncTimer;

		private Character aimedCache;

		private int aimedFrame = -1;

		private float notifyInjuryAt;

		private float notifyHungerAt;

		private float notifyPoisonAt;

		private static UI_Notifications uiNotify;

		private bool tautNotified;

		private float tautNotifyAt;

		private static GUIStyle sigStyle;

		private int diagUpd;

		private int diagDrawEnter;

		private int diagDrawDone;

		private int diagMeshBuilds;

		private int diagErrCount;

		private int diagErrLogged;

		private int diagLinkErr;

		private string diagLastError = "无 / none";

		private string diagPanelText = "(进关卡后开始采集 / collecting once you are in a run)";

		private float diagT;

		private int diagConsoleSkip;

		private string diagFilePath;

		private static readonly List<Vector3> linkVerts = new List<Vector3>(16384);

		private static readonly List<Vector3> linkNorms = new List<Vector3>(16384);

		private static readonly List<int> linkTris = new List<int>(32768);

		private const int LinkMajorSegs = 10;

		private const int LinkMinorSegs = 6;

		private const int MaxLinkMeshes = 220;

		private const float LinkPitchScale = 1.12f;

		private static Material sharedLinkMat;

		private static int linkColorBaseId;

		private static int linkColorId;

		private static bool linkColorProbed;

		private static Material sharedLineMat;

		private static bool sharedLineTextured;

		private static float sharedLineScale = -1f;

		private void Awake()
		{
			ch = ((Component)this).GetComponent<Character>();
		}

		private void SyncTick()
		{
			if (Plugin.SyncFromHost == null || !Plugin.SyncFromHost.Value || (Object)(object)ch == (Object)null || !ch.IsLocal)
			{
				return;
			}
			float num = ((Plugin.SyncIntervalSeconds != null) ? Mathf.Max(1f, Plugin.SyncIntervalSeconds.Value) : 5f);
			syncTimer -= Time.deltaTime;
			if (Plugin.ForceSyncNow)
			{
				Plugin.ForceSyncNow = false;
				syncTimer = 0f;
			}
			if (syncTimer > 0f)
			{
				return;
			}
			syncTimer = num;
			try
			{
				if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
				{
					PhotonView component = ((Component)this).GetComponent<PhotonView>();
					if (!((Object)(object)component == (Object)null))
					{
						component.RPC("RPCA_FateBoundSync", (RpcTarget)1, new object[1] { Plugin.BuildSyncBlob() });
					}
				}
			}
			catch
			{
			}
		}

		[PunRPC]
		public void RPCA_FateBoundSync(byte[] blob)
		{
			Plugin.ApplySyncBlob(blob);
		}

		private void Update()
		{
			diagUpd++;
			try
			{
				UpdateInner();
			}
			catch (Exception ex)
			{
				diagErrCount++;
				diagLastError = ex.GetType().Name + ":" + ex.Message + "  @" + FirstStackLine(ex);
				if (diagErrLogged < 3)
				{
					diagErrLogged++;
					Warn("[命运绑定/诊断] 每帧流程抛异常(第 " + diagErrLogged + " 次记录,之后同类只计数):\n" + ex.ToString());
				}
				Hide();
			}
			DiagTick();
		}

		private void UpdateInner()
		{
			active = false;
			if (Plugin.Enabled == null || !Plugin.Enabled.Value)
			{
				Deactivate();
				Hide();
				return;
			}
			if ((Object)(object)ch == (Object)null)
			{
				ch = ((Component)this).GetComponent<Character>();
			}
			if ((Object)(object)ch == (Object)null || ch.isBot || (Object)(object)ch.Ghost != (Object)null)
			{
				Deactivate();
				Hide();
				return;
			}
			SyncTick();
			HandleDebugKey();
			if (Plugin.DisableInAirport != null && Plugin.DisableInAirport.Value && ch.inAirport)
			{
				Deactivate();
				Hide();
				return;
			}
			CollectPlayers();
			if (ch.IsLocal)
			{
				ProbePullKey();
			}
			if ((Plugin.RequireTwoPlayers == null || Plugin.RequireTwoPlayers.Value) && players.Count < 2)
			{
				Deactivate();
				Hide();
				return;
			}
			EnsureAfflictions();
			if ((Object)(object)aff == (Object)null)
			{
				Deactivate();
				Hide();
				return;
			}
			active = true;
			if (ch.IsLocal && !announcedThisSession)
			{
				announcedThisSession = true;
				FateNotify(Loc.T("命运绑定 v0.5.2 已生效\u3000F9 开关\u3000F10 读数", "FateBound v0.5.2 is active   F9 toggle   F10 readout"));
				Plugin.LogLine("命运绑定 v0.5.2 已在关卡内生效");
			}
			if (ch.IsLocal)
			{
				ShareStatuses();
			}
			if (ch.IsLocal)
			{
				HandlePull();
			}
			if (ch.IsLocal)
			{
				RescueTick();
			}
			DistancePoison();
			if (ch.IsLocal)
			{
				PreparePairs();
			}
			if (Plugin.DrawChain != null && Plugin.DrawChain.Value)
			{
				Draw();
			}
			else
			{
				Hide();
			}
		}

		private void PreparePairs()
		{
			if (pairsFrame != Time.frameCount)
			{
				pairsFrame = Time.frameCount;
				CheckCampfireAdvance();
				BuildPairs();
			}
		}

		private void CollectPlayers()
		{
			players.Clear();
			lastRawCount = (lastBotCount = (lastGhostCount = (lastInactiveCount = 0)));
			List<Character> allCharacters = Character.AllCharacters;
			if (allCharacters == null)
			{
				return;
			}
			lastRawCount = allCharacters.Count;
			bool flag = Plugin.LooseCollect != null && Plugin.LooseCollect.Value;
			for (int i = 0; i < allCharacters.Count; i++)
			{
				Character val = allCharacters[i];
				if ((Object)(object)val == (Object)null)
				{
					lastInactiveCount++;
				}
				else if (flag)
				{
					players.Add(val);
				}
				else if (val.isBot)
				{
					lastBotCount++;
				}
				else if ((Object)(object)val.Ghost != (Object)null)
				{
					lastGhostCount++;
				}
				else if (!((Component)val).gameObject.activeInHierarchy)
				{
					lastInactiveCount++;
				}
				else
				{
					players.Add(val);
				}
			}
		}

		private void ProbePullKey()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			try
			{
				flag = Plugin.PullKey != null && SafeInput.GetKey(Plugin.PullKey.Value);
			}
			catch
			{
			}
			bool num = flag && !soloPullLatch;
			soloPullLatch = flag;
			if (num && players.Count < 2)
			{
				FateNotify(Loc.T("命运绑定:现在只采集到 " + players.Count + " 人,拉人需要至少 2 人(链条也没画)。按 F10 看自检面板。", "FateBound: only " + players.Count + " player(s) — pulling needs 2+. Press F10 for details."));
			}
		}

		private void EnsureAfflictions()
		{
			if ((Object)(object)aff == (Object)null)
			{
				aff = ((Component)ch).GetComponent<CharacterAfflictions>();
			}
		}

		private void ShareStatuses()
		{
			if (!sharePrimed)
			{
				prevTeamMaxInjury = TeamMax(0);
				prevTeamMaxHunger = TeamMax(1);
				prevTeamMaxPoison = TeamMax(3);
				sharePrimed = true;
				return;
			}
			bool flag = Plugin.ShareInjury != null && Plugin.ShareInjury.Value;
			bool flag2 = Plugin.ShareHunger != null && Plugin.ShareHunger.Value;
			bool flag3 = Plugin.SharePoison != null && Plugin.SharePoison.Value;
			if (!shareFlagsInit)
			{
				shareFlagsInit = true;
				lastShareInjury = flag;
				lastShareHunger = flag2;
				lastSharePoison = flag3;
			}
			else
			{
				if (flag != lastShareInjury)
				{
					prevTeamMaxInjury = TeamMax(0);
					lastShareInjury = flag;
				}
				if (flag2 != lastShareHunger)
				{
					prevTeamMaxHunger = TeamMax(1);
					lastShareHunger = flag2;
				}
				if (flag3 != lastSharePoison)
				{
					prevTeamMaxPoison = TeamMax(3);
					lastSharePoison = flag3;
				}
			}
			if (flag)
			{
				CatchUp((STATUSTYPE)0, ref prevTeamMaxInjury);
			}
			if (flag2)
			{
				CatchUp((STATUSTYPE)1, ref prevTeamMaxHunger);
			}
			if (flag3)
			{
				CatchUp((STATUSTYPE)3, ref prevTeamMaxPoison);
			}
		}

		private float TeamMax(int idx)
		{
			float num = 0f;
			for (int i = 0; i < players.Count; i++)
			{
				CharacterAfflictions component = ((Component)players[i]).GetComponent<CharacterAfflictions>();
				if (!((Object)(object)component == (Object)null) && component.currentStatuses != null && idx < component.currentStatuses.Length)
				{
					float num2 = component.currentStatuses[idx];
					if (num2 > num)
					{
						num = num2;
					}
				}
			}
			return num;
		}

		private void CatchUp(STATUSTYPE type, ref float prevMax)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Expected I4, but got Unknown
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			int idx = (int)type;
			float num = TeamMax(idx);
			float num2 = num - prevMax;
			prevMax = num;
			if (num2 <= 0.0001f)
			{
				return;
			}
			float currentStatus = aff.GetCurrentStatus(type);
			if (!(currentStatus >= num))
			{
				float num3 = Mathf.Clamp01((Plugin.ShareRatio != null) ? Plugin.ShareRatio.Value : 1f);
				float num4 = Mathf.Min(num2, num - currentStatus) * num3;
				if (!(num4 <= 0.0001f))
				{
					float num5 = (((int)type == 0) ? 0.125f : 1f);
					aff.AddStatus(type, num4 * num5, false, false, false, false, false);
					NotifyShared(type, num4);
				}
			}
		}

		private void DistancePoison()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			Character val = nearest;
			Character val2 = null;
			float num = float.MaxValue;
			for (int i = 0; i < players.Count; i++)
			{
				Character val3 = players[i];
				if (!((Object)(object)val3 == (Object)(object)ch))
				{
					float num2 = Vector3.Distance(ch.Center, val3.Center);
					if ((Object)(object)val3 == (Object)(object)val)
					{
						num2 -= 2f;
					}
					if (num2 < num)
					{
						num = num2;
						val2 = val3;
					}
				}
			}
			nearest = val2;
			nearestDist = (((Object)(object)nearest != (Object)null) ? Vector3.Distance(ch.Center, nearest.Center) : float.MaxValue);
			if ((Object)(object)nearest == (Object)null)
			{
				ResetPenalty();
				return;
			}
			SampleSpeeds();
			float num3 = ((Plugin.MaxDistance != null) ? Plugin.MaxDistance.Value : 30f);
			if (nearestDist <= num3)
			{
				ResetPenalty();
				return;
			}
			float num4 = ((Plugin.GraceSeconds != null) ? Plugin.GraceSeconds.Value : 5f);
			graceTimer += Time.deltaTime;
			if (!(graceTimer < num4))
			{
				float num5 = ((Plugin.PoisonPerSecond != null) ? Plugin.PoisonPerSecond.Value : 0.035f);
				poisonAccum += num5 * Time.deltaTime;
				if (poisonAccum >= 0.02f)
				{
					ApplyPoison(poisonAccum);
					poisonAccum = 0f;
				}
			}
		}

		private void TetherYank(Character other, float over, float away)
		{
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ch == (Object)null || (Object)(object)other == (Object)null || !ch.IsLocal || over <= 0f || over > 100f)
			{
				return;
			}
			Vector3 val = other.Center - ch.Center;
			float magnitude = ((Vector3)(ref val)).magnitude;
			if (magnitude < 0.1f)
			{
				return;
			}
			float num = ((Plugin.TetherPullSpeed != null) ? Mathf.Max(0f, Plugin.TetherPullSpeed.Value) : 1.4f);
			if (num <= 0f)
			{
				return;
			}
			float num2 = ((Plugin.TetherMaxSpeed != null) ? Mathf.Max(0.5f, Plugin.TetherMaxSpeed.Value) : 14f);
			float num3 = num * (1f + over);
			num3 = Mathf.Clamp(num3, num * 0.5f, num2);
			bool num4 = away > 0.3f;
			bool flag = over > 8f;
			bool flag2 = ch.Center.y < other.Center.y - 1.5f;
			if (!num4 && !flag && !flag2)
			{
				num3 *= 0.25f;
			}
			float num5 = num3 * Time.fixedDeltaTime;
			if (num5 > over)
			{
				num5 = over;
			}
			if (num5 < 0.0005f)
			{
				return;
			}
			Vector3 val2 = val / magnitude;
			Vector3 val3 = ch.Center + Vector3.up * 0.35f;
			int num6 = 0;
			try
			{
				num6 = Physics.SphereCastNonAlloc(val3, 0.3f, val2, tetherHitBuf, num5 + 0.3f, -5, (QueryTriggerInteraction)1);
			}
			catch
			{
				num6 = 0;
			}
			for (int i = 0; i < num6; i++)
			{
				Collider collider = ((RaycastHit)(ref tetherHitBuf[i])).collider;
				if (!((Object)(object)collider == (Object)null) && !((Component)collider).transform.IsChildOf(((Component)ch).transform) && (!((Object)(object)other != (Object)null) || !((Component)collider).transform.IsChildOf(((Component)other).transform)))
				{
					num5 *= 0.35f;
					tetherBlockedNow = true;
					break;
				}
			}
			tetherMove += val2 * num5;
		}

		private void ApplyTetherMove()
		{
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			if (tetherBlockedNow)
			{
				tetherBlockedFor += Time.fixedDeltaTime;
			}
			else
			{
				tetherBlockedFor = 0f;
			}
			if (tetherBlockedFor >= 1.5f && Time.time - tetherWarnAt > 8f)
			{
				tetherWarnAt = Time.time;
				tetherBlockedFor = 0f;
				FateNotify(Loc.T("链子被地形挡住了 —— 绕开这块石头/墙,或者先按 F9 松一下", "The chain is blocked by terrain — step around the rock/wall, or press F9 to loosen it"));
			}
			if (((Vector3)(ref tetherMove)).sqrMagnitude <= 1E-08f)
			{
				return;
			}
			if ((Object)(object)ch == (Object)null)
			{
				tetherMove = Vector3.zero;
				return;
			}
			float num = ((Plugin.TetherMaxSpeed != null) ? Mathf.Max(0.5f, Plugin.TetherMaxSpeed.Value) : 14f) * Time.fixedDeltaTime;
			Vector3 val = tetherMove;
			float num2 = ((Vector3)(ref val)).magnitude;
			if (num2 > num)
			{
				val *= num / num2;
				num2 = num;
			}
			tetherMove = Vector3.zero;
			if (num2 < 0.0005f)
			{
				return;
			}
			try
			{
				if (ch.refs != null && (Object)(object)ch.refs.ragdoll != (Object)null)
				{
					ch.refs.ragdoll.MoveAllRigsInDirection(val);
					tetherHoldFor = Time.time;
					tetherLastStep = num2;
				}
			}
			catch
			{
			}
		}

		private void SampleSpeeds()
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: 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_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			int count = players.Count;
			if (count == 0)
			{
				return;
			}
			if (velBuf.Length < count)
			{
				velBuf = (Vector3[])(object)new Vector3[count];
				lastPosBuf = (Vector3[])(object)new Vector3[count];
			}
			float deltaTime = Time.deltaTime;
			if (deltaTime <= 0f)
			{
				return;
			}
			bool flag = !velPrimed;
			velPrimed = true;
			for (int i = 0; i < count; i++)
			{
				Character val = players[i];
				if ((Object)(object)val == (Object)null)
				{
					velBuf[i] = Vector3.zero;
					continue;
				}
				Vector3 center = val.Center;
				if (flag)
				{
					lastPosBuf[i] = center;
					velBuf[i] = Vector3.zero;
					continue;
				}
				Vector3 val2 = (center - lastPosBuf[i]) / deltaTime;
				if (((Vector3)(ref val2)).sqrMagnitude > 400f)
				{
					val2 = Vector3.zero;
				}
				velBuf[i] = Vector3.Lerp(velBuf[i], val2, 0.35f);
				lastPosBuf[i] = center;
			}
		}

		private Vector3 VelOf(Character who)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)who == (Object)null)
			{
				return Vector3.zero;
			}
			int num = players.IndexOf(who);
			if (num < 0 || num >= velBuf.Length)
			{
				return Vector3.zero;
			}
			return velBuf[num];
		}

		private static float PullCurve(float taut)
		{
			if (Plugin.SpringPull != null && !Plugin.SpringPull.Value)
			{
				return taut;
			}
			return taut * taut;
		}

		private static float DampGain()
		{
			return Mathf.Clamp((Plugin.PullDamping != null) ? Plugin.PullDamping.Value : 0.35f, 0f, 2f);
		}

		private Vector3 TugPair(Character other, out float gap)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			gap = 0f;
			if ((Object)(object)other == (Object)null || (Object)(object)other == (Object)(object)ch)
			{
				return Vector3.zero;
			}
			float num = ((Plugin.TugStartDistance != null) ? Plugin.TugStartDistance.Value : 6f);
			Vector3 val = other.Center - ch.Center;
			float magnitude = ((Vector3)(ref val)).magnitude;
			if (magnitude <= num || magnitude < 0.001f)
			{
				return Vector3.zero;
			}
			Vector3 val2 = val / magnitude;
			float taut = Tautness(ch.Center, other.Center);
			float num2 = 0f - Vector3.Dot(VelOf(ch), val2);
			float num3 = Vector3.Dot(VelOf(other), val2);
			gap = num2 - num3;
			float num4 = ((Plugin.DragSpeedDiff != null) ? Plugin.DragSpeedDiff.Value : 0.5f);
			if (gap > num4)
			{
				float num5 = Mathf.Max(0.1f, (Plugin.DragRampSeconds != null) ? Plugin.DragRampSeconds.Value : 4f);
				float num6 = Mathf.Clamp01(dragAccum / num5);
				float num7 = ((Plugin.MaxDragForce != null) ? Plugin.MaxDragForce.Value : 160f) * PullCurve(taut);
				float num8 = num7 * DampGain() * Mathf.Clamp01(gap / 5f);
				return val2 * ((num7 + num8) * num6);
			}
			if (0f - gap > num4)
			{
				float num9 = ((Plugin.MaxPullForce != null) ? Plugin.MaxPullForce.Value : 160f);
				return val2 * (num9 * PullCurve(taut) * Mathf.Clamp01((0f - gap) / 3f));
			}
			return Vector3.zero;
		}

		private void HandlePull()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0288: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.EnablePull == null || !Plugin.EnablePull.Value)
			{
				ResetPullState();
				return;
			}
			if (Plugin.PullKey == null || (int)Plugin.PullKey.Value == 0)
			{
				ResetPullState();
				return;
			}
			float deltaTime = Time.deltaTime;
			if (pullNoticeAt > 0f)
			{
				pullNoticeAt -= deltaTime;
			}
			bool flag = false;
			try
			{
				flag = SafeInput.GetKey(Plugin.PullKey.Value);
			}
			catch
			{
			}
			bool flag2 = Plugin.PullUseReel == null || Plugin.PullUseReel.Value;
			if (!flag)
			{
				if (flag2 && (Object)(object)pullTarget != (Object)null && pullHoldLeft > 0f && IsPullable(pullTarget))
				{
					pullHoldLeft -= deltaTime;
					ReelTick(deltaTime, reeling: false);
					if (pullHoldLeft > 0f)
					{
						return;
					}
				}
				ResetPullState();
				return;
			}
			Character val = AimedPartnerCached();
			if ((Object)(object)val == (Object)null)
			{
				pullTarget = null;
				return;
			}
			if ((Object)(object)val != (Object)(object)pullTarget)
			{
				pullTarget = val;
				pullNoticeAgainAt = 0f;
				pullWarnedOut = false;
			}
			if ((Object)(object)reelOwner != (Object)(object)pullTarget)
			{
				reelOwner = pullTarget;
				ropeLength = 0f;
				victimStuckFor = 0f;
				mantleActive = false;
				mantleFor = 0f;
				ledgeFor = 0f;
			}
			float num = ((Plugin.PullMinDrop != null) ? Plugin.PullMinDrop.Value : 1.2f);
			if (ch.Center.y - pullTarget.Center.y < num)
			{
				if (Plugin.PullFlatDrag == null || !Plugin.PullFlatDrag.Value)
				{
					flatDrag = false;
					if (pullNoticeAt <= 0f)
					{
						pullNoticeAt = 2.5f;
						FateNotify(Loc.T("这条链另一头不比你低 " + num.ToString("F1") + " 米,拉不上来(拉人只会往上提;想平拖去开「高度差不够也平拉」)", "The other end is not " + num.ToString("F1") + " m below you — nothing to lift (enable \"flat drag\" to pull sideways)"));
					}
					return;
				}
				flatDrag = true;
				if (pullNoticeAt <= 0f)
				{
					pullNoticeAt = 3f;
					FateNotify(Loc.T("他不在你下面 —— 只能平着往你这边拖,提不起来", "They aren't below you — dragging them sideways only, no lift"));
				}
			}
			else
			{
				flatDrag = false;
			}
			float num2 = ((Plugin.PullMaxDistance != null) ? Plugin.PullMaxDistance.Value : 18f);
			if (Vector3.Distance(ch.Center, pullTarget.Center) > num2)
			{
				if (pullNoticeAt <= 0f)
				{
					pullNoticeAt = 2.5f;
					FateNotify(Loc.T("离得太远,拉不动(上限 " + num2.ToString("F0") + " 米)", "Too far to pull (limit " + num2.ToString("F0") + " m)"));
				}
				return;
			}
			float num3 = ((Plugin.PullMinStamina != null) ? Plugin.PullMinStamina.Value : 0.1f);
			if (ch.GetTotalStamina() <= num3)
			{
				if (!pullWarnedOut)
				{
					pullWarnedOut = true;
					FateNotify(Loc.T("体力见底,拉不动了 —— 松手歇一会儿会自动回", "Out of stamina — let go for a moment and it regenerates"));
				}
				return;
			}
			pullWarnedOut = false;
			pullHoldLeft = ((Plugin.PullHoldAfterRelease != null) ? Mathf.Max(0f, Plugin.PullHoldAfterRelease.Value) : 1.5f);
			if (flag2)
			{
				ReelTick(deltaTime, reeling: true);
			}
			else
			{
				LegacyPull(deltaTime);
			}
		}

		private void ReelTick(float dt, bool reeling)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0255: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0232: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0391: Unknown result type (might be due to invalid IL or missing references)
			//IL_0314: Unknown result type (might be due to invalid IL or missing references)