package world import ( "sync" "regionio/internal/worldgen" ) // monster_rooms.go ports net.minecraft.world.level.levelgen.feature. // MonsterRoomFeature, the stage-3 underground-structures feature behind the // monster_room and monster_room_deep placed features. Both share one configured // feature with an empty config; only their placement modifiers differ, and the // generic placement driver already handles those. // // The rooms matter far beyond their own blocks: they open cave_air pockets // during the stage that runs before the ores. Ore ellipsoids crossing those // pockets roll air-exposure discards, so replaying monster rooms in the right // order is what lets the ore schedule see the world vanilla's ores saw. // // The port follows bytecode fidelity like the carvers do: every random draw, // loop order, and early return is vanilla's. Block entities are not modelled // yet — chests place as plain block states and the spawner's mob pick still // consumes its draw so later placement positions stay on vanilla's stream. const undergroundStructuresStage = 3 var ( monsterRoomOnce sync.Once monsterRoomCannotTable []bool monsterRoomStatesOnce sync.Once monsterCaveAirID uint16 monsterChestIDs map[string]uint16 monsterSpawnerID uint16 monsterCobbleID uint16 monsterMossyID uint16 monsterAirIDs map[uint16]bool monsterCannotTable []bool ) func initMonsterRoomTables() { monsterRoomOnce.Do(func() { names, err := worldgen.FeaturesCannotReplace() if err != nil { panic(err) } stateByIDOnce.Do(buildStateTable) monsterCannotTable = make([]bool, totalBlockStates) for _, name := range names { for _, id := range idsByName[name] { if int(id) < len(monsterCannotTable) { monsterCannotTable[id] = true } } } }) monsterRoomStatesOnce.Do(func() { stateByIDOnce.Do(buildStateTable) monsterCaveAirID = monsterMustState("minecraft:cave_air", nil) monsterSpawnerID = monsterMustState("minecraft:spawner", nil) monsterCobbleID = monsterMustState("minecraft:cobblestone", nil) monsterMossyID = monsterMustState("minecraft:mossy_cobblestone", nil) monsterChestIDs = map[string]uint16{} for _, facing := range []string{"north", "south", "west", "east"} { monsterChestIDs[facing] = monsterMustState("minecraft:chest", map[string]string{ "facing": facing, "type": "single", "waterlogged": "false", }) } monsterAirIDs = map[uint16]bool{ mustMonsterAir("minecraft:air"): true, mustMonsterAir("minecraft:cave_air"): true, } }) } func monsterMustState(name string, props map[string]string) uint16 { id, ok := nameToStateID(name, props) if !ok { panic("world: missing block state for monster rooms: " + name) } return id } func mustMonsterAir(name string) uint16 { return monsterMustState(name, nil) } // monsterSafeSetBlock is Feature.safeSetBlock: replace unless the existing // state is in #minecraft:features_cannot_replace. func (r *decorationRegion) monsterSafeSetBlock(x, y, z int, state uint16) bool { if monsterCannotTable[r.getBlock(x, y, z)] { return false } return r.setBlock(x, y, z, state) } // placeScheduledMonsterRooms replays the vanilla stage-3 schedule from one // source center into the mutable decoration region. func (r *decorationRegion) placeScheduledMonsterRooms(seed int64) error { set, err := worldgen.LoadFeatureSet() if err != nil { return err } initMonsterRoomTables() if err := r.ensureSourceNeighborhood(); err != nil { return err } schedule, err := set.FeatureSchedule(possibleBiomeOrder(), r.sourceBiomes(), undergroundStructuresStage) if err != nil { return err } random, decorationSeed := worldgen.DecorationRandom(seed, int(r.sourceX), int(r.sourceZ)) origin := worldgen.FeaturePosition{X: int(r.sourceX) << 4, Y: MinY, Z: int(r.sourceZ) << 4} for _, scheduled := range schedule { placed, ok := set.Placed[scheduled.Name] if !ok { continue } configured, ok := set.Configured[placed.Feature] if !ok || configured.Type != "minecraft:monster_room" { continue } random.SetFeatureSeed(decorationSeed, scheduled.Index, undergroundStructuresStage) context := r.placementContext(func(position worldgen.FeaturePosition) bool { return r.biomeAllowsFeature(set, scheduled.Name, undergroundStructuresStage, position) }) if err := set.ForEachPlacementPosition(scheduled.Name, random, origin, context, func(position worldgen.FeaturePosition) error { placeMonsterRoom(r, random, position.X, position.Y, position.Z) return nil }); err != nil { return err } } return nil } // placeMonsterRoom ports MonsterRoomFeature.place for one origin. func placeMonsterRoom(r *decorationRegion, random worldgen.RandomSource, ox, oy, oz int) bool { initMonsterRoomTables() j := int(random.NextIntN(2)) + 2 // x half-extent minus walls: 2..3 o := int(random.NextIntN(2)) + 2 // z half-extent minus walls: 2..3 k, l := -j-1, j+1 // full x span, walls included p, q := -o-1, o+1 // full z span // Pass 1 validates the shell: solid floor at y=-1 and ceiling at y=4 for // every column, and between one and five open side openings at y=0. openings := 0 for x := k; x <= l; x++ { for y := -1; y <= 4; y++ { for z := p; z <= q; z++ { solid := monsterIsSolid(r.getBlock(ox+x, oy+y, oz+z)) if y == -1 && !solid { return false } if y == 4 && !solid { return false } if (x == k || x == l || z == p || z == q) && y == 0 { if monsterIsAir(r.getBlock(ox+x, oy, oz+z)) && monsterIsAir(r.getBlock(ox+x, oy+1, oz+z)) { openings++ } } } } } if openings < 1 || openings > 5 { return false } // Pass 2 carves: interior becomes cave air; walls become cobblestone, with // a mossy three-in-four roll on the floor row; a wall block whose own floor // was already carved becomes cave air outright, bypassing the replaceable // guard just as vanilla's unconditional setBlock does. The mossy roll only // draws for a solid non-chest cell that reaches the wall branch — the same // conditions under which vanilla reaches its nextInt(4). for x := k; x <= l; x++ { for y := 3; y >= -1; y-- { for z := p; z <= q; z++ { bx, by, bz := ox+x, oy+y, oz+z current := r.getBlock(bx, by, bz) interior := x != k && y != -1 && z != p && x != l && y != 4 && z != q if interior { if current == monsterChestIDs["north"] || current == monsterChestIDs["south"] || current == monsterChestIDs["west"] || current == monsterChestIDs["east"] || current == monsterSpawnerID { continue } r.monsterSafeSetBlock(bx, by, bz, monsterCaveAirID) continue } // The mossy roll draws only here, exactly where vanilla's // floor branch sits; every other wall row goes cobblestone // without a draw. placeWall := func() { state := monsterCobbleID if y == -1 && random.NextIntN(4) != 0 { state = monsterMossyID } r.monsterSafeSetBlock(bx, by, bz, state) } if by < MinY { if !monsterIsSolid(current) || isMonsterChest(current) { continue } placeWall() continue } if !monsterIsSolid(r.getBlock(bx, by-1, bz)) { r.setBlock(bx, by, bz, monsterCaveAirID) continue } if !monsterIsSolid(current) || isMonsterChest(current) { continue } placeWall() } } } // Pass 3 tries two chest spots, three times each: an air cell with exactly // one opaque horizontal neighbour, faced away from it. An adjacent chest // leaves the new one at its default facing instead. for attempt := 0; attempt < 2; attempt++ { for try := 0; try < 3; try++ { cx := ox + int(random.NextIntN(int32(j*2+1))) - j cz := oz + int(random.NextIntN(int32(o*2+1))) - o cy := oy if !monsterIsAir(r.getBlock(cx, cy, cz)) { continue } solids := 0 var solidDir [2]int adjacentChest := false for _, d := range [4][2]int{{0, -1}, {1, 0}, {0, 1}, {-1, 0}} { neighbor := r.getBlock(cx+d[0], cy, cz+d[1]) if isMonsterChest(neighbor) { adjacentChest = true continue } if monsterIsSolidRender(neighbor) { solids++ solidDir = d } } if solids != 1 { continue } facing := "north" if !adjacentChest { switch { case solidDir[0] == 1: facing = "west" case solidDir[0] == -1: facing = "east" case solidDir[1] == 1: facing = "north" default: facing = "south" } } if r.monsterSafeSetBlock(cx, cy, cz, monsterChestIDs[facing]) { // Vanilla seeds the chest's loot table from one long here. random.NextLong() } } } // Pass 4 places the spawner at the center; picking its mob is one draw. if r.monsterSafeSetBlock(ox, oy, oz, monsterSpawnerID) { random.NextIntN(4) // skeleton / zombie / zombie / spider } return true } func isMonsterChest(state uint16) bool { return state == monsterChestIDs["north"] || state == monsterChestIDs["south"] || state == monsterChestIDs["west"] || state == monsterChestIDs["east"] } // monsterIsSolid approximates BlockState.isSolid, which in this version reads // the state's blocks-motion property. func monsterIsSolid(state uint16) bool { return stateFlags(state)&flagBlocksMotion != 0 } // monsterIsSolidRender approximates BlockState.isSolidRender: an occluding, // fully opaque cube. func monsterIsSolidRender(state uint16) bool { f := stateFlags(state) return f&flagCanOcclude != 0 && lightOpacity(state) == 15 } func monsterIsAir(state uint16) bool { return monsterAirIDs[state] }