EasyCraft
[AI-generated] Load 5 items per interaction into smelters/refineries, with per-station material limits (e.g. charcoal kiln = wood only). Refuel torches/hearths, and auto-pull from nearby chests and carts. Client-only.EasyCraft
AI 生成声明:本模组由 AI 智能体(WorkBuddy)在用户的明确需求与逐项验收下编写; 功能设计、真机测试与发布决定均由用户主导。
炼制站点 / 火源的装填增强。 由 EasyBatchFill 与 EasyNearbyFill 合并而成 —— 合并同时消掉了原先两者之间那条「必须成对升级、单升一边 BepInEx 直接加载失败」的跨程序集硬依赖。
功能
- 批量装填:一次交互装入
BatchSize个(默认 5),取代原版的一次 1 个。 站点:熔炉 / 高炉 / 炭窑 / 风车 / 纺车 / Eitr 精炼厂。 - 站点进料限制(1.0.2 新增):限定某个站点只允许自动装填指定材料。 默认 = 炭窑只烧木材(不烧细木 / 圆木)。
- 火源加燃料:火炬 / 壁灯 / 篝火 / 火盆 / 灶 一次加
BatchSize个; 营火(fire_pit)默认走「一次一个」名单:数量保持原版 1 个, 但仍然会从附近箱子自动取木头 —— 做饭的基础火一次烧 5 个木头太快。 - 就近取料:背包料不够时,从半径 30 米内的箱子自动补足。 默认认 木箱 / 加固型宝箱 / 黑金宝箱 / 灰石箱 / 桶 / 衣柜; 个人宝箱与珍宝箱有意排除。
- 板车也算料源(
IncludeCarts,默认开):静止停放的板车/雪橇与箱子同样会被取料; 正在被拖行的板车会跳过(位置在变,且去接管所有权可能把车抢走)。⚠️ 不需要把
Cart加进AllowedContainers—— 板车走的是独立的识别通道, 与箱子白名单无关(判断依据见下方「板车是怎么被认出来的」)。 - 占用保护:队友站在箱子旁就跳过它,避免抢走所有权导致对方物品回弹。
配置(5 节)
| 节 | 内容 |
|---|---|
1 - Batch Fill |
Enabled / BatchSize / StationOreAllow |
2 - Fire Sources |
Enabled / Exclude / Sources(留空 = 全部火源生效)/ OneAtATime / HoldInterval / RespectToggle |
3 - Nearby Chests |
Enabled / Radius / 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 控制) |
每条诊断都会带 板车[是(哪条信号)/否],能直接看出识别有没有生效。
站点进料限制 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。


