Valheim
You are viewing a potentially older version of this package. View Latest Version
Install with App

Details

Date Uploaded
9 hours ago
Downloads
489
Size
49KB
ADDatHost Valheim hosting
€1

EasyCraft

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

炼制站点 / 火源的装填增强。 由 EasyBatchFill 与 EasyNearbyFill 合并而成 —— 合并同时消掉了原先两者之间那条「必须成对升级、单升一边 BepInEx 直接加载失败」的跨程序集硬依赖。

功能

  • 批量装填:一次交互装入 BatchSize 个(默认 5),取代原版的一次 1 个。 站点:熔炉 / 高炉 / 炭窑 / 风车 / 纺车 / Eitr 精炼厂。
  • 站点进料限制(1.0.2 新增):限定某个站点只允许自动装填指定材料。 默认 = 炭窑只烧木材(不烧细木 / 圆木)。
  • 火源加燃料:火炬 / 壁灯 / 篝火 / 火盆 / 灶 一次加 BatchSize 个; 营火(fire_pit)默认走「一次一个」名单:数量保持原版 1 个, 但仍然会从附近箱子自动取木头 —— 做饭的基础火一次烧 5 个木头太快。
  • 就近取料:背包料不够时,从半径 30 米内的箱子自动补足。 默认认 木箱 / 加固型宝箱 / 黑金宝箱 / 灰石箱 / 桶 / 衣柜; 个人宝箱与珍宝箱有意排除。
  • 板车也算料源(IncludeCarts,默认开):静止停放的板车/雪橇与箱子同样会被取料; 正在被拖行的板车会跳过(位置在变,且去接管所有权可能把车抢走)。

    ⚠️ 不需要把 Cart 加进 AllowedContainers —— 板车走的是独立的识别通道, 与箱子白名单无关(判断依据见下方「板车是怎么被认出来的」)。

  • 占用保护:队友站在箱子旁就跳过它,避免抢走所有权导致对方物品回弹。
  • 霜铸熔炉加燃料(1.0.7 新增):给霜铸熔炉(piece_FrostFoundry,官方中文「霜铸熔炉」) 加液态冰霜($item_frozenfuel)时,也能从附近箱子 / 板车自动取料。 一次 1 个(与原版一致,不做批量)。

    为什么要单开一项:它挂的是 CookingStation 组件,不是熔炉用的 Smelter —— 原先两套补丁都覆盖不到它。详见下方「霜铸熔炉(1.0.7 起)」专节。

  • 木炭窑联动(1.0.8 新增):给一座炭窑加木材时,把周围 10 米内的炭窑一起装满 (每座各装 BatchSize 个)。详见下方「木炭窑联动(1.0.8 起)」。
  • 地面掉落物(1.0.8 新增):熔炉 / 高炉加燃料时,背包不够就先捡地上的木炭, 还不够再找箱子。详见下方「地面掉落物(1.0.8 起)」。

配置(5 节)

节 内容
1 - Batch Fill Enabled / BatchSize / StationOreAllow / FrostFoundryPrefabs / KilnLinkPrefabs / KilnLinkRadius
2 - Fire Sources Enabled / Exclude / Sources(留空 = 全部火源生效)/ OneAtATime / HoldInterval / RespectToggle
3 - Nearby Chests Enabled / Radius / GroundPickup / AllowedContainers / IncludeCarts / RespectWards / ClaimOwnership
4 - Safety SkipInUse / SkipIfPlayerNear / PlayerNearDistance
5 - Debug DebugLogging

BepInEx/config/easycraft.client.only.cfg ⚠️ 改完必须重启游戏。

板车是怎么被认出来的(1.0.5 修正)

板车(Cart)与雪橇(Sled)用的都是游戏的 Vagon 组件,内部各带一个容器。 它们不在 AllowedContainers 白名单里 —— 走的是独立识别通道,两条信号任一命中即算板车:

优先级 信号 原理
主 CartRegistry(本模组维护) 用 Harmony 补丁复刻游戏自己的 Vagon.m_instances 清单(Awake 里加、OnDestroy 里删),再用 Vagon.m_container 反查出对应的容器
兜底 Container.m_wagon != null 游戏字段,两者互相引用

为什么不用 Container.m_wagon 当唯一依据(1.0.5 修的就是这个): 这个字段在整个游戏代码里零处写入(只靠预制体序列化),只有 3 处读取, 且全部带 null 判断:

Container.RPC_RequestOpen / RPC_RequestStack / RPC_RequestTakeAll
    if (IsInUse() || (m_wagon != null && m_wagon.InUse())) { 拒绝本次开启 }

⇒ 说明游戏本身就预期它可能为 null,为 null 时一切正常、没有任何可见症状。 而旧版代码只认它 ⇒ 一旦为 null,板车就会被当成"名字不在白名单的普通箱子"静默跳过。

反过来 Vagon.m_container 是必须连的:UpdateMass / UpdateLoadVisualization 都靠它算载重(往板车里放东西会让车变沉,这是核心可见行为)。⇒ 以它为主信号才可靠。

什么情况会跳过板车

情况 是否取料 日志里会说什么
静止停放 ✅ 取 正常取料日志
正在被拖行 ❌ 跳过 [板车被跳过] … → 正在被拖行(停放后即可取)
IncludeCarts = false ❌ 跳过 [板车被跳过] … → IncludeCarts 开关是关的
有人正开着它的容器 ❌ 跳过 [有人正在用] …(受 SkipInUse 控制)
守卫石无权限 ❌ 跳过 [守卫石无权限] …(受 RespectWards 控制)

每条诊断都会带 板车[是(哪条信号)/否],能直接看出识别有没有生效。

木炭窑联动(1.0.8 起)

给一座炭窑加木材,把周围一圈炭窑一起装满。

配置 默认 说明
1 - Batch Fill → KilnLinkPrefabs charcoal_kiln 名单里的窑才联动;留空 = 关闭
1 - Batch Fill → KilnLinkRadius 10 米 搜索半径;0 = 关闭
  • 每座窑各装一份(BatchSize 个),各自按剩余容量封顶 —— 不是总量分摊。 4 座窑 = 各 5 个,共消耗 20 个木材。
  • 料源共享(背包 → 附近箱子),按距离由近到远先到先得。
  • ⚠️ 炭窑加木材走的是进料口(OnAddOre)而不是燃料口 —— 炭窑的机制本来就是"把木材烧成木炭"(StationOreAllow 的默认值也印证了这点)。
  • 找邻居:Physics.OverlapSphere + GetComponentInParent<Smelter>()。 必须去重 —— 一个建筑身上有多个 Collider,不去重会把同一座窑重复装填好几次。
  • 你正在操作的那一座先按原逻辑装(所以"已满" / "没料"的提示文案是准的); 它失败且还没动过料源时直接交回原版 ⇒ 不会出现"既扣了料、又被原版再扣一次"。

地面掉落物(1.0.8 起)

熔炉 / 高炉加燃料时,取料顺序由 背包 → 箱子 变成 背包 → 地面 → 箱子。

配置 默认 说明
3 - Nearby Chests → GroundPickup $item_coal 允许从地上捡的物品;留空 = 关闭
(半径) 沿用 3 - Nearby Chests → Radius(30 米) 圆心判据同箱子:机器 或 玩家,命中任一即可

⚠️ 生效范围刻意收窄:只对 Smelter 的燃料口生效 —— 矿石口 / 火源 / 霜铸熔炉(CookingStation)完全不碰。

名字三种写法都认(忽略大小写):$item_coal / item_coal / Coal。

数量为什么是准的

地面上一堆物品 = 一个 ItemDrop 对象,真实数量在 m_itemData.m_stack 里(可能装着十几个), 绝不能按"一个对象 = 一个物品"来数。IL 实证:

// ItemDrop.RemoveOne()
if (!CanPickup(true)) { RequestOwn(); return false; }          // 没写权 → 一个都不取
if (m_itemData.m_stack <= 1) { m_nview.Destroy(); return true; }
m_itemData.m_stack -= 1;  Save();  return true;

// ItemDrop.CanPickup(bool) 末尾:return m_nview.IsOwner();
// ItemDrop.SetStack(int)  首句:if (!IsOwner()) return;      // 静默失败,所以本模组不用它

⇒ 本模组的做法:

  1. 先 ZNetView.ClaimOwnership()(与箱子取料同一套模式)
  2. Load() 刷新 m_itemData,避免按旧 stack 决定取多少
  3. 只取 min(还需要的, 这堆有的),逐次 RemoveOne() 并按其返回值计数 —— 返回 false 就是没拿到写权,这一下不算数
  4. 遍历前给 ItemDrop.s_instances 做快照(取空会 Destroy,边遍历边改集合会漏项)

宁可少加(下次交互还能补),绝不虚报数量。

霜铸熔炉(1.0.7 起)

问题:给霜铸熔炉加液态冰霜时只能从自己背包拿 1 个,放在附近箱子里的取不过来。

根因:霜铸熔炉(piece_FrostFoundry)挂的是 CookingStation 组件, 不是熔炉 / 高炉用的 Smelter。上面的「批量装填」只接管 Smelter(OnAddOre / OnAddFuel) 与 Fireplace(火源),所以对霜铸熔炉完全没生效。

组件身份是实测的 —— 预制体脚本引用 path_id 交叉比对 (同一 bundle 内,同一个 C# 类只有一个 MonoScript 资产、只有一个固定 path_id, 所以两个预制体引用同一 path_id 即同一个组件):

预制体 组件脚本 path_id
piece_FrostFoundry 霜铸熔炉 A, B, C, 4793016392223414848
piece_cookingstation 烹饪台 A, B, C, 4793016392223414848 ← 完全一致
piece_FrostKiln 霜窑 A, B, -1171893114429426784, C
smelter / blastfurnace / charcoal_kiln / windmill / spinningwheel 都带 -1171893114429426784

⇒ -1171893114429426784 = Smelter,4793016392223414848 = CookingStation。 运行日志也一致:Player.log 里 piece_FrostFoundry 零条 EasyCraft 记录, 而同族的 piece_FrostKiln(真·Smelter)有 10 条 [站点燃料口装填]。

做法:接管 CookingStation.OnAddFuelSwitch,燃料名从 m_fuelItem 动态读 (霜铸熔炉实测 = 液态冰霜),数量固定 1,不足时走与其它站点同一条「就近取料」链路。 RPC 按原版 InvokeRPC("RPC_AddFuel") 调一次 (上游这个方法固定 SetFuel(GetFuel()+1)、无 amount 参数、服务端也不封顶)。

配置 FrostFoundryPrefabs

  • 默认 piece_FrostFoundry;留空 = 该功能完全关闭
  • ⚠️ 普通「烹饪台 / 铁烹饪架」用的是同一个 CookingStation 组件,所以这里是白名单: 只写进去的才接管,名单外的烹饪站行为与不装本模组时一模一样。

明确不碰的两处

  • 铸胚投料口(CookingStation.OnAddFoodSwitch):本模组源码对 OnAddFoodSwitch / UseItem / OnInteract 零引用,只挂了 OnAddFuelSwitch。
  • 批量:燃料口只加 1 个 —— 只解决"取不到料",不改变加料节奏。

站点进料限制 StationOreAllow(1.0.2 起)

原版炭窑能烧 木材 / 细木 / 圆木 三种,价值差很多。这一项让本模组的自动装填 只挑你允许的材料 —— 默认就是「炭窑只烧木材」:

charcoal_kiln=$item_wood

想同时限制别的站点,用分号加组:

charcoal_kiln=$item_wood ; smelter=$item_copperore,$item_tinore
  • 材料名三种写法都认(忽略大小写):$item_wood / item_wood / Wood

  • 站点名也三种都认:charcoal_kiln(GameObject 名)/ piece_charcoalkiln(本地化键)/ charcoalkiln —— 内部会去掉 piece_ 与下划线后归一化比较,所以顺手怎么写都能匹配上。

  • 常用站点名:

    站点 GameObject 名
    炭窑 charcoal_kiln
    冶炼炉 smelter
    高炉 blastfurnace
    风车 windmill
    纺织机 piece_spinningwheel
    埃达之力提炼器 piece_eitrrefinery
  • 只影响本模组的自动装填(批量装填 + 就近取料);你自己手动往站点里放什么都不受限制。

  • 留空 = 不限制任何站点(完全回到原版);某个站点写成 站点=(右边留空)= 单独放行该站点。

  • 名字万一写错,站点不会变成"什么都装不进去" —— 会退回原版行为, 同时打一条 Warning 把该站点实际可用的材料名列出来,直接照抄即可。

  • 生效情形看日志里的 站点进料限制生效:charcoal_kiln 只允许装填 [$item_wood](该站点可用共 3 种:…)。

三张火源名单叠加后的语义(1.0.1 起)

Exclude OneAtATime 行为
✅ 命中 ❌ 不在 完全交回原版(一次 1 个、也不从箱子取料)
✅ 命中 ✅ 在 数量固定 1 个,但料源(附近箱子)可用 ← 营火默认
❌ 未命中 — 正常接管,一次装 BatchSize 个

想给别的火源也用「一次一个 + 拉料」,把它的 GameObject 名同时加进 Exclude 和 OneAtATime。

⚠️ 填火源名字前先看这里

Exclude / Sources 里填的是 GameObject 名,它不一定等于本地化键:

官方中文 GameObject 名 容易误写
营火(能做饭的基础火) fire_pit piece_firepit
篝火 bonfire piece_bonfire
灶 hearth piece_hearth
壁灯 piece_dvergr_lantern —
火炬 piece_groundtorch / piece_groundtorchwood … —

不确定某个火源到底叫什么,看日志里的 [火源探测] 行 —— 它会把「组件在=…」和「ZDO名=…」都打出来。

⚠️ 填箱子名字前先看这里(1.0.6 修正)

好消息:你几乎不用管下划线怎么写 —— 匹配已归一化,两种写法都认。

归一化规则 = 忽略大小写 + 去 piece_ 前缀 + 去掉所有下划线。 所以下面这些写法全部等价,随便挑一种:

piece_chest_blackmetal   ==  piece_chestblackmetal  ==  CHESTBLACKMETAL

为什么改成归一化(1.0.4 的"修正"其实修错了)

游戏里 GameObject 名与本地化键不是同一个字符串。真机日志实证(黑金宝箱采样 260 次):

GameObject 名(匹配用的是这个) 本地化键 一致?
piece_chest $piece_chest ✅
piece_chest_blackmetal $piece_chestblackmetal ❌ 不一致
piece_chest_private $piece_chestprivate ❌ 不一致

⇒ 多词箱子的 GameObject 名带下划线、本地化键不带;单词的 piece_chest 两者相同。 这就是为什么大箱子一直好使,而黑金箱等一直静默失配。

⚠️ 1.0.0~1.0.4 的经历值得一提:1.0.4 曾放话说"箱子名一律不带下划线",依据是扫了 resources.assets —— 而那份里装的其实是本地化键,不是预制体名, 结果把一个原本能匹配的名字(黑金箱)反而写坏了。现在两种写法都通,不必再纠结。

箱子对照表(归一化键 = 实际参与匹配的键)

官方中文 GameObject 名 本地化键 归一化键 默认纳入
宝箱(木箱) piece_chest_wood $piece_chestwood chestwood ✅
加固型宝箱(大箱子) piece_chest $piece_chest chest ✅
黑金宝箱 piece_chest_blackmetal $piece_chestblackmetal chestblackmetal ✅
灰石箱 piece_chest_grausten $piece_chestgrausten chestgrausten ✅
桶 piece_chest_barrel $piece_chestbarrel chestbarrel ✅
衣柜 piece_chest_warderobe $piece_chestwarderobe chestwarderobe ✅
个人宝箱 piece_chest_private $piece_chestprivate chestprivate 排除
珍宝箱 piece_chest_treasure $piece_chesttreasure chesttreasure 排除

想恢复某个被排除的箱子(比如个人宝箱),把它的任意一种写法加进 AllowedContainers 即可:

piece_chestprivate            ← 这样写
piece_chest_private           ← 或者这样写,完全等价

启动自检会同时打印你写的原文和生效的归一化键,名字对不对得上可以当场核对:

就近取料=开:半径 30 米,允许的箱子 6 种 [piece_chest,piece_chestbarrel,…](板车=开),…
  箱子名匹配已归一化(去 piece_ 前缀 + 去所有下划线 + 忽略大小写):生效匹配键=[chest,chestbarrel,…]

💡 现在站点名与箱子名用的是同一套归一化规则,不会再出现"这个要精确、那个能宽松"的混乱。

实现要点

  • 站点装填:Smelter.OnAddOre / OnAddFuel 的 prefix;火源:Fireplace.Interact 的 prefix。
  • 料源:Container.Awake / OnDestroyed 维护活箱子登记表,搜索时以【机器】和【玩家】各自为圆心。 取料顺序:先吃背包 → 按距离更近优先逐个箱子取 → 取够即停。
  • 取料前用 ZNetView.ClaimOwnership() 接管箱子 ZDO 所有权 —— 必须, 否则 Container.OnContainerChanged() 的 if (!IsOwner()) return; 会让改动不被保存,产生复制。
  • 🔴 Harmony prefix 铁律:处理不了就 return true 完整交回原版,绝不静默 return false —— 那会表现为「按 E 完全没反应」,极难排查。

联机

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

不声明 NetworkCompatibilityAttribute,不引用 Jotunn,不内嵌 ServerSync / CCS。

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