Valheim
Install with App

Details

Latest version
1.0.6
Last Updated
First Uploaded
Downloads
355
Likes
0
Size
39KB
Dependants
ADDatHost Valheim hosting
€1

更新日志

1.0.6(2026-09-19)

修复:所有箱子名匹配从「精确比对」改为「归一化比对」—— 黑金宝箱等此前一直取不到料。

根因(真机日志钉死):GameObject 名 ≠ 本地化键

1.0.5 装好后玩家实测,日志给出了决定性证据(黑金宝箱采样 260 次):

· [未通过白名单] GameObject[piece_chest_blackmetal] 本地化键[$piece_chestblackmetal] 板车[否]   ← ×14 个全被拒
· GameObject[piece_chest] 本地化键[$piece_chest] → 不含目标材料,箱内共 ...                    ← 通过并读到内容
GameObject 名(白名单要匹配这个 本地化键 一致?
piece_chest $piece_chest
piece_chest_blackmetal $piece_chestblackmetal 不一致
piece_chest_private $piece_chestprivate 不一致

多词箱子的 GameObject 名带下划线、本地化键不带;单词的 piece_chest 两者相同。 这就解释了为什么大箱子一直好使、其它箱子全静默失配

并且 1.0.4 的"修正"方向是错的 —— 它扫了 resources.assets,而那份里是本地化键, 不是预制体名(预制体本体在压缩过的 bundle 里,裸字节扫描扫不到: piece_chest_private 在全库裸扫描里 0 命中,但运行时 GameObject 名确实是它)。 于是 1.0.4 把默认值改成了不带下划线的形式,黑金箱等仍然失配,只是从一种错法换成另一种。

复盘三个版本:1.0.0~1.0.3 用带下划线的名字 + 精确比对(名字其实写对了,但名单不全); 1.0.4 改成不带下划线的名字(反而把原本能匹配的黑金箱写坏了);1.0.6 归一化 —— 两种写法都通。

修复内容

  1. 新增 NormalizeContainerName():去 $ 前缀 → 去 piece_ 前缀 → 去掉所有下划线 → 转小写。 piece_chest_blackmetal / piece_chestblackmetal / CHESTBLACKMETAL → 全部得到 chestblackmetal怎么写都认
  2. ReloadAllowed() 改为存归一化键IsAllowed 匹配时把实际容器名也归一化再比(两边口径一致)。
  3. 保留 AllowedRaw(用户 cfg 原文)只用于日志回显,启动自检多打一行生效匹配键, 名字对不对得上可以当场看出来。
  4. 诊断行 DescribeNames 增加归一化键一列(原来打 GameObject/本地化键/ZDO 三列, 但没打"实际参与匹配的那个键",所以看不出到底差在哪)。

顺带修复(同批,玩家上一轮日志暴露的)

  1. 板车容器取不到料的第二个原因EnsureOwner 原先用 c.GetComponent<ZNetView>(), 而板车的容器挂在子物体上(日志里它的 GameObject 名直接叫 Container), 自己身上没有 ZNetView —— 它靠 Container.m_rootObjectOverride 指向 Vagon 的那个 ZNetView (Container.Awake 的 IL 就是这么解析 m_nview 的)。 ⇒ 新增 ResolveNView():优先读游戏解析好的 m_nview(反射),退回 public 的 m_rootObjectOverride, 最后才 GetComponent。日志里原先打的 接管所有权失败(没有 ZNetView 组件) 就是这个。
  2. 板车"正在被拖行"判定加子信号诊断:无参 IsAttached() = m_attachJoin != null || zdo.GetBool("attachJoint", false),两条语义不同 —— 日志现在会说明是"本机看得到实体关节"还是"只有 ZDO 标志为真(别人在拖 / 标志残留)", 便于判断"用户说车停着、却有一批被判成拖行"到底哪一种。

重要文档更正

  • README 与配置说明里「箱子名一律不带下划线」的说法是错的,已改为: 匹配已归一化,带不带下划线都认,并附上 GameObject 名 ↔ 本地化键的对照。
  • 离线回归 verify_container_whitelist.py 扩到 44 组,新增第 ⑥ 组专测归一化, 含 【安全】piece_chest 不会误匹配 piece_chest_blackmetal 这类"不能过度匹配"的用例。

1.0.5(2026-09-19)

修复两处,都会让"就近取料"静默失效: ① 取料顺序错 → 从 1.0.0 上线起一次都没成功取到过料(38 条日志 0 次成功); ② 板车识别只靠一个可能为 null 的字段 → 停着的板车取不到料。

① 就近取料从未成功过(顺序错)

  • 起因:用户反馈「黑金宝箱还是没有被正确拉取材料」。查 1.0.4 日志发现比这严重得多:
    [就近料源] 记录 38 条,其中成功(从 N 个箱子取了 X 个)**0 条**
    [火源装填] 0 条
    
    ⇒ 就近取料从来没有工作过,且不报任何错。
  • 排除项(都已逐条实测排除):白名单、cfg 生效、半径、材料名格式、箱子类型判定。 日志明确显示「候选箱 17~18 个」且全部通过白名单 —— 问题不在"找不到箱子"。
  • 根因(IL 实证)Container.GetInventory() 的实现只有一句 return m_inventory; —— 它是 Container.Awake/Load() 那一刻从 ZDO 反序列化出来的缓存,不是实时数据。 而 Container.Load() 的反汇编显示它只在 ZDO.DataRevision 变化时才真正反序列化:
    0000  DataRevision == m_lastRevision → return false   ← 版本没变 = 不加载
    001A  if (m_inUse) return false                       ← 使用中 = 也不加载
    005D  m_inventory.Load(new ZPackage(zdo.GetByteArray(s_items)))
    
    客户端若从没打开过那只箱子,m_inventory 可能一直停在初始空库
  • 原代码的顺序错误(这是关键):
    var item = cinv.GetItem(n, -1, false);  // ← 读到空库,直接 continue
    if (item == null) continue;
    ...
    if (!EnsureOwner(c)) continue;          // ← 永远走不到
    
    先读库存、再接管所有权 ⇒ 读到空快照就 continueClaimOwnership 永远执行不到
  • 修复:把顺序倒过来 —— 先接管所有权 → 强制刷新 ZDO 库存 → 再读
    1. EnsureOwner() 前置,并回报失败原因(原来失败是静默的)
    2. 新增 RefreshChestInventory():反射调(private 的)Container.Load() 强制刷新
    3. M_ContainerLoad 加兜底解析 ResolveContainerLoad()AccessTools.Method 在参数表 不匹配时会静默返回 null(游戏换版本给 Load 加可选参数就会),null 会让刷新被跳过、 症状又变回"箱子是空的"。现退回「按名找唯一无参重载」
    4. 新增 VerifyOwnerAfterTake():取料后补校验,若那一刻仍无写权(改动不会保存 → 会复制) 打 Warning 且不受 DebugLogging 控制,让这类问题无法再静默发生

② 停着的板车取不到料(识别依据不可靠)

  • 起因:用户反馈「静止在地上、没人拉也没人开着的板车,不会被正常拉取材料」。

  • 根因(IL 实证):原先只靠 Container.m_wagon != null 认板车。扫字段引用发现:

    字段 写入点 读取点
    Container.m_wagon 零处(只靠预制体序列化) 仅 3 处,全在开箱 RPC(RPC_RequestOpen/RequestStack/RequestTakeAll),且全部带 null 判断
    Vagon.m_container 零处(同上) Vagon.InUse / UpdateMass / UpdateLoadVisualization

    m_wagon 的三处读取都写 (m_wagon != null && m_wagon.InUse()),说明游戏本身就预期它可能为 null; 为 null 时游戏毫无症状。而 Vagon.m_container 若不连,板车就不按载重变沉(核心可见行为)⇒ 它才可靠。 旧代码只认 m_wagon ⇒ 一旦为 null 就掉进"名字白名单"分支, 而 Cart 不在 AllowedContainers 里 ⇒ 静默失败

  • 修复:改为两条信号任一命中,主信号换到可靠的那一侧:

    1. 新增 CartRegistryEasyCraftPlugin.cs)—— 用 Harmony 补丁复刻游戏自己的 Vagon.m_instances 清单(static List<Vagon>Awake 里 Add、OnDestroy 里 Remove,IL 实证):
      [HarmonyPatch(typeof(Vagon), "Awake")]     // postfix → CartRegistry.Add
      [HarmonyPatch(typeof(Vagon), "OnDestroy")] // prefix  → CartRegistry.Remove
      
      查询时用 Vagon.m_container 反查 Container(表里存 Vagon 而非 Container, 避免 Vagon.Awake / Container.Awake 先后顺序的坑)
    2. Container.m_wagon 保留为兜底信号
    3. 新增 IsCart():只做识别,与 IsAllowed 共用同一套信号,避免两处口径不一致

③ 顺带修正一处文档/注释错误

Vagon.IsAttached()(无参)的旧注释写的是 m_attachJoin != null实际 IL

0000  m_attachJoin != null                                → true
000E  else if (m_nview.IsValid())
001B      zdo.GetBool(ZDOVars::s_attachJointHash, false)   ← 还有 ZDO 网络标志

⇒ 同时看【本地关节】与【ZDO 网络标志】。行为本身是对的(更严格),只是注释误导。

④ 日志不再能骗人

现在同时打「候选 N 个 / 通过 M 个 / 取到 K 件 / 场上板车 J 辆」四个数字; 每个候选容器都会打出 GameObject名 / ZDO预制体名 / 本地化键 / **板车[是/否]及其命中信号** 四连, 板车被跳过时还会说明原因(IncludeCarts 开关是关的正在被拖行(停放后即可取))。 以后同类"静默空手而归"一眼就能定位。

1.0.4(2026-09-19)

修复:箱子白名单从 1.0.0 起就一直写错,导致木箱等取不了料;并补上黑金箱子等分类。

  • 起因:用户反馈「拉取材料的来源没有黑金箱子」。查下来问题比这严重得多 —— 不是少一个黑金箱子,而是整个白名单的写法就是错的。

  • 根因(决定性证据):游戏里所有箱子预制体名一律不带下划线。二进制扫描 valheim_Data/resources.assets 取得现存全部 8 种:

    游戏实际预制体名 简中名
    piece_chestwood 宝箱(木箱)
    piece_chest 加固型宝箱(大箱子)
    piece_chestblackmetal 黑金宝箱
    piece_chestgrausten 灰石箱
    piece_chestbarrel
    piece_chestwarderobe 衣柜
    piece_chestprivate 个人宝箱
    piece_chesttreasure 珍宝箱

    而 1.0.0~1.0.3 的默认值写的是 piece_chest_wood,piece_chest —— 已在同一份资源文件里确认 piece_chest_woodpiece_chest_blackmetalNOT FOUND(根本不存在)。匹配是 AllowedSet.Contains(prefab) 的精确比对 (仅忽略大小写)⇒ piece_chest 外全部失配: 木箱从上线起就没取过料,用户按文档加黑金箱子也永远不生效。

  • 修复AllowedContainers 默认值改为正确的 6 项 ——

    piece_chestwood,piece_chest,piece_chestblackmetal,piece_chestgrausten,piece_chestbarrel,piece_chestwarderobe
    

    个人宝箱(piece_chestprivate)与珍宝箱(piece_chesttreasure)仍有意排除。

  • 新增配置迁移 MigrateAllowedContainers(): 若 cfg 里仍是 1.0.3 的旧默认值,自动替换为新默认值并打一条 Warning 说明原因 —— 用户无需手动改配置,升级即生效。 ⚠️ 只在"恰好等于旧默认值"时才替换:用户自己改过(含清空、写了别的名字)一律原样尊重, 绝不覆盖他手工调过的配置。

  • 配置说明与 README 全部重写:把 8 种箱子的真实名字、哪些默认纳入、哪些有意排除列清楚, 并明确标出 5 个高频误写(piece_chest_woodpiece_chestwood 等)。

  • 自检日志改进:原来直接打 cfg 原始串(写错了也看不出来), 现在打解析后的生效清单 + 项数 —— 配错了一眼可见:

    就近取料=开:半径 30 米,允许的箱子 6 种 [piece_chest,piece_chestbarrel,…](板车=开),…
    

⚠️ 一个容易混淆的点(已写进 README)

站点名StationOreAllow)做了归一化 —— 去掉 piece_ 前缀与所有下划线再比, 所以 charcoal_kiln / piece_charcoalkiln / charcoalkiln 怎么写都认。 箱子名AllowedContainers)是精确比对,必须与游戏资源里的名字完全一致(仅忽略大小写)。 两者规则不同,不要把站点名的宽松写法套到箱子上。

离线验证

新增 verify_container_whitelist.py26 组用例全过,五组:

  1. 新默认值 6 项逐一命中游戏真实预制体名;个人宝箱 / 珍宝箱确认被排除
  2. 旧默认值被迁移;5 种用户自定义值(含清空、带空格、只留一个名字)均不被覆盖
  3. 边界:大小写不敏感、留空 → 空集(不取任何箱子)、逗号空段、纯空格串、None 不崩
  4. 回归:确认旧默认值里带下划线的名字确实命中不了真实名 —— 反证迁移的必要性
  5. 与游戏资源对账:脚本自己扫 resources.assets, 校验「硬编码清单」与「游戏实际」完全一致(游戏更新改名时不会悄悄失真)

1.0.3(2026-09-17)

新增:板车也算作取料来源。

  • 起因:板车上装着一堆材料,但就近取料只认箱子,够不到板车。
  • 板车(Cart)与雪橇(Sled)都用 Vagon 组件 + Container, 所以它们本来就被 ContainerRegistry 收录了(注册挂在 Container.Awake 上), 只是名字不在 AllowedContainers 白名单里、永远匹配不到。
  • 🔑 判据用 Container.m_wagon != null,不是按名字匹配。理由:
    1. 板车的 Container 可能挂在子节点上,GameObject 名未必是 Cart (箱子是 piece_chest_*,名字匹配一直好使;板车没有 piece_ 前缀)
    2. m_wagon 是语义明确的引用,游戏自己就用它(Vagon.InUse() 也依赖它) ⇒ 不怕以后改名/换层级,也不需要Cart 写进 AllowedContainers
  • 新增配置 3 - Nearby Chests → IncludeCarts(默认 )。
  • ⚠️ 正在被拖行的板车会被跳过。两个理由: ① 位置一直在变,距离筛选与"取完就不算了"的假设都不成立; ② 拖行时所有权在拖车人手里,EnsureOwnerClaimOwnership 有可能把车抢走。 停下不动了才会被取料。
  • ⚠️ 这里没有Vagon.InUse() 去做占用判定 —— 它等于 Container.IsInUse() || IsAttached(),会把"有人正开着板车容器"也拦掉, 而那件事已由 IsBusy() 统一处理、且受 4 - Safety → SkipInUse 开关控制。 在此重复拦截会让那个开关对板车失效。所以 IsAllowed 里只拦"被拖行"。
  • 另一处连带修复:Provide() 开头原本"白名单为空就直接返回 0",改为 "白名单为空 板车也未启用" 才早退 —— 否则清空 AllowedContainers 只想用板车取料时会被直接挡掉。
  • 自检行加上板车开关状态:… 允许的箱子 [...](板车=开)…

离线验证

verify_cart_source.py 12 组用例全过,含三条专门回归:

  • 拖行中的板车必须被跳过(且理由明确)
  • IsAllowed 不因"容器被打开中"拦板车,而 IsBusy 会拦、SkipInUse=false 时放行 (保证那个开关对板车同样有效)
  • 白名单为空 + 板车启用 → 不能早退;两者都关才早退 另含:开关关时不认板车、普通箱子路径不受影响、私人箱仍排除、雪橇同理、 m_wagon 取不到时安全退回名字匹配、空候选不崩。

1.0.2(2026-09-17)

新增:站点进料限制 —— 限定某个炼制站点只允许自动装填指定材料。默认「炭窑只烧木材」。

  • 起因:炭窑原版能烧 木材 / 细木 / 圆木 三种,而它们价值差很多。 实测日志(改前): [站点进料口装填] charcoal_kiln|$item_finewood 装了 5 个 —— 自动装填把细木挑去烧了。
  • 新增配置 1 - Batch Fill → StationOreAllow,默认值:
    charcoal_kiln=$item_wood
    
    ⇒ 炭窑只会自动装木材,细木 / 圆木不再被自动投进去。
  • 格式为分号分组(站点=材料1,材料2),可同时限制多个站点。
  • 材料名三种写法都认$item_wood / item_wood / Wood(忽略大小写)。
  • 站点名也三种都认charcoal_kiln / piece_charcoalkiln / charcoalkiln —— 内部去掉 piece_ 前缀与下划线后归一化比较,所以「本地化键」与「GameObject 名」的 写法差异(charcoal_kilnpiece_charcoalkilnfire_pitpiece_firepit)都能匹配上。
  • 只影响本模组的自动装填(批量装填 + 就近取料),手动放置不受限制。 过滤点选在 Engine.OreSharedNames():候选列表会一路传给扩展料源 (IFillSource.Provide(target, user, sharedNames, …)),所以过滤一处、两处同时生效
  • 安全设计:绝不让站点变成"什么都装不进去"。 若配置的材料名在该站点上一个都对不上(多半是写错),会退回原版行为并打一条 Warning 把该站点实际可用的材料名列出来,方便直接照抄修正。
  • 启动自检新增一行,打生效值(解析后的规则组数与内容),而不是 cfg 原始串 —— 否则"配了但没解析成功"时日志会显示得像是生效了。
  • 首次装填某站点时会打一行生效明细: 站点进料限制生效:charcoal_kiln 只允许装填 [$item_wood](该站点可用共 3 种:…)

离线验证

把 C# 判定逻辑逐行照搬成 Python,10 个场景全过(verify_station_ore_filter.py): 默认配置只留木材 / 五种材料写法都认 / piece_ 前缀站点名也认 / 无规则站点不受影响 / 多站点同时限制 / 名字写错则退回原版并警告 / 右边留空则不限制 / 整串留空则全局回原版 / 配置串缺 = 不崩 / 组件挂在子节点时沿父链找站点名。

⚠️ 其中「站点名用 piece_ 前缀写法」这一条当场抓出了一个真 bug: 原实现是"给 GameObject 名拼上 piece_ 再比",但本地化键是 piece_charcoalkilncharcoalkiln 是一个词、没有下划线),永远拼不出来。 已改为归一化比较(去 piece_ + 去下划线)。

1.0.1

  • 营火改为「一次一个 + 允许从附近箱子拉料」:新增 OneAtATime 名单 (Exclude 命中且 OneAtATime 在 → 数量固定 1 但料源可用)。

1.0.0

  • 由 EasyBatchFill + EasyNearbyFill 合并而成(同时消掉了两者之间那条跨程序集硬依赖)。
  • 批量装填(站点 + 火源)、就近取料、占用保护。
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