Newer
Older
rpg / apps / game / src / data / map.ts
import type { TileMapData, TallSpec } from '@rpg/engine';

/** id тайлов луга «Пепельные луга». */
export const TILES = {
    GRASS: 0,
    PATH: 1,
    WATER: 2,
    TREE: 3,
    ASH: 4,
    BELLFLOWER: 5
} as const;

const BLOCKED = [TILES.WATER, TILES.TREE];

/** Дерево — высокий объект 48px, под ним рисуется трава. */
const TALL_OBJECTS: Record<number, TallSpec> = {
    [TILES.TREE]: { height: 48, ground: TILES.GRASS }
};

/** Детерминированный ГПСЧ, чтобы локация выглядела одинаково между запусками. */
function mulberry32(seed: number): () => number {
    return () => {
        seed |= 0;
        seed = (seed + 0x6d2b79f5) | 0;
        let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
        t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
        return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
    };
}

/**
 * Локация «Выжженные луга»: поляны травы, тропа через карту,
 * пара прудов и рощи деревьев по краям.
 */
export function buildMeadowsMap(width = 28, height = 28): TileMapData {
    const rng = mulberry32(20260905);
    const tiles: number[] = new Array(width * height).fill(TILES.GRASS);
    const at = (x: number, y: number) => y * width + x;

    // Пятна пепла (декоративные).
    for (let i = 0; i < 40; i++) {
        const x = Math.floor(rng() * width);
        const y = Math.floor(rng() * height);
        const r = 1 + Math.floor(rng() * 2);
        for (let dy = -r; dy <= r; dy++) {
            for (let dx = -r; dx <= r; dx++) {
                const nx = x + dx;
                const ny = y + dy;
                if (nx >= 0 && ny >= 0 && nx < width && ny < height && rng() < 0.7) {
                    tiles[at(nx, ny)] = TILES.ASH;
                }
            }
        }
    }

    // Тропа: ломаная слева-сверху вниз-вправо.
    let px = 2;
    let py = 2;
    while (px < width - 3 || py < height - 3) {
        tiles[at(px, py)] = TILES.PATH;
        if (rng() < 0.5 && px < width - 3) px++;
        else if (py < height - 3) py++;
        else px++;
    }

    // Пруды.
    for (const [cx, cy] of [
        [6, 16],
        [19, 7]
    ]) {
        for (let dy = 0; dy < 3; dy++) {
            for (let dx = 0; dx < 4; dx++) {
                if (rng() < 0.85) tiles[at(cx + dx, cy + dy)] = TILES.WATER;
            }
        }
    }

    // Лунные колокольчики у берегов прудов (квестовый декор, проходимы).
    for (const [fx, fy] of [
        [10, 17],
        [11, 19],
        [18, 5],
        [22, 9]
    ]) {
        if (tiles[at(fx, fy)] === TILES.GRASS || tiles[at(fx, fy)] === TILES.ASH) {
            tiles[at(fx, fy)] = TILES.BELLFLOWER;
        }
    }

    // Рощи деревьев по краям.
    for (let i = 0; i < 60; i++) {
        const edge = rng() < 0.5;
        const x = edge ? Math.floor(rng() * 4) : width - 1 - Math.floor(rng() * 4);
        const y = Math.floor(rng() * height);
        if (tiles[at(x, y)] === TILES.GRASS || tiles[at(x, y)] === TILES.ASH) {
            tiles[at(x, y)] = TILES.TREE;
        }
    }

    // Стартовая поляна вокруг центра всегда свободна.
    const cx = Math.floor(width / 2);
    const cy = Math.floor(height / 2);
    for (let dy = -1; dy <= 1; dy++) {
        for (let dx = -1; dx <= 1; dx++) {
            tiles[at(cx + dx, cy + dy)] = TILES.GRASS;
        }
    }

    return {
        width,
        height,
        tiles,
        blocked: [...BLOCKED],
        tall: TALL_OBJECTS
    };
}