Decompiled source of FateBound v0.4.4

plugins/com.quaree.fatebound.dll

Decompiled 12 hours 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.4.4")]
[assembly: AssemblyInformationalVersion("0.4.4")]
[assembly: AssemblyProduct("com.quaree.fatebound")]
[assembly: AssemblyTitle("com.quaree.fatebound")]
[assembly: AssemblyVersion("0.4.4.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.4.4")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.quaree.fatebound";

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

		public const string PluginVersion = "0.4.4";

		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<bool> HardTether;

		internal static ConfigEntry<float> HardLimitScale;

		internal static ConfigEntry<float> HardPullForce;

		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;

		private static bool hostDrawChain;

		private static bool hostVisualsKnown;

		internal static readonly string[][] ChainColorPresets = new string[8][]
		{
			new string[2] { "#C08A4A", "古铜" },
			new string[2] { "#9AA3AD", "钢银" },
			new string[2] { "#E0C36B", "暗金" },
			new string[2] { "#B22222", "深红" },
			new string[2] { "#3CB371", "翠绿" },
			new string[2] { "#5B8BD4", "湖蓝" },
			new string[2] { "#8A6FD1", "紫晶" },
			new string[2] { "#2E2E2E", "玄铁黑" }
		};

		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_0a19: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a67: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			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嫌不够硬就往上加,嫌人被打得乱晃就往下调。");
			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 可关掉这项优化。");
			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>("命运绑定.拉人", "拉人每秒体力消耗", 20f, "拉人时每秒掉多少体力。PEAK 满体力通常是 100 —— 所以 20 大约能连续拉 5 秒。\n调到 8 可以拉很久,调到 60 就只能拽两下。");
			PullMinDrop = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人", "拉人最小高度差", 1.2f, "你对着的这条链,另一头至少要低于你这么多米,才拉得动。\n原因是力的方向固定朝你这边(只会往上提),另一头比你高时这一拉等于把人往下拽。\n不满足时只弹提示、**不会偷偷换成别人** —— 你指着谁就是谁。");
			PullMaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人", "拉人最远距离", 18f, "超过这个距离就拉不动了(米)。默认 18 —— 比「拖拽起始距离」6 米宽松,\n因为掉进坑里的队友经常离得很远。");
			PullMinStamina = ((BaseUnityPlugin)this).Config.Bind<float>("命运绑定.拉人", "拉人最低体力", 8f, "体力低于这个值就拉不动了(会提示一次)。防止体力见底还能无限拉。");
			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这是本地显示偏好,不参与主机同步。关掉则链子只按绷紧度变色。");
			SyncFloat = new ConfigEntry<float>[19]
			{
				MaxDistance, PoisonPerSecond, GraceSeconds, ShareRatio, ChainWidth, ChainThickness, ChainSpacing, ChainSlack, ChainMaxSag, ChainHideRadius,
				ChainTextureScale, TugStartDistance, MaxPullForce, MaxDragForce, DragSpeedDiff, DragRampSeconds, LinkDiameter, LinkWire, PullDamping
			};
			SyncBool = new ConfigEntry<bool>[15]
			{
				ShareHunger, ShareInjury, SharePoison, DrawChain, ChainPhysics, EnableTug, ChainLinkTexture, SolidChainLinks, ChainMemory, SpringPull,
				CampfireShuffle, RequireTwoPlayers, DisableInAirport, Enabled, ToggleByHost
			};
			SyncInt = new ConfigEntry<int>[1] { ChainMaxPoints };
			PluginConfig = ((BaseUnityPlugin)this).Config;
			MigrateLegacyKeys();
			ApplyDifficulty();
			new Harmony("com.quaree.fatebound").PatchAll(typeof(FateBoundPatch));
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} v{1} 已加载,难度={2},切换键={3}", "命运绑定", "0.4.4", Difficulty.Value, ToggleKey.Value));
		}

		internal static void EnforceHostVisuals()
		{
			if (hostVisualsKnown && PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient && DrawChain != null && DrawChain.Value != hostDrawChain)
			{
				DrawChain.Value = hostDrawChain;
				FateBoundController.FateNotify("链条显示由房主统一控制 —— 客机改不掉");
			}
		}

		private void Update()
		{
			//IL_0016: 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_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: 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)
			WatchConfig();
			EnforceHostVisuals();
			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("锁链启停由房主统一控制 —— 客机按 F9 无效");
					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
			{
			}
		}

		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("链条颜色:" + ChainColorPresets[num2][1] + "(" + 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.Value != value)
			{
				FateBoundController.FateNotify(Enabled.Value ? "命运锁链被房主开启了" : "命运锁链被房主关闭了");
			}
			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 d switch
			{
				FateDifficulty.Easy => "简单", 
				FateDifficulty.Hell => "地狱", 
				FateDifficulty.Custom => "自定义", 
				_ => "普通", 
			};
		}

		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("难度已切换:" + 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("配置已重新载入\u3000难度:" + 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;
				}
			}
		}
	}
	[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;
		}

		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 = 200;

		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 pullNoticeAt;

		private float pullNoticeAgainAt;

		private bool pullWarnedOut;

		private float nearestDist;

		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 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 static Texture2D chainLinkTex;

		private float syncTimer;

		private float notifyInjuryAt;

		private float notifyHungerAt;

		private float notifyPoisonAt;

		private static UI_Notifications uiNotify;

		private bool tautNotified;

		private float tautNotifyAt;

		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 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()
		{
			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("命运绑定 v0.4.4 已生效\u3000F9 开关\u3000F10 读数");
				Plugin.LogLine("命运绑定 v0.4.4 已在关卡内生效");
			}
			if (ch.IsLocal)
			{
				ShareStatuses();
			}
			if (ch.IsLocal)
			{
				HandlePull();
			}
			DistancePoison();
			if (Plugin.DrawChain != null && Plugin.DrawChain.Value)
			{
				Draw();
			}
			else
			{
				Hide();
			}
		}

		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("命运绑定:现在只采集到 " + players.Count + " 人,拉人需要至少 2 人(链条也没画)。按 F10 看自检面板。");
			}
		}

		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_008c: 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);
					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 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_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: 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_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: 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_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a2: 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)
			if (Plugin.EnablePull == null || !Plugin.EnablePull.Value)
			{
				pullTarget = null;
				return;
			}
			if (Plugin.PullKey == null || (int)Plugin.PullKey.Value == 0)
			{
				pullTarget = null;
				return;
			}
			if (pullNoticeAt > 0f)
			{
				pullNoticeAt -= Time.deltaTime;
			}
			if (!SafeInput.GetKey(Plugin.PullKey.Value))
			{
				pullTarget = null;
				pullWarnedOut = false;
				pullNoticeAgainAt = 0f;
				return;
			}
			Character val = PickAimedPartner();
			if ((Object)(object)val == (Object)null)
			{
				pullTarget = null;
				return;
			}
			if ((Object)(object)val != (Object)(object)pullTarget)
			{
				pullTarget = val;
				pullNoticeAgainAt = 0f;
				pullWarnedOut = false;
			}
			float num = ((Plugin.PullMinDrop != null) ? Plugin.PullMinDrop.Value : 1.2f);
			if (ch.Center.y - pullTarget.Center.y < num)
			{
				if (pullNoticeAt <= 0f)
				{
					pullNoticeAt = 2.5f;
					FateNotify("这条链另一头不比你低 " + num.ToString("F1") + " 米,拉不上来(拉人只会往上提)");
				}
				return;
			}
			float num2 = ((Plugin.PullMaxDistance != null) ? Plugin.PullMaxDistance.Value : 18f);
			if (Vector3.Distance(ch.Center, pullTarget.Center) > num2)
			{
				if (pullNoticeAt <= 0f)
				{
					pullNoticeAt = 2.5f;
					FateNotify("离得太远,拉不动(上限 " + num2.ToString("F0") + " 米)");
				}
				return;
			}
			float num3 = ((Plugin.PullMinStamina != null) ? Plugin.PullMinStamina.Value : 8f);
			if (ch.GetTotalStamina() <= num3)
			{
				if (!pullWarnedOut)
				{
					pullWarnedOut = true;
					FateNotify("体力见底,拉不动了 —— 松手歇一会儿会自动回");
				}
				return;
			}
			pullWarnedOut = false;
			float num4 = ((Plugin.PullStaminaPerSecond != null) ? Plugin.PullStaminaPerSecond.Value : 20f);
			ch.AddStamina((0f - num4) * Time.deltaTime);
			Vector3 val2 = ch.Center - pullTarget.Center;
			float magnitude = ((Vector3)(ref val2)).magnitude;
			if (magnitude < 0.05f)
			{
				return;
			}
			float num5 = ((Plugin.PullForce != null) ? Plugin.PullForce.Value : 190f);
			float num6 = Mathf.Clamp01(magnitude / 3f);
			Vector3 val3 = val2 / magnitude * (num5 * num6);
			try
			{
				((MonoBehaviourPun)pullTarget).photonView.RPC("RPCA_AddForceAtPosition", (RpcTarget)0, new object[3] { val3, pullTarget.Center, 6f });
				if (Plugin.PullNotify != null && Plugin.PullNotify.Value && Time.time >= pullNoticeAgainAt)
				{
					pullNoticeAgainAt = Time.time + 3f;
					((MonoBehaviourPun)pullTarget).photonView.RPC("RPCA_FateBoundPullNotice", (RpcTarget)0, Array.Empty<object>());
				}
			}
			catch
			{
			}
		}

		[PunRPC]
		public void RPCA_FateBoundPullNotice()
		{
			if (!((Object)(object)ch == (Object)null) && ch.IsLocal && (Plugin.PullNotify == null || Plugin.PullNotify.Value))
			{
				FateNotify("有人在拉你上来 —— 对方正在消耗体力");
			}
		}

		private Character PickAimedPartner()
		{
			//IL_001b: 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: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.PullAimRequired != null && !Plugin.PullAimRequired.Value)
			{
				return PickNearestPartner();
			}
			Vector3 val = CameraPosition();
			Vector3 val2 = CameraForward();
			if (((Vector3)(ref val2)).sqrMagnitude < 0.001f)
			{
				return PickNearestPartner();
			}
			float num = Mathf.Cos(Mathf.Clamp((Plugin.PullAimAngle != null) ? Plugin.PullAimAngle.Value : 35f, 5f, 90f) * (MathF.PI / 180f));
			Character result = null;
			float num2 = float.MaxValue;
			for (int i = 0; i < links.Count; i++)
			{
				ChainLink chainLink = links[i];
				if (chainLink == null || chainLink.chain == null)
				{
					continue;
				}
				Character val3 = (((Object)(object)chainLink.a == (Object)(object)ch) ? chainLink.b : (((Object)(object)chainLink.b == (Object)(object)ch) ? chainLink.a : null));
				if (!IsPullable(val3))
				{
					continue;
				}
				Vector3[] points = chainLink.chain.Points;
				if (points == null)
				{
					continue;
				}
				for (int j = 0; j < points.Length; j += 2)
				{
					Vector3 val4 = points[j] - val;
					float num3;
					if (((Vector3)(ref val4)).sqrMagnitude < 0.04f)
					{
						num3 = 0f;
					}
					else
					{
						float num4 = Vector3.Dot(val2, ((Vector3)(ref val4)).normalized);
						if (num4 < num)
						{
							continue;
						}
						num3 = Mathf.Acos(Mathf.Clamp(num4, -1f, 1f));
					}
					if (num3 < num2)
					{
						num2 = num3;
						result = val3;
					}
				}
			}
			return result;
		}

		private Character PickNearestPartner()
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			Character val = null;
			float num = float.MaxValue;
			for (int i = 0; i + 1 < pairBuf.Count; i += 2)
			{
				Character val2 = pairBuf[i];
				Character val3 = pairBuf[i + 1];
				Character val4 = (((Object)(object)val2 == (Object)(object)ch) ? val3 : (((Object)(object)val3 == (Object)(object)ch) ? val2 : null));
				if (IsPullable(val4))
				{
					float num2 = Vector3.Distance(ch.Center, val4.Center);
					if (num2 < num)
					{
						num = num2;
						val = val4;
					}
				}
			}
			if ((Object)(object)val == (Object)null && IsPullable(nearest))
			{
				val = nearest;
			}
			return val;
		}

		private Vector3 CameraForward()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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)
			if ((Object)(object)cam != (Object)null)
			{
				return ((Component)cam).transform.forward;
			}
			Vector3 val = ch.Head - ch.Center;
			if (!(((Vector3)(ref val)).sqrMagnitude > 0.0001f))
			{
				return Vector3.zero;
			}
			return ((Vector3)(ref val)).normalized;
		}

		private bool IsPullable(Character c)
		{
			if ((Object)(object)c == (Object)null || (Object)(object)c == (Object)(object)ch)
			{
				return false;
			}
			if ((Object)(object)c.Ghost != (Object)null)
			{
				return false;
			}
			if (players.Count > 0 && !players.Contains(c))
			{
				return false;
			}
			return true;
		}

		private void ApplyPoison(float amount)
		{
			if (!(amount <= 0f))
			{
				CharacterRefs refs = ch.refs;
				if (refs != null && !((Object)(object)refs.afflictions == (Object)null))
				{
					refs.afflictions.AddStatus((STATUSTYPE)3, amount, false, false, true, false);
				}
			}
		}

		internal static void FateNotify(string msg)
		{
			try
			{
				if ((Object)(object)uiNotify == (Object)null)
				{
					UI_Notifications[] array = Resources.FindObjectsOfTypeAll<UI_Notifications>();
					if (array != null && array.Length != 0)
					{
						uiNotify = array[0];
					}
				}
				if ((Object)(object)uiNotify != (Object)null)
				{
					uiNotify.AddNotification(msg);
				}
			}
			catch
			{
			}
		}

		private void NotifyShared(STATUSTYPE type, float amount)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Invalid comparison between Unknown and I4
			if (Plugin.NotifyShared == null || !Plugin.NotifyShared.Value)
			{
				return;
			}
			float time = Time.time;
			string msg;
			if ((int)type == 0)
			{
				if (time - notifyInjuryAt < 1.2f)
				{
					return;
				}
				notifyInjuryAt = time;
				msg = "命运锁链:队友受伤,你被分摊 -" + Mathf.RoundToInt(amount * 100f) + "%";
			}
			else if ((int)type == 1)
			{
				if (time - notifyHungerAt < 1.2f)
				{
					return;
				}
				notifyHungerAt = time;
				msg = "命运锁链:队友饿了,你的饥饿也上来了 +" + Mathf.RoundToInt(amount * 100f) + "%";
			}
			else
			{
				if (time - notifyPoisonAt < 1.2f)
				{
					return;
				}
				notifyPoisonAt = time;
				msg = "命运锁链:队友中毒,毒素传给你了 +" + Mathf.RoundToInt(amount * 100f) + "%";
			}
			FateNotify(msg);
		}

		private void HandleDebugKey()
		{
			if (!Plugin.DebugRequested || (Object)(object)ch == (Object)null || !ch.IsLocal)
			{
				return;
			}
			Plugin.DebugRequested = false;
			try
			{
				if (Plugin.SelfCheckPanel != null)
				{
					Plugin.SelfCheckPanel.Value = !Plugin.SelfCheckPanel.Value;
				}
			}
			catch
			{
			}
			try
			{
				DumpDebug();
			}
			catch (Exception ex)
			{
				Warn("调试读数失败 - " + ex.Message);
			}
		}

		private static void Warn(string msg)
		{
			Plugin.LogWarn(msg);
		}

		private void DumpDebug()
		{
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: 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_0251: Unknown result type (might be due to invalid IL or missing references)
			if (players.Count == 0)
			{
				CollectPlayers();
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("===== 命运绑定 v0.4.4 调试读数 " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " =====");
			stringBuilder.AppendLine("本机生效=" + active + "  场上玩家=" + players.Count + "  最近队友距离=" + (((Object)(object)nearest != (Object)null) ? (nearestDist.ToString("F1") + "m") : "无"));
			stringBuilder.AppendLine("拖拽开关=" + (Plugin.EnableTug != null && Plugin.EnableTug.Value) + "  绷紧度=" + (((Object)(object)nearest != (Object)null) ? Tautness(ch.Center, nearest.Center).ToString("F2") : "-") + "  (0.00 松 / 1.00 满;≥0.5 就会弹绷紧提示)");
			FateNotify("命运绑定 v0.4.4\u3000生效=" + active + "\u3000读到玩家=" + players.Count);
			if (players.Count == 0)
			{
				FateNotify("没读到玩家:大厅内不生效,请进关卡、两人以上再按 F10");
			}
			for (int i = 0; i < players.Count; i++)
			{
				Character val = players[i];
				if (!((Object)(object)val == (Object)null))
				{
					CharacterAfflictions component = ((Component)val).GetComponent<CharacterAfflictions>();
					string text = ((!((Object)(object)component != (Object)null) || component.currentStatuses == null) ? ("  玩家 " + i + ":状态不可读") : ((((Object)(object)val == (Object)(object)ch) ? "★我   " : "  队友 ") + "受伤 " + St(component, (STATUSTYPE)0) + "  饥饿 " + St(component, (STATUSTYPE)1) + "  中毒 " + St(component, (STATUSTYPE)3) + "  距离 " + Vector3.Distance(ch.Center, val.Center).ToString("F1") + "m"));
					stringBuilder.AppendLine(text);
					FateNotify(text);
				}
			}
			string text2 = stringBuilder.ToString();
			Plugin.LogLine(text2);
			try
			{
				string text3 = null;
				try
				{
					text3 = Paths.BepInExRootPath;
				}
				catch
				{
				}
				if (string.IsNullOrEmpty(text3))
				{
					text3 = Path.Combine(Environment.CurrentDirectory, "BepInEx");
				}
				Directory.CreateDirectory(text3);
				File.AppendAllText(Path.Combine(text3, "fatebound-debug.txt"), text2 + Environment.NewLine);
			}
			catch (Exception ex)
			{
				Warn("写调试文件失败 - " + ex.Message);
			}
		}

		private static string St(CharacterAfflictions a, STATUSTYPE t)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return a.GetCurrentStatus(t).ToString("F3");
		}

		private void FixedUpdate()
		{
			//IL_004a: 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_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: 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_02ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0309: Unknown result type (might be due to invalid IL or missing references)
			//IL_030e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_037d: Unknown result type (might be due to invalid IL or missing references)
			//IL_037e: Unknown result type (might be due to invalid IL or missing references)
			//IL_037f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0384: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: 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_03a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_023f: 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_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_027c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_0286: Unknown result type (might be due to invalid IL or missing references)
			if (!active || (Object)(object)ch == (Object)null)
			{
				dragAccum = 0f;
				return;
			}
			if (Plugin.EnableTug == null || !Plugin.EnableTug.Value)
			{
				dragAccum = 0f;
				return;
			}
			if (lastCenterReady && Vector3.Distance(lastCenter, ch.Center) > 60f)
			{
				lastCenterReady = false;
			}
			if (!lastCenterReady)
			{
				lastCenter = ch.Center;
				lastCenterReady = true;
				return;
			}
			Vector3 val = Vector3.zero;
			Vector3 val2 = Vector3.zero;
			float num = 0f;
			int num2 = 0;
			for (int i = 0; i + 1 < pairBuf.Count; i += 2)
			{
				Character val3 = pairBuf[i];
				Character val4 = pairBuf[i + 1];
				Character val5 = (((Object)(object)val3 == (Object)(object)ch) ? val4 : (((Object)(object)val4 == (Object)(object)ch) ? val3 : null));
				if ((Object)(object)val5 == (Object)null || (Object)(object)val5 == (Object)(object)ch)
				{
					continue;
				}
				num2++;
				val += TugPair(val5, out var gap);
				if (gap > num)
				{
					num = gap;
				}
				if (Plugin.HardTether == null || !Plugin.HardTether.Value)
				{
					continue;
				}
				float num3 = Vector3.Distance(ch.Center, val5.Center);
				float num4 = ((Plugin.MaxDistance != null) ? Plugin.MaxDistance.Value : 30f) * ((Plugin.HardLimitScale != null) ? Mathf.Max(0.2f, Plugin.HardLimitScale.Value) : 1f);
				if (!(num3 > num4) || !lastCenterReady)
				{
					continue;
				}
				Vector3 val6 = ch.Center - val5.Center;
				if (!(((Vector3)(ref val6)).sqrMagnitude > 0.01f))
				{
					continue;
				}
				((Vector3)(ref val6)).Normalize();
				if (Vector3.Dot((ch.Center - lastCenter) / Mathf.Max(0.0001f, Time.fixedDeltaTime), val6) > 0.4f)
				{
					float num5 = num3 - num4;
					float num6 = ((Plugin.HardPullForce != null) ? Plugin.HardPullForce.Value : 520f);
					Vector3 val7 = val2;
					Vector3 val8 = val5.Center - ch.Center;
					val2 = val7 + ((Vector3)(ref val8)).normalized * (num6 * (1f + Mathf.Clamp01(num5 / 8f) * 3f));
					if (!hardWarned && Time.time - hardWarnedAt > 6f)
					{
						hardWarned = true;
						hardWarnedAt = Time.time;
						FateNotify("命运锁链绷到底了 —— 跑不出 " + num4.ToString("F0") + " 米");
					}
				}
			}
			if (num2 == 0 && (Object)(object)nearest != (Object)null)
			{
				val += TugPair(nearest, out var gap2);
				if (gap2 > num)
				{
					num = gap2;
				}
			}
			if (num > 0f)
			{
				dragAccum += Time.fixedDeltaTime;
			}
			else
			{
				dragAccum = 0f;
			}
			float num7 = ((Plugin.VerticalPullScale != null) ? Mathf.Clamp01(Plugin.VerticalPullScale.Value) : 0.35f);
			val.y *= num7;
			if (((Vector3)(ref val2)).sqrMagnitude > 0.0001f)
			{
				val += val2;
				hardWarned = false;
			}
			if (!(((Vector3)(ref val)).sqrMagnitude <= 0.0001f))
			{
				ch.RPCA_AddForceAtPosition(val, ch.Center, 8f);
			}
		}

		private void Deactivate()
		{
			ResetPenalty();
			sharePrimed = false;
		}

		private void ResetPenalty()
		{
			graceTimer = 0f;
			poisonAccum = 0f;
			dragAccum = 0f;
		}

		private void Draw()
		{
			if (!ch.IsLocal)
			{
				Hide();
				return;
			}
			CheckCampfireAdvance();
			BuildPairs();
			bool flag = Plugin.PullAimRequired == null || Plugin.PullAimRequired.Value;
			bool flag2 = Plugin.AimHighlight == null || Plugin.AimHighlight.Value;
			aimHint = ((flag && flag2) ? PickAimedPartner() : null);
			int num = pairBuf.Count / 2;
			while (links.Count < num)
			{
				links.Add(new ChainLink());
			}
			for (int i = num; i < links.Count; i++)
			{
				HideLink(links[i]);
			}
			bool usePhysics = Plugin.ChainPhysics == null || Plugin.ChainPhysics.Value;
			for (int j = 0; j < num; j++)
			{
				DrawOne(links[j], pairBuf[j * 2], pairBuf[j * 2 + 1], usePhysics);
			}
		}

		private void CheckCampfireAdvance()
		{
			if (Plugin.CampfireShuffle == null || !Plugin.CampfireShuffle.Value || !PhotonNetwork.InRoom)
			{
				return;
			}
			if (!segmentPropTried)
			{
				segmentPropTried = true;
				Type type = Type.GetType("MapHandler, Assembly-CSharp");
				if (type == null)
				{
					Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
					for (int i = 0; i < assemblies.Length; i++)
					{
						if (!(type == null))
						{
							break;
						}
						try
						{
							type = assemblies[i].GetType("MapHandler", throwOnError: false);
						}
						catch
						{
						}
					}
				}
				if (type != null)
				{
					segmentProp = type.GetProperty("CurrentSegmentNumber", BindingFlags.Static | BindingFlags.Public);
				}
			}
			if (segmentProp == null)
			{
				return;
			}
			object value;
			try
			{
				value = segmentProp.GetValue(null, null);
			}
			catch
			{
				segmentProp = null;
				return;
			}
			if (value == null)
			{
				return;
			}
			if (!segmentKnown)
			{
				segmentKnown = true;
				lastSegment = value;
			}
			else if (!value.Equals(lastSegment))
			{
				lastSegment = value;
				avoidBuf.Clear();
				for (int j = 0; j < pairBuf.Count; j++)
				{
					avoidBuf.Add(pairBuf[j]);
				}
				shuffleSeed = value.GetHashCode() & 0x7FFFFFFF;
				if (shuffleSeed == 0)
				{
					shuffleSeed = 1;
				}
				FateNotify("命运锁链顺序重排 —— 这一段路重新连过了");
			}
		}

		private bool WasPaired(Character x, Character y)
		{
			for (int i = 0; i + 1 < lastPairsBuf.Count; i += 2)
			{
				if (((Object)(object)lastPairsBuf[i] == (Object)(object)x && (Object)(object)lastPairsBuf[i + 1] == (Object)(object)y) || ((Object)(object)lastPairsBuf[i] == (Object)(object)y && (Object)(object)lastPairsBuf[i + 1] == (Object)(object)x))
				{
					return true;
				}
			}
			return false;
		}

		private bool Avoids(Character x, Character y)
		{
			for (int i = 0; i + 1 < avoidBuf.Count; i += 2)
			{
				if (((Object)(object)avoidBuf[i] == (Object)(object)x && (Object)(object)avoidBuf[i + 1] == (Object)(object)y) || ((Object)(object)avoidBuf[i] == (Object)(object)y && (Object)(object)avoidBuf[i + 1] == (Object)(object)x))
				{
					return true;
				}
			}
			return false;
		}

		private void BuildPairs()
		{
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: 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)
			lastPairsBuf.Clear();
			for (int i = 0; i < pairBuf.Count; i++)
			{
				lastPairsBuf.Add(pairBuf[i]);
			}
			pairBuf.Clear();
			bool flag = false;
			for (int j = 0; j < players.Count; j++)
			{
				if ((Object)(object)players[j] == (Object)(object)ch)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				return;
			}
			switch ((Plugin.ChainTopology != null) ? Plugin.ChainTopology.Value : FateChainTopology.Ring)
			{
			case FateChainTopology.AllPairs:
			{
				for (int k = 0; k < players.Count; k++)
				{
					if (pairBuf.Count >= 32)
					{
						break;
					}
					for (int l = k + 1; l < players.Count; l++)
					{
						if (pairBuf.Count >= 32)
						{
							break;
						}
						pairBuf.Add(players[k]);
						pairBuf.Add(players[l]);
					}
				}
				return;
			}
			case FateChainTopology.NearestWeb:
			{
				for (int m = 0; m < players.Count; m++)
				{
					if (pairBuf.Count >= 32)
					{
						break;
					}
					int num = -1;
					float num2 = float.MaxValue;
					int num3 = -1;
					float num4 = float.MaxValue;
					for (int n = 0; n < players.Count; n++)
					{
						if (n != m)
						{
							float num5 = Vector3.Distance(players[m].Center, players[n].Center);
							if (num5 < num2)
							{
								num2 = num5;
								num = n;
							}
							if (avoidBuf.Count > 0 && !Avoids(players[m], players[n]) && num5 < num4)
							{
								num4 = num5;
								num3 = n;
							}
						}
					}
					int num6 = -1;
					for (int num7 = 0; num7 < players.Count; num7++)
					{
						if (num7 != m && num7 != num && WasPaired(players[m], players[num7]))
						{
							num6 = num7;
							break;
						}
					}
					if (num6 >= 0 && Vector3.Distance(players[m].Center, players[num6].Center) < num2 * 1.15f)
					{
						num = num6;
					}
					if (num3 >= 0)
					{
						num = num3;
					}
					if (num >= 0)
					{
						int num8 = ((m < num) ? m : num);
						int num9 = ((m < num) ? num : m);
						if (!PairExists(num8, num9))
						{
							pairBuf.Add(players[num8]);
							pairBuf.Add(players[num9]);
						}
					}
				}
				return;
			}
			}
			BuildRingOrder();
			int count = ringOrder.Count;
			for (int num10 = 0; num10 < count; num10++)
			{
				if (pairBuf.Count >= 32)
				{
					break;
				}
				Character item = ringOrder[num10];
				Character item2 = ringOrder[(num10 + 1) % count];
				pairBuf.Add(item);
				pairBuf.Add(item2);
			}
		}

		private bool PairExists(int a, int b)
		{
			for (int i = 0; i + 1 < pairBuf.Count; i += 2)
			{
				int num = players.IndexOf(pairBuf[i]);
				int num2 = players.IndexOf(pairBuf[i + 1]);
				if ((num == a && num2 == b) || (num == b && num2 == a))
				{
					return true;
				}
			}
			return false;
		}

		private void BuildRingOrder()
		{
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			ringOrder.Clear();
			int count = players.Count;
			if (count == 0)
			{
				return;
			}
			if (shuffleSeed != 0)
			{
				for (int i = 0; i < count; i++)
				{
					ringOrder.Add(players[i]);
				}
				ringOrder.Sort((Character p, Character q) => (Mathf.Abs(p.Center.x - q.Center.x) > 0.001f) ? p.Center.x.CompareTo(q.Center.x) : p.Center.z.CompareTo(q.Center.z));
				Random random = new Random(shuffleSeed);
				for (int num = 0; num + 1 < count; num++)
				{
					if (random.NextDouble() < 0.65)
					{
						Character value = ringOrder[num];
						ringOrder[num] = ringOrder[num + 1];
						ringOrder[num + 1] = value;
					}
				}
				return;
			}
			int num2 = 0;
			for (int num3 = 1; num3 < count; num3++)
			{
				if (((Object)players[num3]).GetInstanceID() < ((Object)players[num2]).GetInstanceID())
				{
					num2 = num3;
				}
			}
			if (ringUsed.Length < count)
			{
				ringUsed = new bool[count];
			}
			for (int num4 = 0; num4 < count; num4++)
			{
				ringUsed[num4] = false;
			}
			int num5 = num2;
			for (int num6 = 0; num6 < count; num6++)
			{
				ringOrder.Add(players[num5]);
				ringUsed[num5] = true;
				int num7 = -1;
				float num8 = float.MaxValue;
				for (int num9 = 0; num9 < count; num9++)
				{
					if (!ringUsed[num9])
					{
						float num10 = Vector3.Distance(players[num5].Center, players[num9].Center);
						if (num10 < num8)
						{
							num8 = num10;
							num7 = num9;
						}
					}
				}
				if (num7 >= 0)
				{
					num5 = num7;
					continue;
				}
				break;
			}
		}

		private void DrawOne(ChainLink L, Character a, Character b, bool usePhysics)
		{
			//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_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: 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_007c: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: 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_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_028c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0291: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_0355: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f6: Unknown result type (might be due to invalid IL or missing references)
			Vector3 center = a.Center;
			Vector3 center2 = b.Center;
			EnsureLine(L);
			if (L.chain != null && ((Object)(object)L.lastA != (Object)(object)a || (Object)(object)L.lastB != (Object)(object)b))
			{
				L.chain.Reset(center, center2);
			}
			L.lastA = a;
			L.lastB = b;
			if (!usePhysics)
			{
				((Renderer)L.line).enabled = true;
				L.line.positionCount = 2;
				L.line.SetPosition(0, NearCameraTrim(center, center2, Vector3.Distance(center, center2) * 0.5f));
				L.line.SetPosition(1, center2);
				if ((Object)(object)L.linkRend != (Object)null)
				{
					((Renderer)L.linkRend).enabled = false;
				}
				ApplyChainColor(L, center, center2, (Object)(object)a == (Object)(object)ch || (Object)(object)b == (Object)(object)ch, (Object)(object)aimHint != (Object)null && ((Object)(object)a == (Object)(object)aimHint || (Object)(object)b == (Object)(object)aimHint));
				return;
			}
			if (L.chain == null)
			{
				int num = ((Plugin.ChainMaxPoints != null) ? Plugin.ChainMaxPoints.Value : 56);
				L.chain = new PhysicsChain((Plugin.ChainSpacing != null) ? Plugin.ChainSpacing.Value : 0.55f, (Plugin.ChainThickness != null) ? Plugin.ChainThickness.Value : 0.15f, Mathf.Max(num, 200), (Plugin.ChainSlack != null) ? Plugin.ChainSlack.Value : 0.01f, 0.65f, 0.15f, (Plugin.ChainMaxSag != null) ? Plugin.ChainMaxSag.Value : 0.6f);
			}
			if ((Object)(object)L.a != (Object)(object)a || (Object)(object)L.b != (Object)(object)b)
			{
				L.chain.Reset(center, center2);
				L.a = a;
				L.b = b;
			}
			int num2 = (((Object)(object)a == (Object)(object)ch || (Object)(object)b == (Object)(object)ch) ? 1 : ((Plugin.FarChainInterval != null) ? Mathf.Clamp(Plugin.FarChainInterval.Value, 1, 10) : 3));
			if (num2 <= 1 || (Time.frameCount + L.GetHashCode()) % num2 == 0)
			{
				L.chain.Step(center, center2, Time.deltaTime * (float)num2);
			}
			Vector3[] points = L.chain.Points;
			int i = 0;
			int num3 = points.Length - 1;
			float num4 = ((Plugin.ChainHideRadius != null) ? Plugin.ChainHideRadius.Value : 0.85f);
			if (num4 > 0f)
			{
				Vector3 val;
				for (val = CameraPosition(); i < num3 - 1 && Vector3.Distance(points[i], val) <= num4; i++)
				{
				}
				while (num3 > i + 1 && Vector3.Distance(points[num3], val) <= num4)
				{
					num3--;
				}
			}
			int num5 = num3 - i + 1;
			if (num5 < 2)
			{
				HideLink(L);
				return;
			}
			bool flag = (Plugin.SolidChainLinks == null || Plugin.SolidChainLinks.Value) && (Object)(object)L.linkRend != (Object)null && (Object)(object)L.linkMat != (Object)null;
			((Renderer)L.line).enabled = !flag;
			if (!flag)
			{
				L.line.positionCount = num5;
				for (int j = 0; j < num5; j++)
				{
					L.line.SetPosition(j, points[i + j]);
				}
			}
			else
			{
				int num6 = ((Plugin.MeshInterval != null) ? Mathf.Clamp(Plugin.MeshInterval.Value, 1, 6) : 2);
				int num7 = links.IndexOf(L);
				if (num7 >= 0 && num7 < meshCd.Length)
				{
					if (meshCd[num7] <= 0)
					{
						BuildLinkMesh(L, points, i, num3);
						meshCd[num7] = num6 - 1;
					}
					else
					{
						meshCd[num7]--;
					}
				}
				else
				{
					BuildLinkMesh(L, points, i, num3);
				}
			}
			ApplyChainColor(L, center, center2, (Object)(object)a == (Object)(object)ch || (Object)(object)b == (Object)(object)ch, (Object)(object)aimHint != (Object)null && ((Object)(object)a == (Object)(object)aimHint || (Object)(object)b == (Object)(object)aimHint));
		}

		private Vector3 NearCameraTrim(Vector3 from, Vector3 to, float maxAdvance)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			float num = ((Plugin.ChainHideRadius != null) ? Plugin.ChainHideRadius.Value : 0.85f);
			if (num <= 0f)
			{
				return from;
			}
			Vector3 val = CameraPosition();
			if (Vector3.Distance(from, val) > num)
			{
				return from;
			}
			float num2 = Mathf.Min(num, Mathf.Max(0f, maxAdvance));
			return Vector3.MoveTowards(from, to, num2);
		}

		private Vector3 CameraPosition()
		{
			//IL_0080: 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)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)cam == (Object)null)
			{
				cam = Camera.main;
				if ((Object)(object)cam == (Object)null)
				{
					Camera[] allCameras = Camera.allCameras;
					for (int i = 0; i < allCameras.Length; i++)
					{
						if ((Object)(object)allCameras[i] != (Object)null && ((Behaviour)allCameras[i]).enabled)
						{
							cam = allCameras[i];
							break;
						}
					}
				}
			}
			if ((Object)(object)cam != (Object)null)
			{
				return ((Component)cam).transform.position;
			}
			return ch.Center + Vector3.up * 0.6f;
		}

		private void ApplyChainColor(ChainLink L, Vector3 a, Vector3 b, bool involvesMe, bool aimed)