Decompiled source of FateBound v0.5.2
plugins/com.quaree.fatebound.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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)