群系检测
一个区块只画一格,格子的颜色由「这个区块里哪种群系占主导」决定。ItemAtlas 为此按维度选一个 IBiomeDetector。
接口
hunternif.mc.atlas.core.IBiomeDetector,一个方法:
| 成员 | 值 |
|---|---|
int getBiomeID(Chunk chunk) |
返回该区块要用的群系 ID;找不到返回 NOT_FOUND |
NOT_FOUND |
int = -1 |
NOT_FOUND = -1 与伪群系 ID 的取值区间不冲突:ExtTileIdMap 从 lastID = -1 开始递减发号(findNewID() 里 --lastID),所以自定义 ID 全是 -2、-3…,而「未扫描到自然群系」保留 -1。
维度绑定
ItemAtlas 构造时绑定两个,其余维度回落到主世界检测器:
| 维度 ID | 检测器 | 依据 |
|---|---|---|
0(主世界 / Overworld) |
BiomeDetectorBase |
setBiomeDetectorForDimension(0, biomeDetectorOverworld) |
-1(下界 / Nether) |
BiomeDetectorNether |
setBiomeDetectorForDimension(-1, biomeDetectorNether) |
| 其它所有(含 GTNH 模组维度) | BiomeDetectorBase |
getBiomeDetectorForDimension 查不到时返回 biomeDetectorOverworld |
setBiomeDetectorForDimension(int, IBiomeDetector) 是 public,别的模组可覆盖某个维度的检测器。
BiomeDetectorBase 的 doScanPonds 在 ItemAtlas 构造时由 settings.doScanPonds 注入(biomeDetectorOverworld.setScanPonds(settings.doScanPonds))——只有主世界检测器受 do_scan_ponds 影响,下界检测器根本不读这个开关。
BiomeDetectorBase(主世界 / 通用)
hunternif.mc.atlas.core.BiomeDetectorBase。类注释:Detects the 256 vanilla biomes, water pools and lava pools. Water and beach biomes are given priority because shore line is the defining feature of the map, and so that rivers are more connected.
权重
priorityForBiome(BiomeGenBase):
| 条件 | 权重 |
|---|---|
waterBiomes[biomeID](BiomeDictionary.Type.WATER) |
4 |
beachBiomes[biomeID](BiomeDictionary.Type.BEACH) |
3 |
| 其它 | 1 |
waterBiomes / beachBiomes 是两个 boolean[256],由静态方法 scanBiomeTypes() 遍历 BiomeDictionary.getBiomesForType(Type.WATER) 与 Type.BEACH 填充。
水塘 / 岩浆塘
doScanPonds 为 true 时,遍历 16×16 每个 (x, z):取 chunk.getHeightValue(x, z) 作为 y,再读 y-1(topBlock)和 y(topBlock2)两层。读两层的理由写在注释里:For some reason lava doesn’t count in height value(并留了 TODO: check if 1.8 fixes this!)。
| 判定 | 累加 |
|---|---|
topBlock == Blocks.water 且 biomeID != BiomeGenBase.swampland.biomeID 且 != swampland.biomeID + 128 |
biomeOccurrences[BiomeGenBase.river.biomeID] += 3(priorityWaterPool) |
否则若 topBlock2 == Blocks.lava |
lavaOccurences += 6(prioritylavaPool) |
水塘直接记到 BiomeGenBase.river 这个原版 ID 上(常量 waterPoolBiomeID),不进伪群系表。排除沼泽是因为沼泽本来就整片是水。
结算
遍历 biomeOccurrences 取严格大于当前最大值的下标作为 meanBiomeId(平票时取 ID 较小者)。全 0 时 meanBiomeId 保持 NOT_FOUND。
最后一步:如果 meanBiomeOccurences < lavaOccurences,返回伪群系 lava(ExtTileIdMap.TILE_LAVA 的 ID)而不是某个原版群系。所以岩浆塘是伪群系,水塘不是。
BiomeDetectorNether(下界)
hunternif.mc.atlas.core.BiomeDetectorNether extends BiomeDetectorBase。类注释:Detects seas of lava, cave ground and cave walls in the Nether. 覆写整个 getBiomeID。
| 常量 | 值 | 用途 |
|---|---|---|
airProbeLevel |
50 | 探测空气 / 地面的高度 |
lavaSeaLevel |
31 | 探测岩浆的高度 |
priorityLava |
1 | 岩浆每格累加量(注释:让河流更容易连起来) |
判定逻辑
对 16×16 每格读 biomeID = chunkBiomes[x << 4 | z]:
| 分支 | 条件 | 结果 |
|---|---|---|
biomeID == BiomeGenBase.hell.biomeID |
读 (x, 31, z) 是 Blocks.lava |
lavaOccurences += 1 |
| 同上 | 否则读 (x, 50, z);null 或 Blocks.air |
groundOccurences++(算「地面」) |
| 同上 | 否则 | biomeOccurrences[biomeID]++(算「洞穴墙壁」) |
biomeID != hell |
「In case there are custom biomes “modded in”」 | biomeOccurrences[biomeID] += priorityForBiome(biomes[biomeID])(带越界与 null 检查) |
结算顺序(if / else if,先判的赢):
meanBiomeOccurences < lavaOccurences→ 伪群系lava- 否则
meanBiomeOccurences < groundOccurences→ 伪群系lavaShore - 否则
meanBiomeId(可仍是NOT_FOUND)
lavaShore 就是「岩浆海边缘的地面」——下界专用,主世界检测器不会产出。
伪群系 ID 分配
hunternif.mc.atlas.ext.ExtTileIdMap,单例,非线程安全。内部是 Guava BiMap<String, Integer> nameToIdMap(名字 ↔ ID 双向)。
| 方法 | 行为 |
|---|---|
getOrCreatePseudoBiomeID(name) |
仅服务端。已有则返回,没有则 findNewID() 发号并 markDirty() |
getPseudoBiomeID(name) |
未注册返回 NOT_FOUND = -1 |
getPseudoBiomeName(id) |
nameToIdMap.inverse().get(id) |
setPseudoBiomeID(name, id) |
读配置或收 TileNameIDPacket 时用,走 forcePut,注释警告这样设的 ID 不应被保存,否则客户端配置会不一致 |
syncOnPlayer(player) |
整张表发一条 TileNameIDPacket |
findNewID():while (lastID > Short.MIN_VALUE) { if (!inverse().containsKey(--lastID)) break; }——从 -1 往下递减,下限是 Short.MIN_VALUE(-32768),即最多约 32766 个伪群系。
TileNameIDPacket 走负 varint 编码正是因为这些 ID 必为负数(详见 网络消息)。
预定义的伪群系名
ExtTileIdMap 里硬编码 25 个常量。分两类:
村庄(12 个)
npcVillageLibrary、npcVillageSmithy、npcVillageLHouse、npcVillageFarmlandSmall、npcVillageFarmlandLarge、npcVillageWell、npcVillageTorch、npcVillageHut、npcVillageSmallHouse、npcVillageButchersShop、npcVillageChurch
被注释掉的两个:TILE_VILLAGE_PATH_X / TILE_VILLAGE_PATH_Z(VillageWatcher 里对应的 PATH = "ViSR" 分支整段注释掉了,理由是 Usually too narrow to be seen between the houses…)。
下界与下界要塞(13 个)
lava、lavaShore、netherBridge、netherBridgeX、netherBridgeZ、netherBridgeEndX、netherBridgeEndZ、netherBridgeGate、netherTower、netherWall、netherHall、netherFortStairs、netherThrone
lava 和 lavaShore 由本 mod 自己的检测器产出(见上),其余由 NetherFortressWatcher 从要塞结构数据写入。
这些名字都不是本 mod 注册的方块/物品——它们只是伪群系的字符串名,对应 tile_textures.json 里映射到的贴图集。
伪群系坐标的写入优先级
ItemAtlas.onUpdate 里伪群系无条件覆盖自然群系,注释:Custom tiles overwrite even the chunks already seen. 所以只要 extBiomeData 里某坐标有记录,每轮扫描都会重写那一格。
村庄写入时还有一层优先级仲裁:VillageWatcher.tilePriority 规定高优先级 tile 覆盖低优先级 tile。取值:npcVillageWell = 7(最高),npcVillageSmithy / npcVillageChurch = 6,npcVillageLibrary / npcVillageLHouse = 5,npcVillageButchersShop / npcVillageSmallHouse = 4,npcVillageFarmlandLarge / npcVillageFarmlandSmall / npcVillageHut = 3,npcVillageTorch = 1。比较逻辑是 curTilePriority >= prevTilePriority 才写。
要塞写入则用 noTileAt(world, chunkX, chunkZ) 判空——已有 tile 就不覆盖(与村庄的优先级仲裁策略不同)。
相关条目
- Antique Atlas - 检测器的调用方与按维度选择逻辑
- 地图数据模型 -
biomeID最终存进哪 - 网络消息 - 伪群系的 4 条相关封包
- 自动标记 - 村庄 / 要塞 watcher 往表里写
- SettingsConfig -
do_scan_ponds只影响主世界检测器