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.
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).
By s6652289
| 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
BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.
Preferred version: 5.4.2350README
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 组件,说明游戏更新改了名字/结构 ——
特效侧参数不会生效(武器侧仍正常),需要重新核对。