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] Gear QoL: durability x5, 30 m hammer area repair, staggered auto-repair at stations, and switch weapons/tools while running. Client-only, no server sync.
By s6652289
| Date uploaded | 2 weeks ago |
| Version | 1.0.2 |
| Download link | s6652289-EasyGear-1.0.2.zip |
| Downloads | 25 |
| Dependency string | s6652289-EasyGear-1.0.2 |
This mod requires the following mods to function
denikson-BepInExPack_Valheim
BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.
Preferred version: 5.4.2350README
EasyGear
AI 生成声明:本模组由 AI 智能体(WorkBuddy)在用户的明确需求与逐项验收下编写; 功能设计、真机测试与发布决定均由用户主导。
装备维护。 从 EasyQoL 按功能用途拆分而来,纯客户端模组。
1.0.2(2026-09-17)新增:跑步时也能切换武器 / 工具(解除原版限制)。 原版奔跑时会每帧清空「装备动作队列」,导致带抽拿动画的武器/工具按数字键后 动作被取消、看起来完全没反应。现可正常切换。
1.0.1(2026-09-17)修复:耐久倍率(武器 / 工具 / 盾 / 护甲)此前完全不生效。 根因是拆分时
ObjectDB.Awake/ZNetScene.Awake两个挂载点漏在了 EasyVitals, 导致本模组的耐久改写函数成了死代码。详见CHANGELOG.md。
功能(4 项)
| # | 功能 | 默认 | 配置节 |
|---|---|---|---|
| 1 | 武器 / 工具 / 弓 / 盾 / 护甲 耐久 | 5 倍(LossReduction) | 1 - Item Durability |
| 2 | 锤子「范围修理」 | 半径 30 米 | 2 - Area Repair |
| 3 | 进站自动修理 | 开(含背包,150 ms/件) | 3 - Auto Repair At Station |
| 4 | 跑步时也能切换武器 / 工具 | 开 | 4 - Equip While Running |
角色/玩法数值(重量 / 食物 / 药水 / 地图 / Boss 之力 / Rested / 装备减速)在 EasyVitals。
配置
BepInEx/config/easygear.client.only.cfg
1 - Item Durability→Mode三选一:LossReduction(默认)= 耐久消耗 ÷ 倍率。装备明显更耐用,但物品提示里的数字下降很慢。MaxDurability= 耐久上限 × 倍率。提示里一眼可见;已有装备需要先用锤子修一次才会满。Both= 两者都做(寿命约 ×25,慎用)。
2 - Area Repair→Verbose默认 true(沿用原 EasyQoL 的实机配置): 每次范围修理在日志里记一行修好了几个部件。3 - Auto Repair At Station→IntervalMilliseconds是逐件修理的间隔(默认 150); 装备多的时候能看清耐久条一根根回满。填0= 一瞬间全满。4 - Equip While Running→Enabled:关掉即完全回到原版(跑步时切不动装备)。
⚠️ 改完必须重启游戏(BepInEx 只在插件 Awake 读一次配置)。
实现要点
- 耐久:
ObjectDBprefab 数据改写(ObjectDB.Awake+ZNetScene.Awake兜底),原值 × 倍率、幂等。 - 范围修理:
Player.Repair的 postfix —— 以鼠标指向的部件为中心,把半径内所有WearNTear也修一遍。 带防重入标记;Repair()自身会跳过已满耐久/冷却中的部件,所以不会重复计。 - 进站自动修理:
InventoryGui.Show的 prefix 记下「调用前界面是否已可见」, 只有【不可见 → 可见】且activeGroup == 3(制造页)才算「刚打开」;逐件修理走协程带节奏。 身上穿戴的那批走游戏原版修理函数,所以涨熟练度、修理特效、「已修复 xxx」提示 与手动点修理按钮完全一致;原版修理本来就免费,所以不消耗材料。 - 跑步可切换装备:
Player.CheckRun(每帧)的 prefix/postfix 只用来打一个"正在 CheckRun 内"的标记;Humanoid.ClearActionQueue的 prefix 在该标记为真、且实例确实在奔跑(Character.IsRunning())时 跳过这次清空。攻击 / 跳跃 / 翻滚触发的清空不受影响。 双保险设计:即使标记意外卡住,非奔跑状态也不会误跳过。
联机
纯客户端。 主机、其他玩家都不需要装。
不声明 NetworkCompatibilityAttribute,不引用 Jotunn,不内嵌 ServerSync / CCS。
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声明、无内嵌同步框架,主机不装也能用、不挡联机。