EasyGear
[AI-generated] Raises item durability (worn equipment too: helmet/chest/legs/cape). Repairs gear when you use a workbench, plus optional auto-repair on entering a station. Client-only.Changelog
1.0.5
剔除:第 4 项功能「跑步时也能切换武器 / 工具」(含其全部代码与配置)。
- 删掉
src/Patches.cs里的PlayerCheckRunPatch+ClearActionQueuePatch两个补丁类 (连同那一整段注释); - 删掉
src/EasyGearPlugin.cs里的EquipWhileRunningEnabled配置项与4 - Equip While Running配置节,以及ApplyPatches/VerifyPatches里的对应条目; - 结果:本模组回到三项功能(耐久 / 范围修理 / 进站自动修理),
补丁类 7 → 5,DLL 内已不含
PlayerCheckRunPatch/ClearActionQueuePatch/EquipWhileRunningEnabled/InCheckRun/ClearActionQueue等任何相关符号。
为什么直接删而不是留着修好的版本:该功能在 1.0.2 / 1.0.3 期间因补丁挂错方法
(挂在 Humanoid.ClearActionQueue 的空实现上,Harmony 不会把对虚方法的 patch 展开到
派生重写)从未生效;1.0.4 修好挂载点后,经决定不再保留这项功能,故整体移除。
历史上两次与它相关的改动(1.0.2 新增、1.0.4 修挂载点)的记录仍保留在下方,
仅作追溯用 —— 1.0.5 之后不再有这项功能。
⚠️ 旧 cfg 里的 [4 - Equip While Running] 节会留在原地,但 BepInEx 不再读取它(无害)。
1.0.4
修复:「跑步时也能切换武器 / 工具」从 1.0.2 起从未生效(补丁挂错了方法)。
- 现象:跑步中按数字键切武器 / 工具仍然没反应 —— 与 1.0.2 声称修好之前一模一样。
- 根因:1.0.2 把补丁挂在
Humanoid.ClearActionQueue上,而那是空实现 (protected virtual,方法体只有一句ret);真逻辑在Player的 override 里 (ldarg.0 / ldfld m_actionQueue / callvirt List::Clear / ret)。 玩家侧四个调用点(Player.CheckRun/Player.OnJump/Player.UpdateDodge/Humanoid.StartAttack)写的都是callvirt Humanoid::ClearActionQueue—— 虚拟分发到 Player 的重写。 ⇒ Harmony 不会把「对虚方法的 patch」自动展开到派生重写,所以本补丁从未被执行。 ⚠️ 它自己的自检照样打印Humanoid.ClearActionQueue() prefix=1 ... OK—— 注册成功 ≠ 会被执行,这是这次最大的教训。 - 实锤证据(取自 EasyWeapon 1.0.0 的真机日志,2026-09-24):
Player.ClearActionQueue() prefix=1,且下面没有「来自其它模组的 prefix」;Humanoid.ClearActionQueue()(空实现)prefix=1← 就是本模组挂错的那一条;- 日志里出现了
[装填保护] 奔跑触发的清空:…—— 说明奔跑时清空照旧发生。 若本补丁是活的,那个(优先级更低的)prefix 根本不会被调用,这条日志就不会存在。
- 修法:
ClearActionQueuePatch改挂Player.ClearActionQueue;Prefix的参数类型同步由Humanoid改为Player;VerifyPatches的核对目标同步改(否则自检还在核对那个错方法)。 - 顺带增强:自检里新增「列出其它模组挂在该方法上的 prefix(owner + 优先级)」—— 以后一眼就能看出某个模组的补丁是不是挂在死方法上。
1.0.3
修复:耐久倍率对「身上穿的装备」无效 —— 头盔 / 胸甲 / 裤子 / 披风。
-
起因:1.0.1 修好了"耐久倍率完全不生效",但那只覆盖了武器/工具/弓/盾; 穿戴中的护甲仍然按原版速度掉耐久。
-
根因(IL 实证):本模组改的是
SharedData.m_useDurabilityDrain, 而那个字段只管武器侧。实扫全程序集,读它的只有:Attack.DoMeleeAttack/Attack.ProjectileAttackTriggered/Attack.DoNonAttack/Humanoid.BlockAttack/Player.GetPlaceDurability/Player.Repair—— 一个都不管护甲。 护甲走的是另一条路:Player.DamageArmorDurability(HitData)(Character里是空实现,Player重写,唯一调用点是Character.RPC_Damage的callvirt),其 IL 为:- 收集已装备 4 件:
m_chestItem/m_legItem/m_helmetItem/m_shoulderItem damage = hit.GetTotalPhysicalDamage() + hit.GetTotalElementalDamage()- 随机挑其中一件:
Random.Range(0, count) item.m_durability = Mathf.Max(0, item.m_durability - damage)
⇒ 耐久直接减「伤害数值」,硬编码,不读任何倍率字段。这就是护甲不受影响的原因。
- 收集已装备 4 件:
-
修法:新增
src/ArmorDurability.cs,Harmony 补丁Player.DamageArmorDurability:Prefix记下 4 件护甲扣前耐久Postfix找出实际被扣的那件,用Mathf.Max(0, before - damage/倍率)重算- ⇒ 原版的"随机挑一件""按伤害量扣"行为完全保留,只把倍率作用在伤害量上,
与武器侧
m_useDurabilityDrain / 倍率语义一致
-
🔴 一个必须避开的陷阱(本实现第一版错在这): 原版最后一步
Mathf.Max(0, …)会先把负值 clamp 到 0,真实伤害量就丢了。 若取"原版实际扣了多少"再去除以倍率,会出现:- 单次伤害 > 剩余耐久时少扣(例:耐久 3 挨 12 伤害,应扣
12/5=2.4→ 剩 0.6, 错误做法只损失3/5=0.6→ 剩 2.4) - 更糟:若每次都"伤害 > 剩余耐久",就退化成"每击只损失剩余量的 1/5",
护甲趋近于永不损坏、比 5 倍还耐用
⇒ 所以
Postfix必须重新从HitData取原始伤害(GetTotalPhysicalDamage+GetTotalElementalDamage,两者都 PUBLIC),自己算damage/倍率再 clamp。
- 单次伤害 > 剩余耐久时少扣(例:耐久 3 挨 12 伤害,应扣
-
只作用于本地玩家(纯客户端模组;也避免去改远端玩家的"展示用"装备副本)。
-
MaxDurability模式本来就对护甲有效(它改m_shared.m_maxDurability, 而ItemData.GetMaxDurability()正是读m_shared.m_maxDurability + 品质×m_durabilityPerLevel), 所以本补丁只处理LossReduction/Both。 -
启动自检新增一行
Player.DamageArmorDurability(HitData)(护甲耐久倍率), 并额外校验:解析到的必须是Player自己的重写(不是Character的空实现)、 以及 4 个护甲槽位字段反射成功。 -
首次生效时打一条带具体数值的日志:
护甲耐久倍率已生效(LossReduction ÷5):$item_chest_iron 受击伤害 12 → 应扣 2.4(耐久 80 → 77.6)
离线验证
verify_armor_durability.py 11 组用例全过,其中两组是专门为上面那个陷阱写的回归:
- 耐久 3 挨 12 伤害 → 必须剩 0.6(扣 2.4),而不是剩 2.4
- 连续巨额伤害必须能击破,不能"趋近 0 而永不归零"
另含:倍率单调性、
mult<=1不介入、伤害 ≤0 无副作用、空槽位、非本地玩家不介入、 500 组随机参数的数值稳定性、以及「耐久绝不会被改高」这个关键不变量。
1.0.2
新增:跑步时也能切换武器 / 工具(解除原版限制)。
- 原版行为(IL 实证):
Character.UpdateWalking每帧调Player.CheckRun, 而Player.CheckRun在确认"正在奔跑"之后会调Humanoid.ClearActionQueue()清空装备动作队列。 - 而带抽拿动画的装备(
SharedData.m_equipDuration > 0,绝大多数武器/工具)切换时 走的是Humanoid.ToggleEquipped → QueueEquipAction—— 入队,要靠Player.FixedUpdate → UpdateActionQueue逐帧推进才能完成。 - ⇒ 跑步过程中队列刚入队就被下一帧的 CheckRun 清掉,装备动作永远完不成,
表现就是"跑步时按数字键切武器/工具完全没反应"。
(
m_equipDuration == 0的物品走立即装备,所以不受影响 —— 这也解释了为什么现象只出现在"武器和工具"上。) - 修复:只拦「CheckRun 发起的」那一次清空,让装备动作能正常做完; 攻击 / 跳跃 / 翻滚时的清空保持原版行为(那些是有意义的打断)。
- 开关:
[4 - Equip While Running] Enabled(默认 true)。关掉即完全回到原版。 - 启动自检升级:目标方法找不到时明确报错,不再静默跳过 (1.0.0 那次"耐久完全不生效"就是静默失败,这条是补的课)。
1.0.1
修复:耐久倍率(武器/工具/盾/护甲)完全不生效。
- 根因:从 EasyQoL 拆分出 EasyGear / EasyVitals 时,改写物品数据库的两个挂载点
ObjectDB.Awake与ZNetScene.Awake跟着「角色/玩法数值」那一组一起搬去了 EasyVitals, 而耐久数据是 EasyGear 的ObjectDbApplier在改 —— 于是 EasyGear 里的ObjectDbApplier.ApplyIfReady()成了死代码(有定义、零调用点)。 表现:[1 - Item Durability]怎么调都没反应,日志里也不报错(因为它压根没被调用)。 - 修复:把这两个挂载点补回 EasyGear 自己的
Patches.cs,只调用本程序集的 Applier, 不引入任何跨程序集依赖(保持 EasyGear 可单独安装)。 - 启动自检新增两行:
ObjectDB.Awake()/ZNetScene.Awake()的补丁计数, 以后这类"挂载点丢失"一眼可见。 - 日志改进:
已处理 N 件物品 → 耐久 <模式>:消耗 ÷5(N 件) / 上限 ×5(N 件),MaxDurability模式下也能自证生效(旧版那种模式只会打「0 件」)。
1.0.0
- 首个版本:从 EasyQoL 拆出「装备维护」一组 —— 耐久倍率 / 30 米范围修理 / 进站逐件自动修理。
- 纯客户端:无 Jotunn 依赖、无
NetworkCompatibility声明、无内嵌同步框架,主机不装也能用、不挡联机。


