shellquest
Version:
Terminal-based procedurally generated dungeon crawler
318 lines (275 loc) • 14.9 kB
text/typescript
/**
* Dungeon Tile Definitions
* Maps tile names to coordinates in dungeon1.png
*/
import type {TileMap} from '../../TileMap.ts';
interface DungeonTile {
name: string;
x: number;
y: number;
layer: 'bottom' | 'sprite' | 'top';
solid: boolean;
}
export function setupDungeonTileDefinitions(tileMap: TileMap): void {
// Load dungeon tileset as default
tileMap.loadFromFile('dungeon1.png', true);
// Also load the main tileset for entity sprites (player, zombie, weapons)
tileMap.loadFromFile('tilemap_packed.png', false);
const tiles: DungeonTile[] = [
// ==========================================================================
// CEILING TILES (background for wall areas)
// ==========================================================================
{name: 'dungeon-ceiling-base', x: 0, y: 0, layer: 'bottom', solid: true},
{name: 'dungeon-ceiling-variant1', x: 0, y: 1, layer: 'bottom', solid: true},
{name: 'dungeon-ceiling-variant2', x: 0, y: 2, layer: 'bottom', solid: true},
// ==========================================================================
// GROUND/FLOOR TILES
// ==========================================================================
{name: 'dungeon-ground-base', x: 0, y: 4, layer: 'bottom', solid: false},
{name: 'dungeon-ground-textured', x: 1, y: 4, layer: 'bottom', solid: false},
{name: 'dungeon-ground-stone', x: 6, y: 3, layer: 'bottom', solid: false},
// ==========================================================================
// SHADOW TILES (cast by walls onto floor)
// ==========================================================================
{name: 'dungeon-shadow-from-north', x: 2, y: 4, layer: 'sprite', solid: false},
{name: 'dungeon-shadow-from-north-alt', x: 3, y: 4, layer: 'sprite', solid: false},
{name: 'dungeon-shadow-inner-corner', x: 4, y: 4, layer: 'sprite', solid: false},
{name: 'dungeon-shadow-outer-corner', x: 5, y: 4, layer: 'sprite', solid: false},
// ==========================================================================
// WALL TILES - Horizontal (no wall below)
// ==========================================================================
{name: 'dungeon-wall-horizontal-middle', x: 4, y: 3, layer: 'sprite', solid: true},
{name: 'dungeon-wall-horizontal-left-cap', x: 9, y: 4, layer: 'sprite', solid: true},
{name: 'dungeon-wall-horizontal-right-cap', x: 11, y: 4, layer: 'sprite', solid: true},
// ==========================================================================
// WALL TILES - Vertical (wall below, extends downward)
// ==========================================================================
{name: 'dungeon-wall-vertical-left', x: 1, y: 1, layer: 'sprite', solid: true},
{name: 'dungeon-wall-vertical-right', x: 3, y: 1, layer: 'sprite', solid: true},
// ==========================================================================
// WALL CROWN TILES (decorative top, drawn 1 tile above wall face)
// ==========================================================================
{name: 'dungeon-wall-crown-middle', x: 2, y: 0, layer: 'top', solid: false},
{name: 'dungeon-wall-crown-left-cap', x: 4, y: 1, layer: 'top', solid: false},
{name: 'dungeon-wall-crown-right-cap', x: 5, y: 1, layer: 'top', solid: false},
{name: 'dungeon-wall-crown-vertical-left', x: 1, y: 1, layer: 'top', solid: false},
{name: 'dungeon-wall-crown-vertical-right', x: 3, y: 1, layer: 'top', solid: false},
// ==========================================================================
// WALL TOP TILES (top edge of wall mass, when no wall above)
// ==========================================================================
{name: 'dungeon-wall-top-left', x: 4, y: 0, layer: 'top', solid: false},
{name: 'dungeon-wall-top-middle', x: 2, y: 2, layer: 'top', solid: false},
{name: 'dungeon-wall-top-right', x: 5, y: 0, layer: 'top', solid: false},
// ==========================================================================
// WALL CORNER TILES (inside corners - L-shaped void cutting into wall mass)
// ==========================================================================
{name: 'dungeon-wall-corner-inside-sw', x: 3, y: 0, layer: 'sprite', solid: true},
{name: 'dungeon-wall-corner-inside-se', x: 1, y: 0, layer: 'sprite', solid: true},
{name: 'dungeon-wall-corner-inside-nw', x: 3, y: 2, layer: 'sprite', solid: true},
{name: 'dungeon-wall-corner-inside-ne', x: 1, y: 2, layer: 'sprite', solid: true},
// ==========================================================================
// DECAL TILES - Ground
// ==========================================================================
{name: 'dungeon-decal-table', x: 0, y: 6, layer: 'sprite', solid: false},
{name: 'dungeon-decal-ground-knub', x: 1, y: 6, layer: 'sprite', solid: false},
{name: 'dungeon-decal-ground-anvil', x: 2, y: 6, layer: 'sprite', solid: false},
{name: 'dungeon-decal-ground-column-bottom', x: 3, y: 6, layer: 'sprite', solid: false},
{name: 'dungeon-decal-ground-column-top', x: 3, y: 5, layer: 'sprite', solid: false},
{name: 'dungeon-decal-chest-open', x: 8, y: 7, layer: 'sprite', solid: false},
// ==========================================================================
// DECAL TILES - Wall
// ==========================================================================
{name: 'dungeon-decal-wall-crack1', x: 4, y: 2, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-crack2', x: 5, y: 2, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-face1-bottom', x: 7, y: 2, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-face1-top', x: 7, y: 1, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-face2-bottom', x: 8, y: 2, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-face2-top', x: 8, y: 1, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-moss', x: 8, y: 1, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-detail1', x: 9, y: 0, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-detail2', x: 9, y: 1, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-detail3', x: 9, y: 2, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-detail4', x: 9, y: 3, layer: 'sprite', solid: false},
// Wide wall decals (2 tiles wide)
{name: 'dungeon-decal-wall-wide1-left', x: 10, y: 0, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-wide1-right', x: 11, y: 0, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-wide2-left', x: 10, y: 1, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-wide2-right', x: 11, y: 1, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-wide3-left', x: 10, y: 2, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-wide3-right', x: 11, y: 2, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-wide4-left', x: 10, y: 3, layer: 'sprite', solid: false},
{name: 'dungeon-decal-wall-wide4-right', x: 11, y: 3, layer: 'sprite', solid: false},
{name: 'dungeon-lantern', x: 4, y: 10, layer: 'sprite', solid: false},
// Pillar tiles (3 parts: base, middle, crown)
{name: 'dungeon-decal-pillar-base', x: 6, y: 2, layer: 'sprite', solid: false},
{name: 'dungeon-decal-pillar-middle', x: 6, y: 1, layer: 'sprite', solid: false},
{name: 'dungeon-decal-pillar-crown', x: 6, y: 0, layer: 'top', solid: false},
// ==========================================================================
// TRACK TILES (mine cart tracks)
// ==========================================================================
{name: 'dungeon-track-corner-up-right', x: 9, y: 5, layer: 'sprite', solid: false},
{name: 'dungeon-track-corner-left-down', x: 11, y: 5, layer: 'sprite', solid: false},
{name: 'dungeon-track-corner-up-left', x: 11, y: 7, layer: 'sprite', solid: false},
{name: 'dungeon-track-corner-right-down', x: 9, y: 7, layer: 'sprite', solid: false},
{name: 'dungeon-track-straight-vertical', x: 11, y: 6, layer: 'sprite', solid: false},
{name: 'dungeon-track-straight-horizontal', x: 10, y: 5, layer: 'sprite', solid: false},
{name: 'dungeon-track-end-left', x: 7, y: 6, layer: 'sprite', solid: false},
{name: 'dungeon-track-end-right', x: 8, y: 6, layer: 'sprite', solid: false},
{name: 'dungeon-track-end-top', x: 7, y: 5, layer: 'sprite', solid: false},
{name: 'dungeon-track-end-bottom', x: 8, y: 5, layer: 'sprite', solid: false},
{name: 'dungeon-track-barrel', x: 10, y: 6, layer: 'sprite', solid: false},
// ==========================================================================
// FENCE TILES (horizontal fences)
// ==========================================================================
{name: 'dungeon-fence-left', x: 4, y: 6, layer: 'sprite', solid: true},
{name: 'dungeon-fence-middle', x: 5, y: 6, layer: 'sprite', solid: true},
{name: 'dungeon-fence-right', x: 6, y: 6, layer: 'sprite', solid: true},
];
for (const tile of tiles) {
tileMap.defineTile(tile.name, tile.x, tile.y, {
layer: tile.layer,
solid: tile.solid,
});
}
// ==========================================================================
// ENTITY TILES (from tilemap_packed.png)
// These are shared between dungeon and overworld levels
// ==========================================================================
const entityTiles = [
{name: 'player', x: 0, y: 18},
{name: 'zombie', x: 1, y: 18},
{name: 'wand', x: 10, y: 21},
{name: 'sword', x: 8, y: 19},
];
for (const tile of entityTiles) {
tileMap.defineTile(tile.name, tile.x, tile.y, {
layer: 'sprite',
solid: true,
tilemap: 'tilemap_packed', // Load from character tileset
});
}
}
// =============================================================================
// AUTOTILE RESULT TYPE
// =============================================================================
export interface AutotileResult {
face: string | null; // Tile name for face
crown: string | null; // Tile name for crown (rendered above)
}
// =============================================================================
// AUTOTILE LOGIC - Determines which wall tiles to use based on neighbors
// =============================================================================
import type {Neighbors} from './DungeonUtils.ts';
export function computeAutotile(neighbors: Neighbors): AutotileResult {
const {n, s, e, w, ne, nw, se, sw} = neighbors;
let face: string | null = null;
let crown: string | null = null;
// Priority 1: Inside corners (L-shaped floor cutting into wall mass)
// Inside corner SW: walls to W and S, floor at SW
if (w && s && !sw) {
crown = 'dungeon-wall-corner-inside-sw';
face = 'dungeon-wall-corner-inside-sw';
return {face, crown};
}
// Inside corner SE: walls to E and S, floor at SE
if (e && s && !se) {
crown = 'dungeon-wall-corner-inside-se';
return {face, crown};
}
// Inside corner NW: walls to W and N, floor at NW
if (w && n && !nw) {
crown = 'dungeon-wall-corner-inside-nw';
return {face, crown};
}
// Inside corner NE: walls to E and N, floor at NE
if (e && n && !ne) {
crown = 'dungeon-wall-corner-inside-ne';
return {face, crown};
}
// Priority 2: Horizontal walls (no wall below)
if (!s) {
if (!w && e) {
// Left end of horizontal wall
face = 'dungeon-wall-horizontal-left-cap';
crown = 'dungeon-wall-crown-left-cap';
} else if (w && !e) {
// Right end of horizontal wall
face = 'dungeon-wall-horizontal-right-cap';
crown = 'dungeon-wall-crown-right-cap';
} else {
// Middle of horizontal wall
face = 'dungeon-wall-horizontal-middle';
crown = 'dungeon-wall-crown-middle';
}
}
// Priority 3: Vertical walls (wall below)
else {
if (!e) {
// Left edge of vertical wall mass
face = 'dungeon-wall-vertical-left';
crown = 'dungeon-wall-crown-vertical-left';
} else if (!w) {
// Right edge of vertical wall mass
face = 'dungeon-wall-vertical-right';
crown = 'dungeon-wall-crown-vertical-right';
}
}
// Priority 4: Top edge override (no wall above, wall below)
if (!n && s) {
if (!w && e) {
crown = 'dungeon-wall-top-left';
} else if (w && !e) {
crown = 'dungeon-wall-top-right';
} else {
crown = 'dungeon-wall-top-middle';
}
// Top edge walls don't show face tile
face = null;
}
return {face, crown};
}
// =============================================================================
// TILE NAME GETTERS FOR PROCEDURAL GENERATION
// =============================================================================
export function getCeilingTile(noise: number): string {
if (noise < 30) {
return 'dungeon-ceiling-variant1';
} else if (noise < 40) {
return 'dungeon-ceiling-variant2';
}
return 'dungeon-ceiling-base';
}
export function getGroundTile(noise: number): string {
if (noise < 30) {
return 'dungeon-ground-textured';
} else if (noise < 50) {
return 'dungeon-ground-stone';
}
return 'dungeon-ground-base';
}
export function getShadowTile(noise: number): string {
return noise < 70 ? 'dungeon-shadow-from-north' : 'dungeon-shadow-from-north-alt';
}
// =============================================================================
// TRACK TILE HELPERS
// =============================================================================
export const TRACK_TILE_NAMES = {
cornerUpRight: 'dungeon-track-corner-up-right',
cornerLeftDown: 'dungeon-track-corner-left-down',
cornerUpLeft: 'dungeon-track-corner-up-left',
cornerRightDown: 'dungeon-track-corner-right-down',
straightVertical: 'dungeon-track-straight-vertical',
straightHorizontal: 'dungeon-track-straight-horizontal',
endLeft: 'dungeon-track-end-left',
endRight: 'dungeon-track-end-right',
endTop: 'dungeon-track-end-top',
endBottom: 'dungeon-track-end-bottom',
};
// =============================================================================
// FENCE TILE HELPERS
// =============================================================================
export const FENCE_TILE_NAMES = {
left: 'dungeon-fence-left',
middle: 'dungeon-fence-middle',
right: 'dungeon-fence-right',
};