群系检测

一个区块只画一格,格子的颜色由「这个区块里哪种群系占主导」决定。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,先判的赢):

  1. meanBiomeOccurences < lavaOccurences → 伪群系 lava
  2. 否则 meanBiomeOccurences < groundOccurences → 伪群系 lavaShore
  3. 否则 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 就不覆盖(与村庄的优先级仲裁策略不同)。

相关条目