Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
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.
By s6652289
CHANGELOG
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声明、无内嵌同步框架,主机不装也能用、不挡联机。