
PlantEasilyFix
[AI-generated] Patches for Advize-PlantEasily: (1) fixes grids planting every crop on one spot; (2) auto-replant grows back the crop you harvested, not the one you last selected. No cfg edit needed.PlantEasily Fix
给 Advize-PlantEasily 打的两个行为修正补丁(纯客户端,不改写 PlantEasily 任何文件):
| # | 修什么 | 一句话 |
|---|---|---|
| 1 | 重叠种植 | 网格种植时整片作物全部叠在同一个点 → 摆正 |
| 2 | 自动补种 | 收什么都补成同一种 → 改成每种作物各补各的 |
| 项 | 值 |
|---|---|
| 适用模组 | Advize-PlantEasily 2.2.0(2.0.0+ 逻辑相同,理论通用) |
| 本补丁版本 | 1.1.0 |
| 前置 | BepInEx 5.4.2333+ |
| 类型 | 客户端补丁(单人 / 联机客户端皆可,主机无需安装) |
| 依赖 | 无(未安装 PlantEasily 时静默不做事) |
补丁 1 · 重叠种植
症状
用 PlantEasily 一次种一片时,整片作物全部叠在同一个点上。 实测:4×4 = 15 个额外幽灵,坐标完全相同。
例(修复前的探针日志):
ghost[0..14] 全部 pos = (2062.731, 49.423, -4402.94)
不同坐标数 = 1 / 15
落点 1..15 全部 pos = (2062.731, 49.423, -4402.94)
根因
作者代码里有两处设计撞在一起:
① GhostGrid.InitializeGhosts 先把所有幽灵叠在一点
// 重建网格时,每个额外幽灵的位置被直接设成「主幽灵位置」
ExtraGhosts[i].transform.position = 主幽灵位置;
② 真正摆开坐标靠 GhostGrid.Update,但它有脏检查会早退
// GhostGrid.UpdateGhost
if (cache.lastUpdatedVersion == _gridVersion)
return; // ← 判定"已是最新",直接返回,不更新位置
③ 而 GhostCache.Init 不重置 lastUpdatedVersion
// GhostCache.Init —— 只设 piece / plant
this.piece = go.GetComponent<Piece>();
this.plant = go.GetComponent<Plant>();
// ★ 没有重置 lastUpdatedVersion ★
④ 触发点在批量种植协程的末尾
// <BulkPlanting>d__10.MoveNext 尾部(IL 偏移 +0389 起)
_isPlanting = false;
Player.SetupPlacementGhost(); // ← 重建幽灵网格
ReEnableRenderers(...);
SetupPlacementGhost → BuildGrid → GrowPoolIfNeeded(复用旧幽灵池)+ InitializeGhosts
⇒ 位置被重设为"全叠住",而复用幽灵身上残留的 lastUpdatedVersion 仍等于当前 _gridVersion
⇒ 下一帧 UpdateGhost 全部早退 ⇒ 位置永远停在"全叠住"状态。
修复方式
在 GhostGrid.BuildGrid 之后让版本号 +1:
[HarmonyPostfix]
static void BuildGridPostfix()
{
int before = (int)_gridVersionField.GetValue(null);
_gridVersionField.SetValue(null, before + 1); // 让所有幽灵缓存失效
}
所有幽灵的缓存立即失效 ⇒ 下一帧 GhostGrid.Update 必定重算每个幽灵的网格坐标
⇒ 玩家点击种植时幽灵已经摆正。
_gridVersion 是 private static int,只有 UpdateGridVersion() 会写它,
且该函数只会 +1 或保持不变、绝不回退,所以外部 +1 不破坏其单调性。
全局搜索确认它是纯模组内部使用(readonly 初始化 + 两处使用),无跨模组引用。
修复效果(探针实测,同一台机器)
| 修复前 | 修复后 | |
|---|---|---|
| 幽灵不同坐标数 | 1 / 15 | 15 / 15 |
| 落点坐标 | 15 个完全相同 | 15 个各不相同 |
修复后日志(探针版):
[修复] BuildGrid 完成 → _gridVersion 340 → 341
[诊断] Update 后已恢复正常:15 个幽灵分布到 15 种不同坐标 —— 修复生效
连续 88 次网格重建(含多次种植)全部正常。
补丁 2 · 自动补种按「被收获的那种作物」
症状
开着 [Harvesting]ReplantOnHarvest 时,收任何作物都补种成"你最近用耕耘机选中的那一种"。
于是混种地块越收越单一:今天最后种的是胡萝卜,明天收大麦、收萝卜,全给你补胡萝卜。
根因(InteractPatches.Prefix 第二重载的 IL 逐条实证)
| IL 偏移 | 逻辑 |
|---|---|
003D–0042 |
读 PlacementState.LastSelectedPlantName 并 IsNullOrEmpty |
0049 |
非空 ⇒ 查表键 = LastSelectedPlantName ← ★统一补种走这条 |
0050 |
为空 ⇒ 查表键 = Utils.GetPrefabName(被收获物) ← 各补各的走这条 |
005C |
ReplantDB.Registry.TryGetValue(键, out db),失败 ⇒ 不补种(交回原版) |
006C |
ModContext.PrefabRefs.TryGetValue(db.PlantName, out Piece) |
0090 |
Player.HaveRequirements / m_noPlacementCost 检查 |
00A6 |
PlacementController.PlacePiece(localPlayer, 被收获物, Plant.prefab) |
00C6 |
Player.ConsumeResources |
LastSelectedPlantName 的唯一写点是 PlacementState.SetReferences
(方法开头先置 null,仅当幽灵身上有 Plant 组件时才重新赋值),
而它的唯一调用者是 GhostGrid.InitializeGhosts
⇒ 只要种过一次某种作物,这个值就长期留着"那一种"。
该字段的读取点只有两处(已用全程序集成员反查确认,非推测):
InteractPatches.Prefix—— 决定补种哪一种;HoverTextPatches.Postfix—— 拼悬停提示Replant as: X。
两处都带 IsNullOrEmpty 分支,空值是合法状态(该字段在 .cctor 里初始就是 null)
⇒ 把它压制为 null 只会让两边都退回"按被收获物"分支,没有别的副作用。
顺带一提:
ReplantDB.Registry里同时登记了 Pickable 名与 Plant 名两套键 (ModInitPatches.Prefix里Registry.Add(pickable.name, db)), 所以"按被收获物查表"这条路本来就是通的 —— 只是被上面那个非空值挡住了。
修复方式
[HarmonyPostfix]
static void SetReferencesPostfix()
{
if (LastSelectedPlantName == null) return; // 本来就是空,不动
LastSelectedPlantName = null; // 让它退回"按被收获物"分支
}
怎么自己在游戏里验证
- 看提示:悬停一株成熟作物,hover 文本会多一行
Replant as: <绿>X</绿>—— X 就是本次实际会补种的那种。修复后收大麦应显示大麦,收胡萝卜应显示胡萝卜。 - 看日志:
BepInEx/LogOutput.log里会出现首次拦截成功:LastSelectedPlantName「…」→ 置空,此后补种按实际收获的作物进行。 - 想对比原行为:把
[Replant] SameCropReplant改成false,不用重启游戏, 下一次补种立刻恢复"统一成一种"。
配置
首次运行会在 BepInEx/config/s6652289.PlantEasilyFix.cfg 生成:
| 项 | 默认 | 说明 |
|---|---|---|
[General] Enabled |
true |
总开关。关掉并重启即等同于未安装 |
[General] VerboseLog |
false |
每次修正写一行日志(排查用)。正常游玩建议关闭 |
[Replant] SameCropReplant |
true |
补种是否按「被收获的那种作物」。改完立即生效,无需重启 |
安装
手动:把 PlantEasilyFix.dll 放进 BepInEx/plugins/。
r2modman:直接拖入本 zip,或把整个包放进
%APPDATA%\r2modmanPlus-local\Valheim\profiles\<profile>\BepInEx\plugins\PlantEasilyFix\。
安全性
- 补丁 1 只动一个
int版本号,补丁 2 只置一个静态字符串为null—— 不碰位置、资源、存档数据; - 不修改 PlantEasily 的任何配置项,无需改
advize.PlantEasily.cfg; - 未安装 PlantEasily 时,本插件等 2 分钟后静默退出,不做任何事;
- 所有反射调用都包在 try/catch 里 —— 补丁自身出问题最多是"这一次没修到", 不会让游戏崩溃;
- PlantEasily 版本变化导致方法/字段找不到时,只记一条 Error 并放弃该条补丁,不影响游戏;
- 两条补丁互相独立:某一条挂不上不影响另一条。
卸载
删掉 PlantEasilyFix.dll 即可。没有任何持久化改动需要还原(配置项只影响本插件自身)。
备注
- 本补丁是给第三方模组打的行为修正,不改写 PlantEasily 的文件; PlantEasily 作者一旦自行修复,本补丁可直接卸载。
- 与其它补丁/模组无冲突:只挂两个 postfix(
GhostGrid.BuildGrid、PlacementState.SetReferences),都不改原方法返回值。
AI 生成(AI-generated)