258 lines
7.5 KiB
JavaScript
258 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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|