aether-shards/src/generation/ContestedFrontierGenerator.js

257 lines
7.5 KiB
JavaScript

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);
}
}
}
}
}