Turtle Reproduce — 海龟就地产卵
适用于 Minecraft Java 26.1.2 / Fabric。海龟繁殖后会寻找当前位置附近最近水源边、能够走到的沙地,直接挖沙产卵,不再为了产卵返回出生地。
📌 本副本基于上游源文件修改:只对热路径做了一处性能优化,玩法行为不变(不做成成品 mod 分发,只提供工程源码)。 改动清单见文末「本副本的改动(相对上游源文件)」,逐行 diff 见
OPTIMIZATION.diff。
行为规则
- 从海龟当前位置搜索 X/Z 各 ±32 格、Y ±8 格的已加载区域,不主动加载或生成新区块。
- 沙地须上方为空气,且水平 4 格半径、垂直 ±1 格内有露出水面的静止水源。普通沙子和红沙均适用;流动水及含水方块不算水源。
- 优先选择距离海龟最近的水源对应的沙地;对应同一水源时,选择距离海龟最近的可达沙地。被墙挡住、站立空间不足的位置会跳过。
- 保留原版挖沙动画、产卵数量(1~4 枚)、繁殖冷却和孵化机制。
- 找不到合适地点时,海龟保留腹中的蛋并稍后重试。可在附近补上水源和沙地。
- 不改写海龟的出生地数据;没有携带蛋时的普通活动保留原版行为。
安装
需要 Java 25 或更高版本、Fabric Loader 0.19.5 或更高版本,以及适用于 26.1.2 的 Fabric API、Fabric Language Kotlin。
本包已随附编译好的成品 build/libs/turtle-reproduce-1.0.0.jar(由本包源码构建,20,528 字节,SHA-256 f61e53d152dd2722a4dda31d63cd8fedd201af312d5fce03bc4a3c8c36e704b6),可直接使用,无需自行编译。
将 build/libs/turtle-reproduce-1.0.0.jar 放入游戏实例或服务器的 mods 文件夹,不要安装 -sources.jar。
单人游戏安装到客户端。多人游戏安装到服务器即可,玩家客户端不需要安装本模组。
构建与测试
使用 JDK 25,在项目目录运行:
.\gradlew.bat build
Linux / macOS:
./gradlew build
如果终端默认使用 Java 21,先将当前终端的 JAVA_HOME 指向 JDK 25。IntelliJ IDEA 中也应把 Gradle JVM 设置为 25。
服务器游戏测试:
.\gradlew.bat runGameTest
build 会自动运行服务器游戏测试,也可用上面的命令单独运行。测试在独立世界覆盖真实繁殖、异地产卵、最近水岸、16 格外水岸、绕过封闭水岸、无沙地保留蛋、无水沙地拒绝产卵以及身体触水时选择干燥位置。测试代码不打包进发布模组。
手动体验时,可将两只海龟带离出生海滩,在新地点修建开阔沙岸和水池,用海草喂食繁殖。怀卵海龟应在新沙岸挖沙产卵。注意让沙岸足够宽,保留海龟进出水面的通道。
本副本的改动(相对上游源文件)
本副本以上游源文件为基础,改动只集中在一个文件,目的是降低服务端开销;玩法规则、判定条件、数值与数据格式一律未变。
| 项 | 内容 |
|---|---|
| 改动文件 | src/main/java/com/mr/ai/NestingSiteFinder.java(唯一被改动的文件) |
| 未改动 | 4 个 mixin(TurtleLayEggGoalMixin、TurtleGoHomeGoalMixin、TurtleAccessor、PathNavigationAccessor)、Kotlin 入口、fabric.mod.json、turtle-reproduce.mixins.json、src/client、全部 GameTest |
| 逐行 diff | OPTIMIZATION.diff(+146 / −35 行) |
为什么要改
NestingSiteFinder.findCandidates 是热点:单次调用要扫 73 × 73 列 × 19 层 ≈ 10.1 万个方块,而怀卵海龟只要偏离上次搜索原点超过 4 格、或把候选列表消费完,就会整片重扫。原实现每个方块都调用一次 Level#getBlockState(内部要重新解析区块、段索引与建筑高度),并为每块沙新建一个 BlockPos 对象。
具体改动
- 按区块段读方块,整段跳过空气段:改走
ChunkAccess#getSections()+LevelChunkSection#getBlockState();对hasOnlyAir()的段整段跳过。空段里只可能是空气,不可能提供水源或沙地,因此跳过它读不到任何会成为候选的方块。 - 区块解析次数由 5329 次降到 ≤36 次:循环由「列 → 层」改为「区块 → 段 → 方块」,每列只解析一次区块。原来的
Level#hasChunk(x >> 4, z >> 4)换成等价的Level#getChunk(cx, cz, ChunkStatus.FULL, false)——hasChunk内部正是这个调用;loadOrGenerate = false,依然绝不加载或生成区块。 - 沙地候选改用打包 long:
ArrayList<BlockPos>→ fastutilLongArrayList(BlockPos.asLong/getX|getY|getZ(long)),一次扫描不再产生成千上万个临时对象。 isValidSite去掉重复查询:提取循环不变量(方块坐标、建筑高度判定),并记住上一个查询过的区块可用性——半径 4 的圆盘最多跨 4 个区块。
为什么行为不变
- 搜索盒子的范围(X/Z ±32 加 4 格水源外扩、Y ±8 加 ±1)以及每一项判定条件与短路顺序逐条保留。
- 候选最终由同一个比较器排序,而它以沙地
x/y/z结尾,构成全序(不同位置永不相等)。因此结果与扫描的访问顺序无关,排序后的候选列表逐位相同。 javap比对:公开方法签名、4 个公开常量值(SEARCH_RADIUS = 32、VERTICAL_SEARCH_RADIUS = 8、WATER_RADIUS = 4、WATER_VERTICAL_RADIUS = 1)以及Candidate记录的成员完全一致;唯一差异是一个private辅助方法的签名,故其余 class(含 4 个 mixin)的链接关系不受影响。- 常量池比对:新增的类型引用只有原版 Minecraft 与 fastutil 的类,没有引入任何新的运行期依赖。
验证情况
| 项 | 状态 |
|---|---|
| 编译 | ✅ JDK 25,javac --release 25;class 文件版本 69,与上游构建一致 |
| 字节码 | ✅ javap 确认新逻辑(getSections / hasOnlyAir / LongArrayList#getLong / getChunk(..., false))已进入产物 |
| 链接 | ✅ 以真实 Minecraft 26.1.2 + Fabric API + fastutil 作为 classpath 做 Class.forName 反射检查,成员列表与上游逐条一致 |
| 游戏内实测 | ✅ Minecraft 26.1.2 单机:怀卵海龟在本地水岸沙地产卵、蛋数 1~4、home_pos 未被改写、距出生地数千格仍能就地产卵 |
| 7 个 GameTest | ⚠️ 未在本机执行(需要完整 Loom 构建并联网拉取依赖)。行为等价性由上述静态论证 + 游戏内实测支撑,建议在能联网的环境跑一次 .\gradlew.bat build 复核 |
许可
CC0-1.0。