94 lines
6.6 KiB
Markdown
94 lines
6.6 KiB
Markdown
# 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,在项目目录运行:
|
||
|
||
```powershell
|
||
.\gradlew.bat build
|
||
```
|
||
|
||
Linux / macOS:
|
||
|
||
```sh
|
||
./gradlew build
|
||
```
|
||
|
||
如果终端默认使用 Java 21,先将当前终端的 `JAVA_HOME` 指向 JDK 25。IntelliJ IDEA 中也应把 Gradle JVM 设置为 25。
|
||
|
||
服务器游戏测试:
|
||
|
||
```powershell
|
||
.\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` 对象。
|
||
|
||
### 具体改动
|
||
|
||
1. **按区块段读方块,整段跳过空气段**:改走 `ChunkAccess#getSections()` + `LevelChunkSection#getBlockState()`;对 `hasOnlyAir()` 的段整段跳过。空段里只可能是空气,不可能提供水源或沙地,因此跳过它读不到任何会成为候选的方块。
|
||
2. **区块解析次数由 5329 次降到 ≤36 次**:循环由「列 → 层」改为「区块 → 段 → 方块」,每列只解析一次区块。原来的 `Level#hasChunk(x >> 4, z >> 4)` 换成等价的 `Level#getChunk(cx, cz, ChunkStatus.FULL, false)`——`hasChunk` 内部正是这个调用;`loadOrGenerate = false`,**依然绝不加载或生成区块**。
|
||
3. **沙地候选改用打包 long**:`ArrayList<BlockPos>` → fastutil `LongArrayList`(`BlockPos.asLong` / `getX|getY|getZ(long)`),一次扫描不再产生成千上万个临时对象。
|
||
4. **`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。
|