import { BaseGenerator } from "./BaseGenerator.js"; import { TrenchTextureGenerator } from "./textures/TrenchTextureGenerator.js"; import { SimplexNoise } from "../utils/SimplexNoise.js"; /** * Generates "Contested Frontier" biome: * - Linear trench lanes. * - Shell craters. * - Fortifications. */ export class ContestedFrontierGenerator extends BaseGenerator { constructor(grid, seed) { super(grid, seed); this.textureGen = new TrenchTextureGenerator(seed); this.generatedAssets = { palette: {}, spawnZones: { player: [], enemy: [] }, }; this.preloadTextures(); } preloadTextures() { const VARIATIONS = 10; const TEXTURE_SIZE = 128; // Walls (120-129) for (let i = 0; i < VARIATIONS; i++) { const seed = this.seed + i * 100; this.generatedAssets.palette[120 + i] = this.textureGen.generateWall( TEXTURE_SIZE, seed ); } // Floors (220-229) // We split into 3 tiers of 3-4 variants each // 220-222: Muddy (Bias -0.6) // 223-226: Mixed (Bias 0.0) // 227-229: Grassy (Bias 0.6) // Muddy for (let i = 0; i < 3; i++) { const seed = this.seed + i * 200; this.generatedAssets.palette[220 + i] = this.textureGen.generateFloor( TEXTURE_SIZE, seed, -0.6 ); } // Mixed for (let i = 0; i < 4; i++) { const seed = this.seed + (i + 3) * 200; this.generatedAssets.palette[223 + i] = this.textureGen.generateFloor( TEXTURE_SIZE, seed, 0.0 ); } // Grassy for (let i = 0; i < 3; i++) { const seed = this.seed + (i + 7) * 200; this.generatedAssets.palette[227 + i] = this.textureGen.generateFloor( TEXTURE_SIZE, seed, 0.6 ); } } generate() { // 1. Terrain Shape // We want a central trench (lower Y) surrounded by higher ground (banks). // The trench should meander slightly along the Z-axis (if lane is X-axis) or X-axis (if lane is Z-axis). // Let's assume the "Lane" is along the Z-axis (player starts at Z=0, enemy at Z=end). const trenchNoise = new SimplexNoise(this.seed); // Parameters const bankHeight = 5; const trenchFloorY = 2; // Depth of trench const trenchWidth = 8; // Width in blocks for (let x = 0; x < this.width; x++) { for (let z = 0; z < this.depth; z++) { // Calculate Trench Center per Z slice // Meander the center X coordinate const meander = trenchNoise.noise2D(z * 0.1, 0) * (this.width * 0.2); const centerX = this.width / 2 + meander; // Distance from current X to center X const dist = Math.abs(x - centerX); // Determine Ground Height // If within trench width, low height. If outside, bank height. // We use smooth interpolation for a slope let targetHeight = bankHeight; const halfWidth = trenchWidth / 2; if (dist < halfWidth) { targetHeight = trenchFloorY; } else if (dist < halfWidth + 3) { // Slope area (3 blocks wide) const t = (dist - halfWidth) / 3; // 0 to 1 targetHeight = trenchFloorY + t * (bankHeight - trenchFloorY); } // Add some noise to the ground height const groundNoise = trenchNoise.noise2D(x * 0.2, z * 0.2) * 1.5; let yHeight = Math.floor(targetHeight + groundNoise); // Clamp height if (yHeight < 1) yHeight = 1; if (yHeight > this.height - 2) yHeight = this.height - 2; // Fill Voxels for (let y = 0; y <= yHeight; y++) { // Pick Texture ID // If it's a "Wall" (vertical step) use 120+, else Floor 220+ // Simple heuristic: fill solid. We will texture pass later. this.grid.setCell(x, y, z, 1); // Temp solid } } } // 2. Texture Pass this.applyTextures(); // 3. Scatter Fortifications (Sandbags, etc) this.scatterFortifications(); // 4. Scatter Nature (Stumps, Rocks) this.scatterCover(10, 0.03); // Use standard cover ID 10 for nature // 5. Define Spawn Zones // Player at Z-Start, Enemy at Z-End for (let x = 1; x < this.width - 1; x++) { for (let z = 1; z < 5; z++) { // Player Zone (Z < 5) this.checkSpawnSpot(x, z, "player"); } for (let z = this.depth - 6; z < this.depth - 1; z++) { // Enemy Zone (Z > Depth-6) this.checkSpawnSpot(x, z, "enemy"); } } } checkSpawnSpot(x, z, type) { // Find surface Y let y = this.height - 1; while (y > 0 && this.grid.getCell(x, y, z) === 0) { y--; } // Must be solid floor if (this.grid.getCell(x, y, z) !== 0) { // Add spawn (standing one block above floor) this.generatedAssets.spawnZones[type].push({ x, y: y + 1, z }); } } applyTextures() { // Noise for biome distribution (Mud vs Grass patches) // Frequency 0.1 gives similar scale to existing generators const biomeNoise = new SimplexNoise(this.seed + 999); for (let x = 0; x < this.width; x++) { for (let z = 0; z < this.depth; z++) { // Find surface Y let maxY = -1; for (let y = this.height - 1; y >= 0; y--) { if (this.grid.getCell(x, y, z) !== 0) { maxY = y; break; } } if (maxY !== -1) { // Determine Biome Patch Type const noiseVal = biomeNoise.noise2D(x * 0.15, z * 0.15); // -1 to 1 let textureId; if (noiseVal < -0.3) { // Muddy Patch (220-222) textureId = 220 + this.rng.rangeInt(0, 2); } else if (noiseVal > 0.3) { // Grassy Patch (227-229) textureId = 227 + this.rng.rangeInt(0, 2); } else { // Mixed Patch (223-226) textureId = 223 + this.rng.rangeInt(0, 3); } // Surface Block: Floor Texture this.grid.setCell(x, maxY, z, textureId); // Blocks below: Wall/Dirt Texture for (let y = 0; y < maxY; y++) { this.grid.setCell(x, y, z, 120 + (y % 5)); // varied dirt } } } } } scatterFortifications() { // Place Sandbags (ID 12) along the edges of the trench (Top of the slope) // Logic: If I am on a high block and neighbor is significantly lower, place sandbag for (let x = 1; x < this.width - 1; x++) { for (let z = 1; z < this.depth - 1; z++) { // Find surface y let y = 0; while (y < this.height && this.grid.getCell(x, y + 1, z) !== 0) { y++; } // y is now the top solid block level (assuming we found one) if (this.grid.getCell(x, y, z) === 0) continue; // Should be solid // Check neighbors for dropoff const neighbors = [ { x: x + 1, z }, { x: x - 1, z }, { x, z: z + 1 }, { x, z: z - 1 }, ]; let isRidge = false; for (const n of neighbors) { // Get neighbor height let ny = 0; // scan up to reasonable height while ( ny < this.height && this.grid.getCell(n.x, ny + 1, n.z) !== 0 ) { ny++; } // If neighbor is 2+ blocks lower, we are on a ridge/trench lip if (y > ny + 1) { isRidge = true; break; } } if (isRidge && this.rng.chance(0.4)) { // Place Sandbag on top (y+1) this.grid.setCell(x, y + 1, z, 12); } } } } }