Valheim
Install with App

Details

Latest version
1.0.5
Last Updated
First Uploaded
Downloads
270
Likes
0
Size
21KB
Dependants
ADDatHost Valheim hosting
€1

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):
    1. Player.ClearActionQueue() prefix=1,且下面没有「来自其它模组的 prefix」;
    2. Humanoid.ClearActionQueue()(空实现)prefix=1 ← 就是本模组挂错的那一条;
    3. 日志里出现了 [装填保护] 奔跑触发的清空:… —— 说明奔跑时清空照旧发生。 若本补丁是活的,那个(优先级更低的)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 为:

    1. 收集已装备 4 件:m_chestItem / m_legItem / m_helmetItem / m_shoulderItem
    2. damage = hit.GetTotalPhysicalDamage() + hit.GetTotalElementalDamage()
    3. 随机挑其中一件:Random.Range(0, count)
    4. item.m_durability = Mathf.Max(0, item.m_durability - damage)

    ⇒ 耐久直接减「伤害数值」,硬编码,不读任何倍率字段。这就是护甲不受影响的原因。

  • 修法:新增 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。
  • 只作用于本地玩家(纯客户端模组;也避免去改远端玩家的"展示用"装备副本)。

  • 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 声明、无内嵌同步框架,主机不装也能用、不挡联机。
DatHost — Valheim server hosting, first month for one euro
DatHost — Valheim server hosting, first month for one euro
DatHost — Valheim server hosting, first month for one euro
DatHost — Valheim server hosting, first month for one euro
DatHost — Valheim server hosting, first month for one euro