s6652289-EasyWeapon icon

EasyWeapon

[AI-generated] Client-only weapon tweaks: faster reload (crossbows/grappling hook), reloads that survive run/jump/dodge, and a tuned chain lightning (weapon proc chance + ChainLightningRed effect parameters).

Last updated a week ago
Total downloads 87
Total rating 0 
Categories Mods AI Generated
Dependency string s6652289-EasyWeapon-1.1.0
Dependants 0 other packages depend on this package

This mod requires the following mods to function

denikson-BepInExPack_Valheim-5.4.2350 icon
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.2350

README

EasyWeapon

AI 生成声明:本模组由 AI 智能体(WorkBuddy)在用户的明确需求与逐项验收下编写; 功能设计、真机测试与发布决定均由用户主导。

武器手感。 纯客户端模组。

功能(3 项)

# 功能 默认 配置节
1 装填时间倍率 —— 弩 / 钩索 0.5(= 装填时间减半) 1 - Reload Speed
2 装填不被打断 —— 奔跑 / 跳跃 / 闪避翻滚时不清掉装填 三项都开 2 - Uninterruptible Reload
3 连锁闪电强化 —— 武器触发率统一 + ChainLightningRed 特效参数 见 3 - Chain Lightning 节 3 - Chain Lightning

这两个需求在游戏里其实是同一套机制

反汇编确认(游戏 1.0.14,assembly_valheim.dll):

物品 本地化键 m_requiresReload m_reloadTime 装填动画 技能
钩索 $item_graplinghook ⚠️ 游戏里就少一个 p true 2.0 s reload_crossbow 无(不吃技能)
弩(Arbalest / Ripper×4 / Gold×3 及其变体,共 8 把) $item_crossbow_* true 3.5 s reload_crossbow Crossbows
闪电法杖(默认不动) $item_staff_lightning true 1.9 s recharge_lightningstaff ElementalMagic

两者共用 ItemData.GetWeaponLoadingTime():

if (!m_requiresReload) return 1f;                                    ← 该分支必须绕开
float skill = Player.m_localPlayer.GetSkillFactor(m_skillType);
return Mathf.Lerp(m_reloadTime, m_reloadTime * 0.5f, skill);         ← 弩技能满级本来就已减半

所以「弩减半」和「钩索减半」是一个挂载点,不需要写两套。

配置

BepInEx/config/easyweapon.client.only.cfg

1 - Reload Speed

键 默认 说明
Enabled true 总开关
ReloadTimeMultiplier 0.5 时间倍率:0.5 = 装填时间减半(更快)。⚠️ 填 2 是变慢
Crossbows true 弩
GrapplingHook true 钩索
Staves false 法杖(目前走装填的是闪电法杖,充能 1.9 s)
OtherReloadWeapons false 其它「需要装填」的武器(游戏更新新增、本模组还不认识的)。宁可不动,也不误伤
Verbose true 首次遇到某件武器时记一条日志(含前后耗时),同一件只记一次

2 - Uninterruptible Reload

键 默认 说明
Enabled true 总开关
OnRun true 奔跑时不打断装填
OnJump true 跳跃时不打断装填
OnDodge true 闪避翻滚时不打断装填
Verbose true 每次新的装填动作被保护时记一条(同一个动作只记一次,不刷屏)

攻击仍然会打断装填 —— 那是原版设计,本模组不动。

3 - Chain Lightning

键 默认 说明
Enabled true 总开关(武器侧 + 特效侧一起管)
WeaponProcChance 0.66 所有「命中时生成连锁预制体」的攻击槽,主/副攻击统一用这个几率。原版主攻击 0.25 / 副攻击 0.20
RedStartChance 1.0 ChainLightningRed 的起始判定(原版 0.9)。⚠️ 判定是每帧一次(存活期间),不是"每次挥击的触发率"
RedChancePerTarget 0.66 每个候选目标被链上的几率(原版 0.3),各目标独立判定
RedMinTargets 4 一次连锁最少链几个目标(原版 2)
RedMaxTargets 6 一次连锁最多链几个目标(原版 4),实际个数在 Min~Max 之间随机
RedFalloff 0.8 逐跳衰减(原版 0.6):下一跳几率 = 本跳几率 × 该值。填 1 = 不衰减
RedStartDelay 0.18 特效生成后多久开始判定(秒)
RedRadius 12 连锁判定半径(米),原版 8
RedTtl -1(= 不改) 特效存活时长。-1 = 保持游戏默认(3 秒),本模组完全不碰这个字段;填正数(0.1~30)才覆盖。⚠️ 它同时是连锁的判定窗口,填小了会明显削弱
RedLightningDamage 90 每一跳的闪电伤害,原版 75
Verbose true 打印每条改动的前后数值(便于自证生效)

⚠️ 本模组只改 ChainLightningRed —— 那是「雷血 / BloodLightning」那一族武器 ($item_*_gold_bloodlightning)用的连锁特效。 另一族 ChainLightning(闪电附魔 $item_*_lightning)的参数不动, 但那些武器的触发几率同样受 WeaponProcChance 影响。

⚠️ 改完必须重启游戏(BepInEx 只在插件 Awake 读一次配置)。

实现要点

功能 1:装填倍率

  • 挂载点:ItemDrop.ItemData.GetWeaponLoadingTime() 的 postfix,返回值 × 倍率。
  • 这个方法是全程序集唯一调用点为 Player.QueueReloadAction() 的纯计算函数 (asm_member_refs.py 实测),返回值写进 MinorActionData.m_duration (进度条文案 $hud_reloading)。⇒ 影响面被天然圈死: 只影响本地玩家按下的那一次装填,不碰 NPC 的 Attack.Start 路径,也不改任何 prefab 数据。
  • ⚠️ 必须绕开 !m_requiresReload 的分支:那条路径原版返回常量 1f, 对它乘倍率会破坏不装填武器的时长语义。代码里在分类之前就先判 m_requiresReload。
  • 幂等:纯计算、每次由原始值重算,无累计副作用 —— 所以不需要「记账 + 原值」那套(对比:改写 prefab 数据的模组必须记账)。
  • 分类判据用本地化键(SharedData.m_name),不是显示名(会随语言变)、也不是类名 (游戏里没有 Crossbow / GrapplingHook 类型,行为全挂在 Attack + ItemDrop 上)。 归一化:去 $item_ 前缀 → 去 _/空格/-/. → 转小写。 弩与法杖用 StartsWith(变体多),钩索用精确等值(避免误伤名字相近的物品)。

功能 2:装填不被打断

原版有四个地方把整个动作队列清空(asm_member_refs.py Humanoid::ClearActionQueue 给出):

来源 位置 本模组
奔跑 Player.CheckRun(每帧) 拦
跳跃 Player.OnJump(第一条指令就是 callvirt ClearActionQueue) 拦
闪避翻滚 Player.UpdateDodge(偏移 0x0074) 拦
攻击 Humanoid.StartAttack 不拦(保持原版)
  • 拦截点是 Player.ClearActionQueue(override,真正执行的那份),不是 Humanoid.ClearActionQueue(基类是空实现,只有一句 ret;四个调用点写的都是 callvirt Humanoid::ClearActionQueue,虚拟分发到 Player 的重写)。
  • Player.ClearActionQueue 的实现只有三条指令:ldarg.0 / ldfld m_actionQueue / callvirt List::Clear / ret, 没有任何其它副作用 —— 所以「只清空非装填动作」与「整体清空」在其它方面完全等价。
  • 队列里的动作类型:Player.MinorActionData.ActionType = Equip(0) / Unequip(1) / Reload(2)。 本模组遍历 m_actionQueue,移除除 Reload 外的所有条目,装填那条原地保留 ⇒ 装填进度(m_time)不丢、进度条不归零。
  • m_actionQueue 是私有字段,用 AccessTools.FieldRefAccess 反射取(解析失败会在 启动自检里打 LogError,不会静默失效)。
  • 来源判定用深度计数而非 bool:bool 一旦因异常卡住(prefix 跑了、postfix 没跑) 就会永久误保护;计数配合 > 0 夹紧可以自愈。
  • 标了 [HarmonyPriority(Priority.Low)](数值越小越后执行)—— 让别的模组的判定先做。

功能 3:连锁闪电强化

游戏里「闪电伤害」和「连锁闪电」是两件事 —— 不是所有带闪电伤害的武器都会连锁。 反汇编 + prefab 数据核对(游戏 1.0.14)得到的完整链路:

武器命中
  └─ Attack.SpawnOnHit():  if (m_spawnOnHitChance > 0 && m_spawnOnHit != null)
                                if (Random.Range(0f,1f) < m_spawnOnHitChance)   ← 功能 3 改这里
                                    在命中点实例化 m_spawnOnHit(连锁特效预制体)
  └─ 特效上的 Aoe.CustomFixedUpdate():                                        ← 功能 3 也改这里
        延迟 m_chainStartDelay 后,每帧判一次:Random.value < m_chainStartChance
          → 找目标,个数 = Random.Range(m_chainMinTargets, m_chainMaxTargets + 1)
          → 每个目标:Random.value < m_chainChancePerTarget
          → 生成下一个连锁对象,并把 m_chainChance × m_chainStartChanceFalloff 传下去
  • 武器侧挂载点:ObjectDB.Awake 的 postfix。遍历 ObjectDB.m_items → ItemDrop.m_itemData.m_shared 的 m_attack / m_secondaryAttack。 ⚠️ 判据用与游戏完全相同的两个条件(m_spawnOnHitChance > 0 且 m_spawnOnHit != null) 再加一道「目标预制体名以 ChainLightning 开头」的过滤: 前者保证只改"原版真的会触发"的槽(否则会把"不触发"改成"触发"), 后者保证不误伤将来别的 hit-spawn 特效。不写死武器名单,游戏新增武器自动跟上。 当前版本命中 19 件武器(7 件闪电附魔 + 12 件雷血附魔)× 主/副槽。
  • 特效侧挂载点:ZNetScene.Awake 的 postfix。ZNetScene.Awake 开头就填 m_namedPrefabs(GetStableHashCode 做键),所以 postfix 时 GetPrefab("ChainLightningRed") 可用; 而 ZNetScene 没有"运行时注册预制体"的方法 ⇒ 名字表在 Awake 之后固定不变, 要改就得赶在实例化之前 —— 这个时机是唯一正确的窗口。 取不到时再遍历 m_prefabs 兜底(防止注册名与 GameObject 名不一致)。
  • 幂等:全部是绝对值赋值(不是"原值 × 倍率")⇒ 重复执行结果不变。 对比功能 1 的纯计算写法,这里额外加了"已应用"标记,只为避免场景重建时重复刷日志。
  • 影响面:只改 prefab 静态数据,不碰任何运行时逻辑、不碰存档、不加网络同步。 连锁的每一跳都是同一个预制体实例化出来的,所以自然继承以上全部参数。
  • ⚠️ Host 端权威性:伤害由攻击者本地计算后同步,所以装了本模组的一方打出的连锁伤害确实变高; 但别人屏幕上看到的连锁范围/存活时长仍按他们自己的 prefab 数据。

与其它模组的关系

本模组不依赖任何其它模组 —— 拦截点直接落在 Player.ClearActionQueue,不假手上游。

历史上 EasyGear 曾有一项 「跑步时也能切换武器 / 工具」会拦同一个清空,两者当时可共存(谁先返回 false 都能保住队列); 该功能已在 EasyGear 1.0.5 整体剔除,所以这段共存逻辑现在只在将来出现同类模组时才有意义 —— [HarmonyPriority(Priority.Low)] 就是为此保留的(数值越小越后执行,让别人的判定先做)。

启动自检里同时打印 Humanoid.ClearActionQueue(空实现)与 Player.ClearActionQueue (实际执行的重写)的 patch 数,两条一对比就能看出补丁是否落在真正会被执行的方法上 (诊断用,不是判据 —— EasyGear 1.0.2/1.0.3 正是栽在这一点上,详见其 CHANGELOG)。

联机

纯客户端。 主机、其他玩家都不需要装。

不声明 NetworkCompatibilityAttribute(= NotEnforced,不参与连接校验), 不引用 Jotunn,不内嵌 ServerSync / CCS。

⚠️ 功能 3 改的是 prefab 静态数据 ⇒ 效果只体现在装了本模组的那一端: 连锁伤害由攻击者本地计算后同步(你打出的伤害确实更高), 但别人屏幕上看到的连锁范围 / 存活时长仍按他们自己的 prefab 数据。 三项功能都互不依赖,也可以只开其中一项。

真机验证怎么看日志

启动后 BepInEx/LogOutput.log 里应有:

[Info :  EasyWeapon] ---- 补丁挂载自检 ----
[Info :  EasyWeapon]   ItemData.GetWeaponLoadingTime()  prefix=0 postfix=1  OK
[Info :  EasyWeapon]   Player.CheckRun()  prefix=1 postfix=1  OK
[Info :  EasyWeapon]   Player.OnJump()  prefix=1 postfix=1  OK
[Info :  EasyWeapon]   Player.UpdateDodge()  prefix=1 postfix=1  OK
[Info :  EasyWeapon]   Player.ClearActionQueue()  prefix=1 postfix=0  OK
[Info :  EasyWeapon]       (若这一行下面还有「来自其它模组的 prefix:xxx」,说明那个模组的补丁
                             也挂在了**实际执行的重写**上;一条都没有则表示它的补丁挂在
                             Humanoid 的空实现上、运行时不会被执行 —— Harmony 不会把
                             「对虚方法的 patch」自动展开到派生重写。)
[Info :  EasyWeapon]   ObjectDB.Awake()  prefix=0 postfix=1  OK
[Info :  EasyWeapon]   ZNetScene.Awake()  prefix=0 postfix=1  OK
[Info :  EasyWeapon]   [诊断] Humanoid.ClearActionQueue()(空实现)prefix=?

游戏内装填一次后应出现(同一件武器只记一次):

[Info :  EasyWeapon] [装填] $item_crossbow_arbalest(弩):3.50s → 1.75s(倍率 0.5)
[Info :  EasyWeapon] [装填] $item_graplinghook(钩索):2.00s → 1.00s(倍率 0.5)

装填途中奔跑 / 跳跃 / 翻滚时应有:

[Info :  EasyWeapon] [装填保护] 奔跑触发的清空:保留装填动作「$item_crossbow_arbalest」(进度 0.83s / 1.75s),移除其它动作 0 个,队列现有 1 条。

若自检里出现 !! 未挂上 !! 或 反射不到 Player.m_actionQueue, 说明游戏更新改了结构 —— 对应功能不会生效,需要重新核对。

功能 3 的启动日志(进世界时 ObjectDB / ZNetScene 各 Awake 一次):

[Info :  EasyWeapon] [ObjectDB.Awake] 武器连锁触发率 → 0.66(主/副攻击统一):命中 38 个攻击槽 / 19 件武器(共扫 N 件物品)。
[Info :  EasyWeapon] [ZNetScene.Awake] 连锁特效 ChainLightningRed 已改写:
     起始判定   0.9 → 1
     每目标几率 0.3 → 0.66
     目标数     2~4 → 4~6
     逐跳衰减   0.6 → 0.8
[Info :  EasyWeapon]      起始延迟 0.18 → 0.18 s|半径 8 → 12 m|每跳闪电伤害 75 → 90|存活 3 s(保持游戏默认,未改动)

若出现 找不到预制体 ChainLightningRed 或 没有 Aoe 组件,说明游戏更新改了名字/结构 —— 特效侧参数不会生效(武器侧仍正常),需要重新核对。