diff --git a/html/js/render/assetPacks.js b/html/js/render/assetPacks.js index e1443d3..8d0b345 100644 --- a/html/js/render/assetPacks.js +++ b/html/js/render/assetPacks.js @@ -141,17 +141,25 @@ // keep one coherent tile per kind. (TileRenderer still accepts an array here if a // future theme wants subtle, same-material variation.) tiles: { - floor: [5, 2], // tile 119 — wood floor (uniform) - floor2: [6, 2], // tile 120 — stone floor (accent) - wall: [13, 12], // tile 697 — stone masonry - - bar: [28, 0], // tile 28 — counter - table: [20, 3], // tile 191 — table + floor: [5, 2], // 119 — wood floor + floor2: [6, 2], // 120 — stone floor (accent) + wall: [13, 12], // 697 — stone masonry + // Tavern furnishings (coords from roguelikeSheet_meta.json labels: index -> [i%57, i/57]). + window: [42, 2], // 156 — window (in the wall) + bar: [29, 0], // 29 — bar counter + keg: [23, 0], // 23 — barrel / beer keg (behind the bar) + shelf: [30, 1], // 87 — shelf of bottles (behind the bar) + table: [27, 5], // 312 — table + chair: [19, 3], // 190 — chair + barrel: [23, 0], // 23 — barrel (decor) + crate: [25, 0], // 25 — crate (decor) + rug: [11, 16], // 923 — rug + door: [37, 9], // 550 — door // Dungeon features drawn as real tiles instead of monospace glyphs. - entrance: [37, 9], // tile 550 — door - exit: [37, 9], // tile 550 — door - treasure: [42, 15], // tile 897 — item - torch: [50, 10] // tile 620 — rubble / prop + entrance: [37, 9], // 550 — door + exit: [37, 9], // 550 — door + treasure: [42, 15], // 897 — item + torch: [50, 10] // 620 — rubble / prop } } }; diff --git a/html/js/render/sceneModel.js b/html/js/render/sceneModel.js index 78b2205..af0eee6 100644 --- a/html/js/render/sceneModel.js +++ b/html/js/render/sceneModel.js @@ -8,14 +8,26 @@ // Tile kinds and how each renderer treats them (glyph for ASCII, colour for tiled/fancy). var TAVERN_LEGEND = { - wall: { char: '#', color: '#20262e', solid: true }, - floor: { char: '·', color: '#0c1410', solid: false }, - bar: { char: '=', color: '#5a4632', solid: true }, - table: { char: 'o', color: '#4a3a28', solid: true } + wall: { char: '#', color: '#3a3f4b', solid: true }, + window: { char: 'W', color: '#4a6a8a', solid: true }, + floor: { char: '·', color: '#6b4d33', solid: false }, // warm wood, not the old dark green + rug: { char: '=', color: '#7a3b39', solid: false }, + bar: { char: '=', color: '#6a4a2a', solid: true }, + keg: { char: 'k', color: '#5a4028', solid: true }, + shelf: { char: 'h', color: '#4a3a28', solid: true }, + table: { char: 'T', color: '#6a4a2a', solid: true }, + chair: { char: 'c', color: '#5a4028', solid: false }, // walkable — stand on it to "sit" + barrel: { char: 'B', color: '#5a4028', solid: true }, + crate: { char: 'C', color: '#7a5a38', solid: true }, + door: { char: 'D', color: '#3fb950', solid: false } }; // Layout character -> tile kind. - var TAVERN_TILE_OF = { '#': 'wall', '.': 'floor', '@': 'floor', '=': 'bar', 'o': 'table' }; + var TAVERN_TILE_OF = { + '#': 'wall', 'W': 'window', '.': 'floor', '@': 'floor', 'r': 'rug', + '=': 'bar', 'k': 'keg', 'h': 'shelf', 'T': 'table', 'c': 'chair', + 'B': 'barrel', 'C': 'crate', 'D': 'door' + }; var AVATAR_COLORS = { 'default': '#9aa4b2', green: '#3fb950', amber: '#d29922', red: '#f85149' }; diff --git a/html/js/render/tavernLife.js b/html/js/render/tavernLife.js index dad2ea4..7b09adb 100644 --- a/html/js/render/tavernLife.js +++ b/html/js/render/tavernLife.js @@ -69,8 +69,9 @@ } function bartendersArea(cols, rows) { - // Bartender roams along the top interior (bar counter row ~ y=2). - return { minY: 1, maxY: 3, minX: 2, maxX: Math.max(5, cols - 3) }; + // Behind the bar: the row between the top wall and the counter (y=1). Sealed off by the + // counter, so the bartender stays back there and never wanders onto the customer floor. + return { minY: 1, maxY: 1, minX: 3, maxX: Math.max(5, cols - 5) }; } function patronsArea(cols, rows) { // Patrons roam the middle/lower floor. @@ -195,8 +196,8 @@ return { x: 1, y: 1 }; } - // Bartender near the bar (top area). - var barSpot = findFloor(Math.floor(cols / 2), 3); + // Bartender BEHIND the bar (row 1, between the top wall and the counter). + var barSpot = findFloor(Math.floor(cols / 2), 1); var bartender = makeNPC('bartender', 'bartender', 'char-merchant', barSpot.x, barSpot.y, { label: 'Bartender', color: '#c9a25e', role: 'bartender', roamArea: bartendersArea(cols, rows) diff --git a/src/multiplayer/tavernMap.js b/src/multiplayer/tavernMap.js index be6a0c1..a56172b 100644 --- a/src/multiplayer/tavernMap.js +++ b/src/multiplayer/tavernMap.js @@ -1,26 +1,22 @@ /** - * Default Tavern layout — a small room with hand-placed props (bar, tables), generated - * procedurally so every row is guaranteed the same width. + * Default Tavern layout — a hand-built room rendered through the theme atlas. Every row is the same + * width (ragged ASCII maps are a classic off-by-one collision bug), and the legend below drives both + * the client tiles (sceneModel TAVERN_LEGEND/TILE_OF) and Room walkability. * * Legend: - * '#' wall '=' bar counter 'o' table / stool (all solid) - * '.' floor '@' spawn point (walkable) + * '#' wall 'W' window '=' bar counter 'k' keg (barrel) 'h' shelf of bottles + * 'T' table 'c' chair 'B' barrel 'C' crate 'D' door + * '.' floor 'r' rug '@' spawn * - * Anything NOT in FLOOR_CHARS is treated as solid by the Room engine. The layout is - * generated procedurally so every row is guaranteed the same width (ragged ASCII maps - * are a classic source of off-by-one collision bugs). + * Walkable = FLOOR_CHARS ('.', '@', 'r' rug, 'c' chair, 'D' door). Everything else is solid, so + * players weave between the furniture. The bartender stands BEHIND the bar (the row between the top + * wall and the counter). */ -// Characters an Occupant may stand on. -const FLOOR_CHARS = new Set(['.', '@']); +// Characters an Occupant may stand on. Chairs are walkable so standing on one reads as "sitting". +const FLOOR_CHARS = new Set(['.', '@', 'r', 'c', 'D']); -/** - * Build the default tavern as an array of equal-length strings. - * @param {number} cols - * @param {number} rows - * @returns {string[]} - */ -function buildDefaultTavern(cols = 26, rows = 13) { +function buildDefaultTavern(cols = 28, rows = 15) { const grid = []; for (let y = 0; y < rows; y++) { const row = new Array(cols); @@ -30,25 +26,36 @@ function buildDefaultTavern(cols = 26, rows = 13) { } grid.push(row); } + // set: overwrite anything (incl. walls, for windows/door). put: don't clobber walls. + const set = (x, y, ch) => { if (grid[y] && x >= 0 && x < cols) grid[y][x] = ch; }; + const put = (x, y, ch) => { if (grid[y] && grid[y][x] !== undefined && grid[y][x] !== '#') grid[y][x] = ch; }; - const put = (x, y, ch) => { - if (grid[y] && grid[y][x] !== undefined && grid[y][x] !== '#') grid[y][x] = ch; - }; + // Windows punched into the top wall and the two side walls. + for (let x = 3; x < cols - 3; x += 4) set(x, 0, 'W'); + for (let y = 4; y < rows - 3; y += 4) { set(0, y, 'W'); set(cols - 1, y, 'W'); } - // Bar counter along the top interior wall. - for (let x = 3; x <= 16; x++) put(x, 2, '='); + // The bar: a counter across the top interior (row 2), a shelf of bottles behind it (row 1), + // and a keg (barrel) bracketing each end. Row 1 is the bartender's space, sealed off by the + // counter — players can't get behind it. + const barY = 2, barX0 = 3, barX1 = cols - 5; + for (let x = barX0; x <= barX1; x++) set(x, barY, '='); + for (let x = barX0; x <= barX1; x += 2) set(x, barY - 1, 'h'); // bottles behind the bar + set(barX0 - 1, barY, 'k'); set(barX1 + 1, barY, 'k'); // kegs bracket the counter - // Table + stool clusters (2x2 blocks of 'o'). - const tables = [[5, 5], [12, 5], [18, 5], [5, 9], [12, 9], [18, 9]]; - for (const [tx, ty] of tables) { - put(tx, ty, 'o'); put(tx + 1, ty, 'o'); - put(tx, ty + 1, 'o'); put(tx + 1, ty + 1, 'o'); - } + // Tables (each with a chair below it), spread across the main floor with walkable aisles. + const tables = [[5, 6], [12, 6], [19, 6], [5, 10], [12, 10], [19, 10]]; + for (const [tx, ty] of tables) { put(tx, ty, 'T'); put(tx, ty + 1, 'c'); } - // Spawn pad near the entrance (bottom-center). Two tiles so early tests have a - // deterministic adjacent pair to exercise occupant-collision rules. - put(12, 11, '@'); - put(13, 11, '@'); + // A rug in the centre of the room. + for (let y = 8; y <= 9; y++) for (let x = 11; x <= 15; x++) put(x, y, 'r'); + + // Corner decor — barrels and crates. + put(1, 1, 'B'); put(cols - 2, 1, 'C'); put(1, rows - 2, 'C'); put(cols - 2, rows - 2, 'B'); + + // Door + spawn pad at the bottom-centre (two spawn tiles for deterministic adjacency tests). + const mid = Math.floor(cols / 2); + set(mid, rows - 1, 'D'); + put(mid, rows - 2, '@'); put(mid - 1, rows - 2, '@'); return grid.map(r => r.join('')); } diff --git a/test/renderScene.test.js b/test/renderScene.test.js index 586d447..f8083b7 100644 --- a/test/renderScene.test.js +++ b/test/renderScene.test.js @@ -9,7 +9,7 @@ describe('sceneFromTavern (render kit adapter)', () => { }; const state = { cols: 3, rows: 2, - layout: ['#=@', 'o..'], + layout: ['#=@', 'T..'], occupants: [{ id: 'a', x: 2, y: 0, avatar: 'green', appearance, facing: 'left', name: 'Al' }] }; const s = scene.sceneFromTavern(state, 'a');