Change black empty tile to grass and make it destructable

This commit is contained in:
Peter Stockings
2026-01-05 20:59:33 +11:00
parent a7091c70c6
commit ecf58dded1
8 changed files with 150 additions and 43 deletions

View File

@@ -1,4 +1,5 @@
import { type World, type EntityId, type RunState, type Tile, type Actor, type Vec2 } from "../../core/types";
import { TileType } from "../../core/terrain";
import { idx } from "./world-logic";
import { GAME_CONFIG } from "../../core/config/GameConfig";
import { seededRandom } from "../../core/math";
@@ -20,7 +21,7 @@ interface Room {
export function generateWorld(floor: number, runState: RunState): { world: World; playerId: EntityId } {
const width = GAME_CONFIG.map.width;
const height = GAME_CONFIG.map.height;
const tiles: Tile[] = new Array(width * height).fill(GAME_CONFIG.terrain.wall);
const tiles: Tile[] = new Array(width * height).fill(TileType.WALL);
const random = seededRandom(floor * 12345);
@@ -103,7 +104,7 @@ function generateRooms(width: number, height: number, tiles: Tile[], floor: numb
dungeon.create((x: number, y: number, value: number) => {
if (value === 0) {
// 0 = floor, 1 = wall
tiles[y * width + x] = GAME_CONFIG.terrain.empty;
tiles[y * width + x] = TileType.EMPTY;
}
});
@@ -148,7 +149,7 @@ function extractRoomsFromCave(width: number, height: number, tiles: Tile[]): Roo
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const idx = y * width + x;
if (tiles[idx] === GAME_CONFIG.terrain.empty && !visited.has(idx)) {
if (tiles[idx] === TileType.EMPTY && !visited.has(idx)) {
const cluster = floodFill(width, height, tiles, x, y, visited);
// Only consider clusters larger than 20 tiles
@@ -206,7 +207,7 @@ function floodFill(width: number, height: number, tiles: Tile[], startX: number,
for (const { nx, ny } of neighbors) {
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
const nIdx = ny * width + nx;
if (tiles[nIdx] === GAME_CONFIG.terrain.empty && !visited.has(nIdx)) {
if (tiles[nIdx] === TileType.EMPTY && !visited.has(nIdx)) {
queue.push(nIdx);
}
}
@@ -220,53 +221,53 @@ function decorate(width: number, height: number, tiles: Tile[], random: () => nu
const world = { width, height };
// Set exit tile
tiles[idx(world as any, exit.x, exit.y)] = GAME_CONFIG.terrain.exit;
tiles[idx(world as any, exit.x, exit.y)] = TileType.EXIT;
// Use Simplex noise for natural-looking water distribution
const waterNoise = new ROT.Noise.Simplex();
// Use Simplex noise for natural-looking grass distribution
const grassNoise = new ROT.Noise.Simplex();
const decorationNoise = new ROT.Noise.Simplex();
// Offset noise to get different patterns for water vs decorations
const waterOffset = random() * 1000;
// Offset noise to get different patterns for grass vs decorations
const grassOffset = random() * 1000;
const decorOffset = random() * 1000;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = idx(world as any, x, y);
if (tiles[i] === GAME_CONFIG.terrain.empty) {
// Water lakes: use noise to create organic shapes
const waterValue = waterNoise.get((x + waterOffset) / 15, (y + waterOffset) / 15);
if (tiles[i] === TileType.EMPTY) {
// Grass patches: use noise to create organic shapes
const grassValue = grassNoise.get((x + grassOffset) / 15, (y + grassOffset) / 15);
// Create water patches where noise is above threshold
if (waterValue > 0.35) {
tiles[i] = GAME_CONFIG.terrain.water;
// Create grass patches where noise is above threshold
if (grassValue > 0.35) {
tiles[i] = TileType.GRASS;
} else {
// Floor decorations (moss/grass): clustered distribution
// Floor decorations (moss/rocks): clustered distribution
const decoValue = decorationNoise.get((x + decorOffset) / 8, (y + decorOffset) / 8);
// Dense clusters where noise is high
if (decoValue > 0.5) {
tiles[i] = GAME_CONFIG.terrain.emptyDeco;
tiles[i] = TileType.EMPTY_DECO;
} else if (decoValue > 0.3 && random() < 0.3) {
// Sparse decorations at medium noise levels
tiles[i] = GAME_CONFIG.terrain.emptyDeco;
tiles[i] = TileType.EMPTY_DECO;
}
}
}
}
}
// Wall decorations (algae near water)
// Wall decorations (moss near grass)
for (let y = 0; y < height - 1; y++) {
for (let x = 0; x < width; x++) {
const i = idx(world as any, x, y);
const nextY = idx(world as any, x, y + 1);
if (tiles[i] === GAME_CONFIG.terrain.wall &&
tiles[nextY] === GAME_CONFIG.terrain.water &&
if (tiles[i] === TileType.WALL &&
tiles[nextY] === TileType.GRASS &&
random() < 0.25) {
tiles[i] = GAME_CONFIG.terrain.wallDeco;
tiles[i] = TileType.WALL_DECO;
}
}
}

View File

@@ -1,8 +1,8 @@
import type { World, EntityId } from "../../core/types";
import { GAME_CONFIG } from "../../core/config/GameConfig";
import { isBlocking, isDestructible, getDestructionResult } from "../../core/terrain";
import { type EntityManager } from "../EntityManager";
export function inBounds(w: World, x: number, y: number): boolean {
return x >= 0 && y >= 0 && x < w.width && y < w.height;
}
@@ -12,13 +12,34 @@ export function idx(w: World, x: number, y: number): number {
}
export function isWall(w: World, x: number, y: number): boolean {
// Alias for isBlocking for backward compatibility
return isBlockingTile(w, x, y);
}
export function isBlockingTile(w: World, x: number, y: number): boolean {
const tile = w.tiles[idx(w, x, y)];
return tile === GAME_CONFIG.terrain.wall || tile === GAME_CONFIG.terrain.wallDeco;
return isBlocking(tile);
}
export function tryDestructTile(w: World, x: number, y: number): boolean {
if (!inBounds(w, x, y)) return false;
const i = idx(w, x, y);
const tile = w.tiles[i];
if (isDestructible(tile)) {
const nextTile = getDestructionResult(tile);
if (nextTile !== undefined) {
w.tiles[i] = nextTile;
return true;
}
}
return false;
}
export function isBlocked(w: World, x: number, y: number, em?: EntityManager): boolean {
if (!inBounds(w, x, y)) return true;
if (isWall(w, x, y)) return true;
if (isBlockingTile(w, x, y)) return true;
if (em) {
return em.isOccupied(x, y, "exp_orb");