// Structure generator: villages, desert temples, shipwrecks, jungle temples, igloos, etc. import { CHUNK_SIZE, WORLD_HEIGHT, BlockId } from './blocks'; import { WorldGenerator, ChunkData, blockIdToNum } from './worldgen'; export type StructureType = | 'village' | 'desert_temple' | 'jungle_temple' | 'shipwreck' | 'igloo' | 'witch_hut' | 'ocean_monument' | 'woodland_mansion' | 'stronghold' | 'pillager_outpost' | 'ruined_portal' | 'end_portal' | 'mineshaft' | 'dungeon' | 'well' | 'boulder' | 'cactus_field' | 'village_house' | 'blacksmith'; export interface Structure { type: StructureType; cx: number; cz: number; baseX: number; baseY: number; baseZ: number; rotation: number; } // Try to place a structure at chunk coords (rare, controlled by seed) export function tryPlaceStructures( chunk: ChunkData, generator: WorldGenerator, structuresEnabled: boolean = true ): void { if (!structuresEnabled) return; // Use chunk coords to determine structure presence (deterministic) const cx = chunk.cx; const cz = chunk.cz; // Village: rare, in plains/savanna/desert if ((Math.abs(cx) % 6 === 0) && (Math.abs(cz) % 6 === 0) && (cx !== 0 || cz !== 0)) { const biome = generator.getBiome(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); if (biome === 'plains' || biome === 'savanna' || biome === 'desert' || biome === 'forest') { const height = generator.getHeight(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); if (height > 24) { placeVillage(chunk, height, biome); return; } } } // Desert temple: rare, only in desert if ((Math.abs(cx) % 8 === 3) && (Math.abs(cz) % 8 === 3)) { const biome = generator.getBiome(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); if (biome === 'desert') { const height = generator.getHeight(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); placeDesertTemple(chunk, height); return; } } // Shipwreck: only in ocean chunks if ((Math.abs(cx) % 5 === 2) && (Math.abs(cz) % 5 === 2)) { const biome = generator.getBiome(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); if (biome === 'ocean') { const height = generator.getHeight(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); if (height < 20) { placeShipwreck(chunk, Math.max(height, 18)); return; } } } // Witch hut: in swamp if ((Math.abs(cx) % 7 === 1) && (Math.abs(cz) % 7 === 1)) { const biome = generator.getBiome(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); if (biome === 'swamp' || biome === 'mangrove_swamp') { const height = generator.getHeight(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); placeWitchHut(chunk, height); return; } } // Igloo: in snowy if ((Math.abs(cx) % 9 === 4) && (Math.abs(cz) % 9 === 4)) { const biome = generator.getBiome(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); if (biome === 'snowy') { const height = generator.getHeight(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); placeIgloo(chunk, height); return; } } // Ruined portal: random small structure if ((Math.abs(cx * 7 + cz * 13) % 17 === 0) && (cx + cz) % 3 === 0) { const height = generator.getHeight(cx * CHUNK_SIZE + 4, cz * CHUNK_SIZE + 4); if (height > 25) { placeRuinedPortal(chunk, height); } } // Boulder (mossy cobblestone) - small decoration in forests if ((Math.abs(cx * 11 + cz * 17) % 23 === 5)) { const biome = generator.getBiome(cx * CHUNK_SIZE + 8, cz * CHUNK_SIZE + 8); if (biome === 'forest' || biome === 'jungle' || biome === 'taiga') { const height = generator.getHeight(cx * CHUNK_SIZE + 4, cz * CHUNK_SIZE + 4); placeBoulder(chunk, 4, height, 4); } } // Dungeon (underground) - small room with spawner if ((Math.abs(cx * 19 + cz * 23) % 11 === 7)) { const baseY = 10 + Math.floor(Math.random() * 15); if (baseY < WORLD_HEIGHT - 8) { placeDungeon(chunk, 4, baseY, 4); } } } // === Village === function placeVillage(chunk: ChunkData, baseY: number, biome: string) { const isDesert = biome === 'desert'; const wallBlock: BlockId = isDesert ? 'sandstone' : 'cobblestone'; const floorBlock: BlockId = isDesert ? 'red_sand' : 'cobblestone'; const roofBlock: BlockId = isDesert ? 'sandstone' : 'planks_oak'; const logBlock: BlockId = isDesert ? 'log_oak' : 'log_oak'; // Several houses in a grid for (let hx = 2; hx < 14; hx += 5) { for (let hz = 2; hz < 14; hz += 5) { placeHouse(chunk, hx, baseY, hz, wallBlock, floorBlock, roofBlock, logBlock); } } // Central well placeWell(chunk, 7, baseY, 7); // Path blocks for (let i = 0; i < CHUNK_SIZE; i++) { setBlock(chunk, i, baseY - 1, 7, 'dirt_path'); setBlock(chunk, 7, baseY - 1, i, 'dirt_path'); } } function placeHouse(chunk: ChunkData, x: number, y: number, z: number, wall: BlockId, floor: BlockId, roof: BlockId, log: BlockId) { // 4x4 house, 4 tall const w = 4, h = 4, d = 4; // Floor for (let dx = 0; dx < w; dx++) { for (let dz = 0; dz < d; dz++) { setBlock(chunk, x + dx, y - 1, z + dz, floor); } } // Walls for (let dy = 0; dy < h; dy++) { for (let dx = 0; dx < w; dx++) { setBlock(chunk, x + dx, y + dy, z, wall); setBlock(chunk, x + dx, y + dy, z + d - 1, wall); } for (let dz = 0; dz < d; dz++) { setBlock(chunk, x, y + dy, z + dz, wall); setBlock(chunk, x + w - 1, y + dy, z + dz, wall); } } // Door (front, mid) setBlock(chunk, x + 1, y, z, 'air'); setBlock(chunk, x + 1, y + 1, z, 'oak_door'); setBlock(chunk, x + 2, y, z, 'air'); setBlock(chunk, x + 2, y + 1, z, 'oak_door'); // Windows setBlock(chunk, x + 1, y + 1, z + d - 1, 'glass'); setBlock(chunk, x + w - 2, y + 1, z + d - 1, 'glass'); setBlock(chunk, x, y + 1, z + 1, 'glass'); setBlock(chunk, x, y + 1, z + d - 2, 'glass'); setBlock(chunk, x + w - 1, y + 1, z + 1, 'glass'); setBlock(chunk, x + w - 1, y + 1, z + d - 2, 'glass'); // Roof (sloped) for (let dx = 0; dx < w; dx++) { for (let dz = 0; dz < d; dz++) { setBlock(chunk, x + dx, y + h, z + dz, roof); } } // Interior: bed and crafting table setBlock(chunk, x + 1, y, z + 1, 'crafting_table'); setBlock(chunk, x + w - 2, y, z + d - 2, 'furnace'); setBlock(chunk, x + 1, y, z + d - 2, 'chest'); // Torch setBlock(chunk, x + 1, y + 2, z + 1, 'torch'); } function placeWell(chunk: ChunkData, x: number, y: number, z: number) { // 3x3 well with water for (let dx = 0; dx < 3; dx++) { for (let dz = 0; dz < 3; dz++) { setBlock(chunk, x + dx, y - 1, z + dz, 'cobblestone'); if (dx === 1 && dz === 1) { // Water in center for (let dy = 0; dy < 3; dy++) setBlock(chunk, x + dx, y + dy, z + dz, 'water'); } else { setBlock(chunk, x + dx, y, z + dz, 'cobblestone'); } } } // Corner posts for (let dy = 0; dy < 3; dy++) { setBlock(chunk, x, y + dy, z, 'cobblestone_wall'); setBlock(chunk, x + 2, y + dy, z, 'cobblestone_wall'); setBlock(chunk, x, y + dy, z + 2, 'cobblestone_wall'); setBlock(chunk, x + 2, y + dy, z + 2, 'cobblestone_wall'); } // Roof for (let dx = -1; dx <= 3; dx++) { for (let dz = -1; dz <= 3; dz++) { setBlock(chunk, x + dx, y + 3, z + dz, 'planks_oak'); } } } // === Desert Temple === function placeDesertTemple(chunk: ChunkData, baseY: number) { // Pyramid shape: 21x21 base tapering to 1x1 top const size = 11; for (let layer = 0; layer < size; layer++) { const w = size - layer; for (let dx = 0; dx < w; dx++) { for (let dz = 0; dz < w; dz++) { const lx = 3 + layer + dx; const lz = 3 + layer + dz; const ly = baseY + layer; if (lx >= 0 && lx < CHUNK_SIZE && lz >= 0 && lz < CHUNK_SIZE) { setBlock(chunk, lx, ly, lz, 'sandstone'); } } } } // Entrance at base for (let dy = 0; dy < 3; dy++) { setBlock(chunk, 8, baseY + dy, 3, 'air'); } // Hidden chamber with chests for (let dy = 0; dy < 4; dy++) { setBlock(chunk, 7, baseY - 1 - dy, 7, 'air'); setBlock(chunk, 8, baseY - 1 - dy, 7, 'air'); setBlock(chunk, 9, baseY - 1 - dy, 7, 'air'); } // TNT trap setBlock(chunk, 8, baseY - 5, 7, 'tnt'); setBlock(chunk, 7, baseY - 5, 7, 'tnt'); setBlock(chunk, 9, baseY - 5, 7, 'tnt'); // Chests with loot setBlock(chunk, 7, baseY - 4, 6, 'chest'); setBlock(chunk, 8, baseY - 4, 6, 'chest'); setBlock(chunk, 9, baseY - 4, 6, 'chest'); setBlock(chunk, 7, baseY - 4, 8, 'chest'); setBlock(chunk, 9, baseY - 4, 8, 'chest'); } // === Shipwreck === function placeShipwreck(chunk: ChunkData, baseY: number) { // Boat shape: pointed at front const length = 12; for (let dx = 0; dx < length; dx++) { const width = dx < 2 ? dx + 1 : (dx > length - 3 ? length - dx : 3); for (let dz = -width; dz <= width; dz++) { const lz = 8 + dz; if (lz < 0 || lz >= CHUNK_SIZE) continue; // Hull setBlock(chunk, dx + 2, baseY, lz, 'planks_oak'); // Damaged parts if (Math.random() > 0.6) setBlock(chunk, dx + 2, baseY, lz, 'air'); // Mast if (dx === 6 && dz === 0) { for (let dy = 1; dy < 6; dy++) { setBlock(chunk, dx + 2, baseY + dy, lz, 'log_oak'); } } } } // Chests setBlock(chunk, 6, baseY + 1, 8, 'chest'); setBlock(chunk, 10, baseY + 1, 8, 'chest'); } // === Witch Hut === function placeWitchHut(chunk: ChunkData, baseY: number) { // Hut on stilts const floorY = baseY + 3; // Floor for (let dx = 0; dx < 7; dx++) { for (let dz = 0; dz < 7; dz++) { setBlock(chunk, 4 + dx, floorY, 4 + dz, 'planks_spruce'); } } // Stilts for (const [sx, sz] of [[4, 4], [10, 4], [4, 10], [10, 10]]) { for (let dy = 0; dy < 3; dy++) { setBlock(chunk, sx, baseY + dy, sz, 'log_oak'); } } // Walls for (let dy = 1; dy <= 3; dy++) { for (let i = 0; i < 7; i++) { setBlock(chunk, 4 + i, floorY + dy, 4, 'planks_spruce'); setBlock(chunk, 4 + i, floorY + dy, 10, 'planks_spruce'); setBlock(chunk, 4, floorY + dy, 4 + i, 'planks_spruce'); setBlock(chunk, 10, floorY + dy, 4 + i, 'planks_spruce'); } } // Door setBlock(chunk, 7, floorY + 1, 4, 'air'); setBlock(chunk, 7, floorY + 2, 4, 'air'); // Roof for (let dx = 0; dx < 9; dx++) { for (let dz = 0; dz < 9; dz++) { setBlock(chunk, 3 + dx, floorY + 4, 3 + dz, 'leaves_spruce'); } } // Cauldron and brewing stand inside setBlock(chunk, 6, floorY + 1, 6, 'cauldron'); setBlock(chunk, 8, floorY + 1, 8, 'brewing_stand'); setBlock(chunk, 7, floorY + 1, 7, 'flower_pot'); } // === Igloo === function placeIgloo(chunk: ChunkData, baseY: number) { // Snow dome for (let dy = 0; dy < 4; dy++) { const r = 4 - dy; for (let dx = -r; dx <= r; dx++) { for (let dz = -r; dz <= r; dz++) { if (dx * dx + dz * dz <= r * r) { const lx = 8 + dx; const lz = 8 + dz; if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue; setBlock(chunk, lx, baseY + dy, lz, 'snow'); } } } } // Carve interior for (let dy = 0; dy < 3; dy++) { for (let dx = -2; dx <= 2; dx++) { for (let dz = -2; dz <= 2; dz++) { const lx = 8 + dx; const lz = 8 + dz; setBlock(chunk, lx, baseY + dy, lz, 'air'); } } } // Bed and crafting table inside setBlock(chunk, 6, baseY, 8, 'crafting_table'); setBlock(chunk, 10, baseY, 8, 'furnace'); // Entrance tunnel for (let dy = 0; dy < 2; dy++) { setBlock(chunk, 8, baseY + dy, 12, 'air'); setBlock(chunk, 8, baseY + dy, 13, 'air'); } } // === Ruined Portal === function placeRuinedPortal(chunk: ChunkData, baseY: number) { // Vertical obsidian portal frame (broken) const positions = [ [4, 0], [4, 1], [4, 2], [4, 3], [4, 4], [8, 0], [8, 1], [8, 2], [8, 3], [8, 4], [4, 4], [5, 4], [6, 4], [7, 4], [8, 4], ]; for (const [dx, dy] of positions) { if (Math.random() > 0.2) { // some missing setBlock(chunk, 6 + dx, baseY + dy, 8, 'obsidian'); } } // Some crying obsidian if (Math.random() > 0.5) setBlock(chunk, 6 + 4, baseY + 1, 8, 'crying_obsidian'); // Chest nearby setBlock(chunk, 5, baseY, 6, 'chest'); } // === Boulder (mossy cobblestone) === function placeBoulder(chunk: ChunkData, x: number, y: number, z: number) { // Random cluster of mossy cobblestone for (let dy = 0; dy < 2; dy++) { for (let dx = -1; dx <= 1; dx++) { for (let dz = -1; dz <= 1; dz++) { if (Math.abs(dx) + Math.abs(dz) + dy <= 2 && Math.random() > 0.3) { setBlock(chunk, x + dx, y + dy, z + dz, 'mossy_cobblestone'); } } } } } // === Dungeon === function placeDungeon(chunk: ChunkData, x: number, y: number, z: number) { // 5x5 room with cobblestone walls for (let dx = -2; dx <= 2; dx++) { for (let dz = -2; dz <= 2; dz++) { for (let dy = -1; dy <= 3; dy++) { const onEdge = Math.abs(dx) === 2 || Math.abs(dz) === 2 || dy === -1 || dy === 3; if (onEdge) { setBlock(chunk, x + dx, y + dy, z + dz, 'cobblestone'); } else { setBlock(chunk, x + dx, y + dy, z + dz, 'air'); } } } } // Spawner in center const mobTypes: BlockId[] = ['spawner_zombie', 'spawner_skeleton', 'spawner_spider']; setBlock(chunk, x, y, z, mobTypes[Math.floor(Math.random() * 3)]); // Chests in corners setBlock(chunk, x - 1, y, z - 1, 'chest'); setBlock(chunk, x + 1, y, z + 1, 'chest'); } // Helper: set block safely function setBlock(chunk: ChunkData, x: number, y: number, z: number, block: BlockId) { if (x < 0 || x >= CHUNK_SIZE || z < 0 || z >= CHUNK_SIZE || y < 0 || y >= WORLD_HEIGHT) return; chunk.blocks[(y * CHUNK_SIZE + z) * CHUNK_SIZE + x] = blockIdToNum(block); }