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.
PlantEasilyFix
[AI-generated] Compatibility patch for Advize-PlantEasily: keeps grid planting and auto-replant correct when other mods operate on the same placement piece, including large grids. Read the applicability notice first.
By s6652289
CHANGELOG
Changelog
1.2.2
再修 bug:补丁 3「大网格全量补摆」仍然从未生效 —— 1.2.1 只修了一半。
- 根因(2026-09-25 真机实证):
ModConfig.Columns是实例属性 (上游ModConfig是sealed class,该属性无static),而 1.2.0 / 1.2.1 用Invoke(null, null)调它 ⇒ 每次都抛TargetException: Non-static method requires a target., 被RepositionAllGhosts自己的 catch 吞成一条LogError。 该行在for循环之前,所以补摆一次都没进入过循环。 真机日志:补摆幽灵坐标失败(…):Non-static method requires a target.× 54, 而[补摆 …]成功自证行 0。 - 为什么 1.2.1 的自检没拦住:当时新增的
RequireArity只核对参数个数 (get_Columns是 0 参 ✓),没核对静态性 ⇒ 句柄解析通过、启动日志照样打 「已生效:…并当场补摆全部网格格位」。这与 1.2.0 那次(4 参)是同一个 「解析得到 ≠ 调用得动」失效模式的第二次出现。 - 修正:
- 解析期从静态字段
ModContext.config取ModConfig实例(新增句柄_modConfigInstanceField), 调用期改为_columnsGetter.Invoke(modConfigInstance, null); RequireArity升级为 参数个数 + 静态性双检(新增hasInstance形参): 实例成员拿不到实例时,在解析阶段就停用补摆并打出明确日志,不再等到运行时被吞掉;- 不再尝试
GhostGrid.Columns—— 上游GhostGrid根本没有该成员,而 HarmonyX 的AccessTools.Property找不到时会打一条Could not find property … GhostGrid and name Columns的 Warning,与「补摆降级」混淆,纯属噪音(真机日志行 242 就是它)。
- 解析期从静态字段
- IL 复核(
asm_ildump.py):_columnsGetter的Invoke前已是ldloc.0(实例)而不再是ldnull; 同类静态句柄(_maxActiveGhostsGetter)则仍是ldnull—— 形成对照。 - 适配确认(官方 2.2.2 DLL metadata):
ModConfig.get_Columns/get_Rows=mdStatic=False(实例); 而GhostGrid的GetGhostPosition/GetGhostObject/GetGhostCache/UpdateGhostTransform/get_MaxActiveGhosts/_gridVersion全部static=True。 - 顺带订正文档:README 的伪代码片段此前仍写着 1.2.0 的错误调用
(
GetGhostPosition(go, slot % columns, slot / columns, slot)—— 4 个实参,且row/col顺序颠倒), 已改为与实现一致的 3 参GetGhostPosition(slot / columns, slot % columns, slot)。 - DLL 重新编译:15,360 字节,sha256
0c8bc0bfe52d51daee245f18935c0a739317d18c5e2b6322a2c76380ccfd7340。
连读三代:1.2.0 跑不了(4 参)→ 1.2.1 还是跑不了(实例当 null 调)→ 1.2.2 才真正跑起来。 两次根因同类,都是"反射句柄解析成功 ≠ 调用得动"。
1.2.1
修 bug:补丁 3「大网格全量补摆」自 1.2.0 起从未真正生效。
- 根因:
GhostGrid.GetGhostPosition的签名是 3 个参数(int row, int column, int index),而 1.2.0 按(go, col, row, slot)传了 4 个实参 ⇒ 每次调用抛TargetParameterCountException,被RepositionAllGhosts自己的 catch 吞成一条LogError⇒ 补摆一次都没成功过。 而启动自检照样打印「已生效:…并当场补摆全部网格格位」⇒ 极具误导性。 - 修正:调用改回 3 参
(row, col, slot)(row = slot / Columns、col = slot % Columns, 与上游Update的div/rem一致)。行为与 1.2.0 的设计意图完全相同,只是这次真的会跑。 - 防复发:新增
RequireArity—— 解析阶段逐个核对每个反射句柄的参数个数 (GetGhostPosition3、UpdateGhostTransform2、GetGhostObject/GetGhostCache1、MaxActiveGhosts/Columns0),不符即停用补摆并打出「上游给 N 个、本补丁按 M 个调用」的明确日志, 不再静默。 - 可自证:首次成功补摆时无条件打一行
[补摆 …] 已摆正 N 个幽灵、同步 N 个坐标缓存(不再依赖[General] VerboseLog)—— 1.2.0 的失效正是藏在静默里,这次让它一开机就看得出来。 - 适配确认:对
Advize-PlantEasily 2.2.1官方 DLL 做了 metadata 核对 ——GetGhostPosition= 3 参、UpdateGhostTransform= 2 参、GhostCache字段piece/plant/lastUpdatedVersion均在;2.2.0与2.2.1的这些签名完全相同 (⇒ 1.2.0 的 bug 与版本升级无关,是从写下来那天就有的)。 - DLL 重新编译:14,336 字节,sha256
9ff095b1dead21af54fff1ad21719ee9cabc319e289a1c440389d9a2318c8da9。 - 编译后 IL 复核(
asm_ildump.py):newarr System.Object前的ldc.i4.3已从 4 变 3, 且三个实参装载顺序为ldloc row → ldloc col → ldloc slot,与上游GetGhostPosition(int row, int column, int index)逐一对应。
一处方法学更正
1.2.0 的"离线验证"(verify_reposition_logic.py 等)用 Python 重算了坐标算法、
枚举了 BuildGrid/Update 的帧内时序,但从没做过真实反射调用
⇒ 正好落在「离线模拟只能验证拿到输入之后的分支,输入从哪来必须真机验」的盲区里。
本次改为直接对官方 DLL 读 metadata 核对参数个数。
⚠️ 1.2.2 补注:本版声称的「只是这次真的会跑」并未成立 ——
columns那行(_columnsGetter.Invoke(null, null))在for循环之前就抛了TargetException。 也就是说 1.2.1 修对了参数个数,但补摆依然一次都没跑起来。详见 1.2.2 条目。
1.2.0
新增第三条机制:大网格幽灵「全量补摆」—— 修复网格调大后仍有零星几株作物种到一起。 DLL 重新编译:13824 字节。
- 起因:用户反馈「小范围种植没问题,范围调大后还是会有一些种到一起」。
- 为什么补丁 1(
BuildGrid后_gridVersion++)不够 —— 它保证的是 "下一帧会被摆正",而不是"点下去的时候已经摆正了"。查下来有两重缺口:- 缺口一(帧内时序,小网格也会中招):
Player.SetupPlacementGhost重建网格(BuildGrid) 与每帧的GhostGrid.Update在同一帧内的先后顺序不固定。若Update先跑, 它摆好的坐标随后被BuildGrid → InitializeGhosts整体覆盖回主幽灵位置, 而本帧再无第二次Update(BuildGrid的唯一调用点是PlayerSetupPlacementGhost.Postfix0052)。 - 缺口二(分批轮转,大网格才中招):
GhostGrid.Update每帧只摆B = min(GhostUpdateBatchSize, 总格数)个,且从_ghostUpdateIndex(只 +1、永不复位) 开始轮转。本机GhostUpdateBatchSize=50:4×4(16) 与1×20(20) 一帧摆完,10×20(200) 首帧漏 150 个、20×20(400) 首帧漏 350 个。 - 叠加条件:
_gridVersion只在位移 > 1cm / 朝向 > 1° / 有效朝向 > 0.1° /Rows·Columns变化时才自增 ⇒ 鼠标一停,版本号冻结 ⇒UpdateGhost开头的脏检查把没轮到的幽灵全部早退挡掉 ⇒ 它们永久停在主幽灵位置。 - 按下种植时
GetGhostPosition(0, 0)对它们全返回同一个BasePosition, 而PlayerPlacePiece.Postfix只按for (i = 0; i < MaxActiveGhosts; i++)逐槽收集 (无坐标去重)⇒ 这几株全部种在同一点。
- 缺口一(帧内时序,小网格也会中招):
- 修复:
BuildGrid之后不再依赖后续帧 —— 当场把[1, MaxActiveGhosts]全部补摆:GetGhostObject(slot)→GetGhostPosition(go, slot % Columns, slot / Columns, slot)→UpdateGhostTransform(go, pos)→GetGhostCache(go).lastUpdatedVersion = _gridVersion。- 方位分解
slot / Columns、slot % Columns与原版Update里那两行div/rem完全一致 (已核对编译后 IL:两条指令确实都在)。 - ✅ 只写坐标与缓存版本号,不调
UpdateGhostStatus⇒ 不介入任何合法性判定。 - ✅ 不推高
GhostUpdateBatchSize⇒ 只在BuildGrid那一刻做一次全量, 之后每帧仍是原版分批节奏,不吃性能(这是本方案优于"抬批次"之处)。
- 方位分解
- 自证日志(
[General] VerboseLog = true时):[补摆 3] 网格 20 格(MaxActiveGhosts=19, Columns=20)→ 已摆正 19 个幽灵、 同步 19 个坐标缓存;累计补摆 3 次 / 57 个。已摆正 N 个应等于MaxActiveGhosts。 - 反射句柄缺失时自动降级:新增的 8 个反射目标(
GetGhostPosition/UpdateGhostTransform/GetGhostObject/GetGhostCache/lastUpdatedVersion/ExtraGhosts/MaxActiveGhosts/Columns)缺任何一个都只关闭补摆 (各打一条补摆降级:…警告),补丁 1 / 2 照常工作。
离线验证
verify_plantEasilyFix_1_2_0.py:DLL 自证 全部命中 PASS —— 三条机制的方法名与目标成员、补摆链路的 8 个反射目标、版本号与配置键, 并确认RepositionAllGhosts的 IL 里div/rem指令确实存在。verify_reposition_logic.py:9 种网格尺寸(含 20×20)下 补摆后每个格位坐标唯一、方位分解与原版div/rem逐槽一致 ✅。verify_reposition_order.py:枚举BuildGrid/Update的帧内先后顺序, 确认两重缺口都真实存在、且补摆对两种顺序都成立 ✅。
一处诊断更正(记录在案)
初版诊断把本机 1×20 归因于"分批轮转漏掉 30 个"——这是错的:
1×20 总格数只有 20,batch=50 一帧就能扫完全部,分批不构成缺口。
真正的缺口是上面的缺口一(帧内时序);分批轮转只在 10×20 及以上的大网格上
作为第二重缺口叠加进来。补摆方案对两者都覆盖,故结论与实现不变,仅归因更正。
1.1.2
仅文档 —— DLL 仍与 1.1.0 逐字节一致(10752 字节,sha256 c1700599807e4087…)。
- 把「兼容性补丁」这一新定位推到线上:1.1.1 只带了适用性声明,正文仍按"修 bug"叙述; 本版把正文一并改掉。
- README 正文重写:章节标题从「症状 / 根因 / 修复方式」改为中性的 「默认行为 / 实现位置 / 归位方式」;明确写出 「只装 BepInExPack_Valheim + PlantEasily 时该症状不复现」;撤掉全部归因措辞; 开头的概述改成"把几个模组共用的那两处运行时状态归位"。
manifest.json描述改为[AI-generated] Compatibility patch for Advize-PlantEasily: keeps grid planting and auto-replant correct when other mods operate on the same placement piece; auto-replant also grows back the crop you harvested. Read the applicability notice first.- 源码
src/PlantEasilyFixPlugin.cs的类级 XML 注释同步为同一口径 (注释不进 IL,故仍不重新编译)。
1.1.1
仅文档 —— DLL 一字未改。
- README 顶部新增中英双语「适用性声明 / Applicability Notice」:本补丁是在本机 (BepInEx 5.4.2350 / 游戏 1.0.15 / 30 个启用模组)那套环境下做出来的,所修症状 有可能是模组组合互相冲突造成的,并不一定是原模组本身的 bug,可能不适用于他人的环境; 如遇问题请先向原模组作者反馈。
manifest.json的description末尾加一句Read the applicability notice first.; README 表格里的「本补丁版本」同步更新为 1.1.1。- 正文定位整体改为「兼容性补丁」:确认「只装 BepInExPack + PlantEasily 时该症状不复现」之后,
README 里原先按"原模组自身的 bug"叙述的措辞全部撤掉,统一表述为
"把几个模组共用的运行时状态归位",且不指名任何具体模组;
manifest.json的描述同步改为Compatibility patch ...; 章节标题也从「修复方式 / 症状」改成中性的「归位方式 / 默认行为」。 - 源码
src/PlantEasilyFixPlugin.cs的类级 XML 注释同步改为同一口径 (注释不进 IL,所以不影响产物,也就没有重新编译)。 - 补丁本体未重新编译:
PlantEasilyFix.dll10752 字节, sha256c1700599807e408753f766a17868199ce1bf8632413947bad33db763478ce05b—— 与 1.1.0 完全一致,已装 1.1.0 的不需要换文件。
1.1.0
新增「按作物补种」修正 —— 自动补种不再统一成同一种作物。
- 新增 Harmony postfix:
Advize_PlantEasily.PlacementState.SetReferences→ 把静态字段LastSelectedPlantName置回null。 - 效果:开启
[Harvesting]ReplantOnHarvest后,每一种被收割的作物都补种回它自己, 而不是统一补成"你最近用耕耘机选中的那一种"。混种地块不再越收越单一。 - 该字段只有两处读取(补种判定 + 悬停提示),且都有空值分支 —— 置空无副作用;
悬停提示
Replant as: X也会同步显示"实际会补种的那种",可直接在游戏里核对。 - 新增配置项:[Replant]
SameCropReplant = true。关掉即恢复 PlantEasily 原行为, 改动无需重启游戏,下一次补种即时生效。 - 新增模组图标
icon.png(两株高矮不同的作物 + 补丁对勾)。
1.0.0
- 首个版本:修复 PlantEasily 网格种植(Rows/Columns ≥ 2)时,整片作物 (如 4×4 = 16 株)全部叠在同一个点上的 bug。
- 实现:Harmony postfix
Advize_PlantEasily.GhostGrid.BuildGrid→_gridVersion++, 让所有幽灵的坐标缓存失效,下一帧必定重算。