feat: Add traps
This commit is contained in:
@@ -11,6 +11,9 @@ import {
|
||||
} from "../../core/config/Items";
|
||||
import { seededRandom } from "../../core/math";
|
||||
import * as ROT from "rot-js";
|
||||
import { ECSWorld } from "../ecs/World";
|
||||
import { Prefabs } from "../ecs/Prefabs";
|
||||
|
||||
|
||||
interface Room {
|
||||
x: number;
|
||||
@@ -23,9 +26,9 @@ interface Room {
|
||||
* Generates a procedural dungeon world with rooms and corridors using rot-js Uniform algorithm
|
||||
* @param floor The floor number (affects difficulty)
|
||||
* @param runState Player's persistent state across floors
|
||||
* @returns Generated world and player ID
|
||||
* @returns Generated world, player ID, and ECS world with traps
|
||||
*/
|
||||
export function generateWorld(floor: number, runState: RunState): { world: World; playerId: EntityId } {
|
||||
export function generateWorld(floor: number, runState: RunState): { world: World; playerId: EntityId; ecsWorld: ECSWorld } {
|
||||
const width = GAME_CONFIG.map.width;
|
||||
const height = GAME_CONFIG.map.height;
|
||||
const tiles: Tile[] = new Array(width * height).fill(TileType.WALL);
|
||||
@@ -80,6 +83,13 @@ export function generateWorld(floor: number, runState: RunState): { world: World
|
||||
|
||||
placeEnemies(floor, rooms, actors, random);
|
||||
|
||||
// Create ECS world and place traps
|
||||
const ecsWorld = new ECSWorld();
|
||||
const occupiedPositions = new Set<string>();
|
||||
occupiedPositions.add(`${playerX},${playerY}`); // Don't place traps on player start
|
||||
occupiedPositions.add(`${exit.x},${exit.y}`); // Don't place traps on exit
|
||||
placeTraps(floor, rooms, ecsWorld, tiles, width, random, occupiedPositions);
|
||||
|
||||
// Place doors for dungeon levels (Uniform/Digger)
|
||||
// Caves (Floors 10+) shouldn't have manufactured doors
|
||||
if (floor <= 9) {
|
||||
@@ -94,7 +104,8 @@ export function generateWorld(floor: number, runState: RunState): { world: World
|
||||
|
||||
return {
|
||||
world: { width, height, tiles, actors, exit },
|
||||
playerId
|
||||
playerId,
|
||||
ecsWorld
|
||||
};
|
||||
}
|
||||
|
||||
@@ -426,6 +437,70 @@ function placeEnemies(floor: number, rooms: Room[], actors: Map<EntityId, Actor>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Place traps randomly in dungeon rooms.
|
||||
* Trap density increases with floor depth.
|
||||
*/
|
||||
function placeTraps(
|
||||
floor: number,
|
||||
rooms: Room[],
|
||||
ecsWorld: ECSWorld,
|
||||
tiles: Tile[],
|
||||
width: number,
|
||||
random: () => number,
|
||||
occupiedPositions: Set<string>
|
||||
): void {
|
||||
// Trap configuration
|
||||
const trapTypes = ["poison", "fire", "paralysis"] as const;
|
||||
|
||||
// Number of traps scales with floor (1-2 on floor 1, up to 5-6 on floor 10)
|
||||
const minTraps = 1 + Math.floor(floor / 3);
|
||||
const maxTraps = minTraps + 2;
|
||||
const numTraps = minTraps + Math.floor(random() * (maxTraps - minTraps + 1));
|
||||
|
||||
for (let i = 0; i < numTraps; i++) {
|
||||
// Pick a random room (not the starting room)
|
||||
const roomIdx = 1 + Math.floor(random() * (rooms.length - 1));
|
||||
const room = rooms[roomIdx];
|
||||
|
||||
// Try to find a valid position
|
||||
for (let attempts = 0; attempts < 10; attempts++) {
|
||||
const tx = room.x + 1 + Math.floor(random() * (room.width - 2));
|
||||
const ty = room.y + 1 + Math.floor(random() * (room.height - 2));
|
||||
const key = `${tx},${ty}`;
|
||||
|
||||
// Check if position is valid (floor tile, not occupied)
|
||||
const tileIdx = ty * width + tx;
|
||||
const isFloor = tiles[tileIdx] === TileType.EMPTY ||
|
||||
tiles[tileIdx] === TileType.EMPTY_DECO ||
|
||||
tiles[tileIdx] === TileType.GRASS_SAPLINGS;
|
||||
|
||||
if (isFloor && !occupiedPositions.has(key)) {
|
||||
// Pick a random trap type
|
||||
const trapType = trapTypes[Math.floor(random() * trapTypes.length)];
|
||||
|
||||
// Scale effect duration/magnitude with floor
|
||||
const duration = 3 + Math.floor(floor / 3);
|
||||
const magnitude = 2 + Math.floor(floor / 2);
|
||||
|
||||
switch (trapType) {
|
||||
case "poison":
|
||||
Prefabs.poisonTrap(ecsWorld, tx, ty, duration, magnitude);
|
||||
break;
|
||||
case "fire":
|
||||
Prefabs.fireTrap(ecsWorld, tx, ty, Math.ceil(duration / 2), magnitude + 2);
|
||||
break;
|
||||
case "paralysis":
|
||||
Prefabs.paralysisTrap(ecsWorld, tx, ty, Math.max(2, Math.ceil(duration / 2)));
|
||||
break;
|
||||
}
|
||||
|
||||
occupiedPositions.add(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const makeTestWorld = generateWorld;
|
||||
|
||||
Reference in New Issue
Block a user