feat(frontend): tile-based office interior with LimeZu Modern Interiors tileset
- Replace outdoor farm scene with 20x15 tile office floor plan - Add officeMap.js with tile definitions, map grid, walls, doorways, furniture - Rewrite FarmCanvas.jsx with layered tile rendering (background, floor, walls, doors, furniture, agents) - Use tileset.png for floor/wall/furniture tiles with proper source coordinates - Design 6-room layout: Briefing Room, Commons, War Room, Engineering Bay, Quiet Zone, Ops Center - Central hallway connecting all rooms with doorway openings - Room labels drawn as floating pills - Default agent positions reassigned to appropriate rooms - Remove old BeOS icon tiles (replaced by LimeZu tileset)
1
assets/dawnlike
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit b5898187c2253d9213e4ae02d7228e81cdace302
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 21 KiB |
11
assets/modern-interiors/Modern tiles_Free/LICENSE.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
FREE VERSION LICENSE:
|
||||
|
||||
CAN:
|
||||
YOU CAN USE THE ASSET IN NON COMMERCIAL PROJECTS
|
||||
YOU CAN EDIT THE SPRITES AND USE THEM IN NON COMMERCIAL PROJECTS
|
||||
|
||||
CAN'T:
|
||||
YOU CAN'T USE THE ASSET IN COMMERCIAL PROJECTS
|
||||
YOU CAN'T EDIT THE SPRITES AND USE THEM IN COMMERCIAL PROJECTS
|
||||
YOU CAN'T EDIT AND RESELL THE SPRITES
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/modern-interiors/Modern tiles_Free/Old/idle_16x16_2.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/modern-interiors/Modern tiles_Free/Old/idle_32x32_2.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
assets/modern-interiors/Modern tiles_Free/Old/idle_48x48_2.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
5
assets/modern-interiors/Modern tiles_Free/READ ME.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Hi, thank you for downloading the free version!
|
||||
|
||||
This version has around 1% of material of the full asset
|
||||
|
||||
Consider buying the complete version (1.20$) if you like the asset :)
|
||||
BIN
assets/modern-interiors/Modern tiles_Free/free_overview.png
Normal file
|
After Width: | Height: | Size: 290 KiB |
|
|
@ -1,23 +1,12 @@
|
|||
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
||||
import useStore, { ZONES } from './store.js';
|
||||
import useStore from './store.js';
|
||||
import { OFFICE_MAP, WALLS, DOORWAYS, FURNITURE, TILE_SIZE, MAP_COLS, MAP_ROWS } from './officeMap.js';
|
||||
|
||||
const TILE_SIZE = 32;
|
||||
const COLS = 28;
|
||||
const ROWS = 20;
|
||||
const CANVAS_W = COLS * TILE_SIZE; // 896
|
||||
const CANVAS_H = ROWS * TILE_SIZE; // 640
|
||||
const CANVAS_W = MAP_COLS * TILE_SIZE; // 640
|
||||
const CANVAS_H = MAP_ROWS * TILE_SIZE; // 480
|
||||
|
||||
const LERP_SPEED = 0.15;
|
||||
|
||||
// ── Building definitions (grid coords) ─────────────────────────
|
||||
const BUILDINGS = [
|
||||
{ id: 'workshop', zoneId: 'pod-a', label: 'Workshop', color: '#A0522D', col: 8, row: 2, w: 5, h: 5, roofColor: '#6B4226' },
|
||||
{ id: 'studio', zoneId: 'pod-b', label: 'Studio', color: '#DEB887', col: 14, row: 2, w: 5, h: 5, roofColor: '#8B6914' },
|
||||
{ id: 'cabins', zoneId: 'solo-1', label: 'Cabins', color: '#8B4513', col: 0, row: 2, w: 6, h: 10, roofColor: '#6B4226' },
|
||||
{ id: 'library', zoneId: 'solo-2', label: 'Library', color: '#5B8FA8', col: 22, row: 2, w: 6, h: 10, roofColor: '#2F5F7A' },
|
||||
{ id: 'shrine', zoneId: 'coaching', label: 'Shrine', color: '#DAA520', col: 8, row: 13, w: 12, h: 3, roofColor: '#C68B3C' },
|
||||
];
|
||||
|
||||
// ── Sprite loader ────────────────────────────────────────────────
|
||||
function loadSprite(id) {
|
||||
const img = new Image();
|
||||
|
|
@ -31,6 +20,25 @@ const AGENT_IDS = ['cos', 'engineering', 'senior-eng', 'trust', 'market', 'healt
|
|||
const sprites = {};
|
||||
AGENT_IDS.forEach(id => { sprites[id] = loadSprite(id); });
|
||||
|
||||
// ── Tileset loader ────────────────────────────────────────────────
|
||||
let tilesetImg = null;
|
||||
function loadTileset() {
|
||||
if (!tilesetImg) {
|
||||
tilesetImg = new Image();
|
||||
tilesetImg.src = '/office/tiles/tileset.png';
|
||||
}
|
||||
return tilesetImg;
|
||||
}
|
||||
|
||||
let roomBuilderImg = null;
|
||||
function loadRoomBuilder() {
|
||||
if (!roomBuilderImg) {
|
||||
roomBuilderImg = new Image();
|
||||
roomBuilderImg.src = '/office/tiles/room-builder.png';
|
||||
}
|
||||
return roomBuilderImg;
|
||||
}
|
||||
|
||||
// ── Particle system for sparkles ─────────────────────────────────
|
||||
let sparkles = [];
|
||||
|
||||
|
|
@ -46,301 +54,189 @@ function spawnSparkle(x, y) {
|
|||
if (sparkles.length > 60) sparkles.shift();
|
||||
}
|
||||
|
||||
// ── BeOS activity icons ──────────────────────────────────────────
|
||||
const activityIcons = {};
|
||||
const ACTIVITY_ICONS = [
|
||||
'dispatch', 'code', 'build', 'review', 'research', 'analyze',
|
||||
'write', 'security', 'monitor', 'memory', 'coach', 'heartbeat',
|
||||
'mail', 'collaborate', 'present', 'vault', 'think', 'idle',
|
||||
'blocked', 'document', 'terminal', 'package', 'network',
|
||||
'search', 'design', 'data',
|
||||
];
|
||||
// ── Tile drawing helpers ──────────────────────────────────────────
|
||||
|
||||
function loadActivityIcons() {
|
||||
ACTIVITY_ICONS.forEach(name => {
|
||||
const img = new Image();
|
||||
img.src = `/office/beos-icons/${name}.png`;
|
||||
activityIcons[name] = img;
|
||||
});
|
||||
// Draw a single tile from tileset.png (16-wide grid)
|
||||
function drawTileFromTileset(ctx, tileId, dx, dy) {
|
||||
if (tileId < 0 || tileId === undefined) return;
|
||||
const img = loadTileset();
|
||||
if (!img || !img.complete) return;
|
||||
const sx = (tileId % 16) * TILE_SIZE;
|
||||
const sy = Math.floor(tileId / 16) * TILE_SIZE;
|
||||
ctx.drawImage(img, sx, sy, TILE_SIZE, TILE_SIZE, dx, dy, TILE_SIZE, TILE_SIZE);
|
||||
}
|
||||
|
||||
// Map agent state → BeOS icon name
|
||||
function getActivityIcon(agent) {
|
||||
const activity = agent.activity || 'idle';
|
||||
|
||||
if (activity === 'idle' || activity === 'offline') return 'idle';
|
||||
if (activity === 'blocked') return 'blocked';
|
||||
if (activity === 'thinking') return 'think';
|
||||
|
||||
// activity === 'working' → check currentTask keywords
|
||||
const task = (agent.currentTask || '').toLowerCase();
|
||||
|
||||
if (/\b(dispatch|rout)\b/.test(task)) return 'dispatch';
|
||||
if (/\b(code|implement|build|pr)\b/.test(task)) return /\bbuild\b/.test(task) ? 'build' : 'code';
|
||||
if (/\b(review|audit)\b/.test(task)) return 'review';
|
||||
if (/\bresearch\b/.test(task)) return 'research';
|
||||
if (/\bsearch\b/.test(task)) return 'search';
|
||||
if (/\banalyz\b/.test(task)) return 'analyze';
|
||||
if (/\b(writ|draft|content|blog)\b/.test(task)) return 'write';
|
||||
if (/\bsecurit|complianc\b/.test(task)) return 'security';
|
||||
if (/\bmonitor\b/.test(task)) return 'monitor';
|
||||
if (/\bhealth|check\b/.test(task)) return 'heartbeat';
|
||||
if (/\b(memory|index|vault|note)\b/.test(task)) return /\bvault\b/.test(task) ? 'vault' : 'memory';
|
||||
if (/\b(coach|learn|improv)\b/.test(task)) return 'coach';
|
||||
if (/\b(mail|message|communicat)\b/.test(task)) return 'mail';
|
||||
if (/\b(collaborat|pair|meeting)\b/.test(task)) return 'collaborate';
|
||||
if (/\b(present|slide|deck)\b/.test(task)) return 'present';
|
||||
if (/\b(document|spec|readme)\b/.test(task)) return 'document';
|
||||
if (/\b(terminal|cli|script)\b/.test(task)) return 'terminal';
|
||||
if (/\b(deploy|package|release)\b/.test(task)) return 'package';
|
||||
if (/\b(network|connect|api)\b/.test(task)) return 'network';
|
||||
if (/\b(design|layout|ui|css)\b/.test(task)) return 'design';
|
||||
if (/\b(data|stats)\b/.test(task)) return 'data';
|
||||
|
||||
// Default: code icon for working agents
|
||||
return 'code';
|
||||
// Draw a single tile from room-builder.png (17-wide grid)
|
||||
function drawTileFromRoomBuilder(ctx, tileId, dx, dy) {
|
||||
if (tileId < 0 || tileId === undefined) return;
|
||||
const img = loadRoomBuilder();
|
||||
if (!img || !img.complete) return;
|
||||
const RB_COLS = 17;
|
||||
const sx = (tileId % RB_COLS) * TILE_SIZE;
|
||||
const sy = Math.floor(tileId / RB_COLS) * TILE_SIZE;
|
||||
ctx.drawImage(img, sx, sy, TILE_SIZE, TILE_SIZE, dx, dy, TILE_SIZE, TILE_SIZE);
|
||||
}
|
||||
|
||||
// ── Draw helpers ──────────────────────────────────────────────────
|
||||
function drawGrass(ctx) {
|
||||
// Dark green base
|
||||
ctx.fillStyle = '#2D5A27';
|
||||
// ── Layer 1: Background / outdoors ────────────────────────────────
|
||||
function drawBackground(ctx) {
|
||||
// Dark exterior (outside the office)
|
||||
ctx.fillStyle = '#1a1a2e';
|
||||
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
|
||||
|
||||
// Random pixel texture dots
|
||||
const imageData = ctx.getImageData(0, 0, CANVAS_W, CANVAS_H);
|
||||
for (let i = 0; i < 2000; i++) {
|
||||
const x = Math.floor(Math.random() * CANVAS_W);
|
||||
const y = Math.floor(Math.random() * CANVAS_H);
|
||||
const idx = (y * CANVAS_W + x) * 4;
|
||||
const shade = 60 + Math.random() * 40;
|
||||
imageData.data[idx] = shade + 20;
|
||||
imageData.data[idx + 1] = shade + 40;
|
||||
imageData.data[idx + 2] = shade;
|
||||
imageData.data[idx + 3] = 255;
|
||||
// Grass/texture around building (top strip)
|
||||
ctx.fillStyle = '#2D5A27';
|
||||
ctx.fillRect(0, 0, CANVAS_W, 3 * TILE_SIZE);
|
||||
|
||||
// Add some texture to the grass
|
||||
for (let i = 0; i < 300; i++) {
|
||||
const x = Math.random() * CANVAS_W;
|
||||
const y = Math.random() * 3 * TILE_SIZE;
|
||||
ctx.fillStyle = `rgb(${40 + Math.random() * 30},${70 + Math.random() * 40},${30 + Math.random() * 20})`;
|
||||
ctx.fillRect(x, y, 2, 2);
|
||||
}
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
|
||||
function drawDirtPaths(ctx) {
|
||||
ctx.fillStyle = '#8B6914';
|
||||
// Horizontal paths
|
||||
ctx.fillRect(0, 48, CANVAS_W, 16);
|
||||
ctx.fillRect(0, 224, CANVAS_W, 16);
|
||||
ctx.fillRect(0, 544, CANVAS_W, 16);
|
||||
// Vertical paths
|
||||
ctx.fillRect(208, 0, 16, CANVAS_H);
|
||||
ctx.fillRect(432, 0, 16, CANVAS_H);
|
||||
ctx.fillRect(704, 0, 16, CANVAS_H);
|
||||
|
||||
// Path edge shading
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.15)';
|
||||
ctx.fillRect(0, 48, CANVAS_W, 2);
|
||||
ctx.fillRect(0, 224, CANVAS_W, 2);
|
||||
ctx.fillRect(0, 544, CANVAS_W, 2);
|
||||
}
|
||||
|
||||
function drawBuilding(ctx, b) {
|
||||
const px = b.col * TILE_SIZE;
|
||||
const py = b.row * TILE_SIZE;
|
||||
const pw = b.w * TILE_SIZE;
|
||||
const ph = b.h * TILE_SIZE;
|
||||
const roofH = 24;
|
||||
|
||||
// Shadow under building
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.2)';
|
||||
ctx.fillRect(px + 4, py + 4, pw, ph);
|
||||
|
||||
// Roof (triangle)
|
||||
ctx.fillStyle = b.roofColor;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px - 2, py);
|
||||
ctx.lineTo(px + pw / 2, py - roofH);
|
||||
ctx.lineTo(px + pw + 2, py);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// Building body
|
||||
ctx.fillStyle = b.color;
|
||||
ctx.fillRect(px, py, pw, ph);
|
||||
|
||||
// Brick/wall texture pattern
|
||||
ctx.strokeStyle = 'rgba(0,0,0,0.2)';
|
||||
ctx.lineWidth = 1;
|
||||
for (let r = 0; r < b.h; r++) {
|
||||
const y = py + r * TILE_SIZE;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px, y);
|
||||
ctx.lineTo(px + pw, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
for (let c = 0; c < b.w; c++) {
|
||||
const x = px + c * TILE_SIZE;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, py);
|
||||
ctx.lineTo(x, py + ph);
|
||||
ctx.stroke();
|
||||
// ── Layer 2: Floor tiles ──────────────────────────────────────────
|
||||
function drawFloorLayer(ctx) {
|
||||
const img = loadTileset();
|
||||
if (!img || !img.complete) {
|
||||
// Fallback: draw solid colors
|
||||
const colors = {
|
||||
16: '#C4A882', 22: '#8BA87E', 83: '#9B9B9B', 67: '#B8956E',
|
||||
38: '#4A5070', 54: '#7A7A85', 70: '#A89B8A',
|
||||
};
|
||||
for (let row = 0; row < MAP_ROWS; row++) {
|
||||
for (let col = 0; col < MAP_COLS; col++) {
|
||||
const tileId = OFFICE_MAP[row]?.[col];
|
||||
if (tileId && tileId > 0) {
|
||||
ctx.fillStyle = colors[tileId] || '#555';
|
||||
ctx.fillRect(col * TILE_SIZE, row * TILE_SIZE, TILE_SIZE, TILE_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Door
|
||||
const doorW = 16;
|
||||
const doorH = 24;
|
||||
ctx.fillStyle = '#4A3520';
|
||||
ctx.fillRect(px + pw / 2 - doorW / 2, py + ph - doorH - 4, doorW, doorH);
|
||||
ctx.fillStyle = '#DAA520';
|
||||
ctx.fillRect(px + pw / 2 + doorW / 2 - 4, py + ph - doorH / 2 - 2, 3, 3);
|
||||
|
||||
// Windows (small squares)
|
||||
ctx.fillStyle = '#87CEEB';
|
||||
if (b.w >= 4) {
|
||||
for (let side = 0; side < 2; side++) {
|
||||
const wx = side === 0 ? px + TILE_SIZE * 0.5 : px + pw - TILE_SIZE * 1.5;
|
||||
ctx.fillRect(wx, py + TILE_SIZE * 1.5, 14, 12);
|
||||
ctx.strokeStyle = '#6B4226';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(wx, py + TILE_SIZE * 1.5, 14, 12);
|
||||
for (let row = 0; row < MAP_ROWS; row++) {
|
||||
for (let col = 0; col < MAP_COLS; col++) {
|
||||
const tileId = OFFICE_MAP[row]?.[col];
|
||||
if (tileId && tileId > 0) {
|
||||
drawTileFromTileset(ctx, tileId, col * TILE_SIZE, row * TILE_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Label pill
|
||||
const labelW = ctx.measureText(b.label).width + 12;
|
||||
const labelH = 16;
|
||||
const labelX = px + pw / 2 - labelW / 2;
|
||||
const labelY = py - roofH - labelH - 2;
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.65)';
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(labelX, labelY, labelW, labelH, 4);
|
||||
ctx.fill();
|
||||
ctx.font = 'bold 10px "Courier New", monospace';
|
||||
ctx.fillStyle = '#FFD700';
|
||||
ctx.fillText(b.label, labelX + 6, labelY + 12);
|
||||
}
|
||||
|
||||
function drawCampfire(ctx, time) {
|
||||
const cx = 10 * TILE_SIZE + 4 * TILE_SIZE; // center of campfire (col 10, 8 wide)
|
||||
const cy = 0 * TILE_SIZE + 32; // row 0, 2 tall
|
||||
// ── Layer 3: Walls ────────────────────────────────────────────────
|
||||
function drawWallLayer(ctx) {
|
||||
const img = loadTileset();
|
||||
const drawn = new Set();
|
||||
|
||||
// Ground circle
|
||||
ctx.fillStyle = '#E8D5B7';
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(cx, cy + 16, 112, 24, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
for (const wall of WALLS) {
|
||||
const key = `${wall.row},${wall.col}`;
|
||||
if (drawn.has(key)) continue;
|
||||
drawn.add(key);
|
||||
|
||||
// Logs
|
||||
ctx.fillStyle = '#6B4226';
|
||||
ctx.fillRect(cx - 48, cy + 20, 40, 8);
|
||||
ctx.fillRect(cx + 8, cy + 20, 40, 8);
|
||||
ctx.fillRect(cx - 20, cy + 16, 8, 40);
|
||||
const dx = wall.col * TILE_SIZE;
|
||||
const dy = wall.row * TILE_SIZE;
|
||||
|
||||
// Animated flame
|
||||
const flicker = Math.sin(time * 0.05) * 4;
|
||||
const f1 = Math.sin(time * 0.08 + 0.5) * 6;
|
||||
const f2 = Math.sin(time * 0.11 + 1.2) * 5;
|
||||
|
||||
ctx.fillStyle = '#FF8C42';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx - 6 + f2, cy + 8);
|
||||
ctx.quadraticCurveTo(cx - 4 + f1, cy - flicker - 8, cx, cy - flicker - 20);
|
||||
ctx.quadraticCurveTo(cx + 4 + f1, cy - flicker - 8, cx + 6 + f2, cy + 8);
|
||||
ctx.fill();
|
||||
|
||||
// Inner flame
|
||||
ctx.fillStyle = '#FFD700';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx - 3, cy + 8);
|
||||
ctx.quadraticCurveTo(cx - 2, cy - 8, cx, cy - 16);
|
||||
ctx.quadraticCurveTo(cx + 2, cy - 8, cx + 3, cy + 8);
|
||||
ctx.fill();
|
||||
|
||||
// Core
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx - 1, cy + 8);
|
||||
ctx.quadraticCurveTo(cx, cy - 4, cx, cy - 10);
|
||||
ctx.quadraticCurveTo(cx + 1, cy - 4, cx + 1, cy + 8);
|
||||
ctx.fill();
|
||||
|
||||
// Warm glow
|
||||
const glow = ctx.createRadialGradient(cx, cy - 10, 2, cx, cy, 80);
|
||||
glow.addColorStop(0, 'rgba(255,200,50,0.3)');
|
||||
glow.addColorStop(0.5, 'rgba(255,140,40,0.1)');
|
||||
glow.addColorStop(1, 'rgba(255,100,30,0)');
|
||||
ctx.fillStyle = glow;
|
||||
ctx.fillRect(cx - 80, cy - 40, 160, 100);
|
||||
|
||||
// Label
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.65)';
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(cx - 35, cy - 42, 70, 16, 4);
|
||||
ctx.fill();
|
||||
ctx.font = 'bold 10px "Courier New", monospace';
|
||||
ctx.fillStyle = '#FFD700';
|
||||
ctx.fillText('Campfire', cx - 30, cy - 30);
|
||||
if (img && img.complete) {
|
||||
drawTileFromTileset(ctx, wall.tile, dx, dy);
|
||||
} else {
|
||||
// Fallback wall rendering
|
||||
ctx.fillStyle = '#6B5B4F';
|
||||
ctx.fillRect(dx, dy, TILE_SIZE, TILE_SIZE);
|
||||
ctx.strokeStyle = '#5A4A3E';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(dx + 1, dy + 1, TILE_SIZE - 2, TILE_SIZE - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawDecorations(ctx) {
|
||||
// Trees
|
||||
const treePositions = [
|
||||
...Array.from({ length: 7 }, (_, i) => ({ x: i * 20 + 10, y: 596 })),
|
||||
...Array.from({ length: 7 }, (_, i) => ({ x: i * 20 + 10, y: 40 })),
|
||||
{ x: 845, y: 80 }, { x: 845, y: 128 }, { x: 845, y: 176 },
|
||||
{ x: 10, y: 80 }, { x: 10, y: 128 },
|
||||
// ── Layer 3.5: Doorways ───────────────────────────────────────────
|
||||
function drawDoorways(ctx) {
|
||||
const img = loadTileset();
|
||||
for (const door of DOORWAYS) {
|
||||
const dx = door.col * TILE_SIZE;
|
||||
const dy = door.row * TILE_SIZE;
|
||||
if (img && img.complete) {
|
||||
drawTileFromTileset(ctx, door.tile, dx, dy);
|
||||
} else {
|
||||
// Fallback doorway
|
||||
ctx.fillStyle = '#4A3520';
|
||||
ctx.fillRect(dx, dy, TILE_SIZE, TILE_SIZE);
|
||||
ctx.fillStyle = '#DAA520';
|
||||
ctx.fillRect(dx + TILE_SIZE - 5, dy + TILE_SIZE / 2 - 1, 4, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Layer 4: Furniture ────────────────────────────────────────────
|
||||
function drawFurniture(ctx) {
|
||||
const img = loadTileset();
|
||||
for (const furn of FURNITURE) {
|
||||
const dx = furn.col * TILE_SIZE;
|
||||
const dy = furn.row * TILE_SIZE;
|
||||
if (img && img.complete) {
|
||||
drawTileFromTileset(ctx, furn.tile, dx, dy);
|
||||
} else {
|
||||
// Fallback colored furniture
|
||||
const fColors = {
|
||||
528: '#8B6914', 936: '#4A3728', 736: '#6B4226', 584: '#2D8B2D',
|
||||
674: '#DAA520', 1012: '#555', 672: '#8B7355', 996: '#222',
|
||||
1010: '#444', 889: '#FFD700', 709: '#EEE',
|
||||
};
|
||||
ctx.fillStyle = fColors[furn.tile] || '#666';
|
||||
ctx.fillRect(dx + 2, dy + 2, TILE_SIZE - 4, TILE_SIZE - 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Room labels ───────────────────────────────────────────────────
|
||||
function drawRoomLabels(ctx) {
|
||||
const rooms = [
|
||||
{ label: 'Briefing Room', col: 0, row: 3, w: 6 },
|
||||
{ label: 'Commons', col: 6, row: 3, w: 8 },
|
||||
{ label: 'War Room', col: 14, row: 3, w: 6 },
|
||||
{ label: 'Engineering Bay', col: 0, row: 9, w: 6 },
|
||||
{ label: 'Quiet Zone', col: 6, row: 9, w: 8 },
|
||||
{ label: 'Ops Center', col: 14, row: 9, w: 6 },
|
||||
];
|
||||
|
||||
for (const t of treePositions) {
|
||||
// Trunk
|
||||
ctx.fillStyle = '#6B4226';
|
||||
ctx.fillRect(t.x - 1, t.y + 8, 3, 8);
|
||||
// Canopy
|
||||
ctx.fillStyle = '#2D5A27';
|
||||
for (const room of rooms) {
|
||||
const cx = room.col * TILE_SIZE + (room.w * TILE_SIZE) / 2;
|
||||
const topY = room.row * TILE_SIZE;
|
||||
const labelW = ctx.measureText(room.label).width + 10;
|
||||
const labelH = 12;
|
||||
|
||||
// Semi-transparent pill behind text
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(t.x, t.y - 8);
|
||||
ctx.lineTo(t.x - 6, t.y + 8);
|
||||
ctx.lineTo(t.x + 6, t.y + 8);
|
||||
ctx.closePath();
|
||||
ctx.roundRect(cx - labelW / 2, topY + 2, labelW, labelH, 3);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#4A7C3F';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(t.x, t.y - 4);
|
||||
ctx.lineTo(t.x - 4, t.y + 4);
|
||||
ctx.lineTo(t.x + 4, t.y + 4);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Rocks
|
||||
const rocks = [
|
||||
{ x: 250, y: 560 }, { x: 480, y: 570 }, { x: 420, y: 550 }, { x: 720, y: 560 },
|
||||
{ x: 380, y: 540 }, { x: 160, y: 570 },
|
||||
];
|
||||
for (const r of rocks) {
|
||||
ctx.fillStyle = '#777';
|
||||
ctx.fillRect(r.x, r.y + 2, 8, 5);
|
||||
ctx.fillStyle = '#999';
|
||||
ctx.fillRect(r.x + 1, r.y, 7, 5);
|
||||
ctx.font = 'bold 8px "Courier New", monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = '#DDD';
|
||||
ctx.fillText(room.label, cx, topY + 11);
|
||||
}
|
||||
ctx.textAlign = 'start';
|
||||
|
||||
// Flowers
|
||||
const flowers = [
|
||||
{ x: 240, y: 580, c: '#FF6347' }, { x: 500, y: 590, c: '#FFD700' },
|
||||
{ x: 350, y: 585, c: '#87CEEB' }, { x: 650, y: 595, c: '#DDA0DD' },
|
||||
{ x: 130, y: 582, c: '#FF6347' }, { x: 580, y: 580, c: '#FFD700' },
|
||||
];
|
||||
for (const f of flowers) {
|
||||
ctx.fillStyle = '#4CAF50';
|
||||
ctx.fillRect(f.x + 1, f.y + 3, 1, 5);
|
||||
ctx.fillStyle = f.c;
|
||||
ctx.fillRect(f.x, f.y, 4, 4);
|
||||
}
|
||||
|
||||
// Fence posts along top path
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const fx = 120 + i * 64;
|
||||
ctx.fillStyle = '#8B4513';
|
||||
ctx.fillRect(fx, 56, 3, 14);
|
||||
}
|
||||
// Hallway label
|
||||
const hx = MAP_COLS * TILE_SIZE / 2;
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.4)';
|
||||
const hlW = ctx.measureText('Hallway').width + 10;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(hx - hlW / 2, 7 * TILE_SIZE + 6, hlW, 12, 3);
|
||||
ctx.fill();
|
||||
ctx.font = 'bold 8px "Courier New", monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = '#CCC';
|
||||
ctx.fillText('Hallway', hx, 7 * TILE_SIZE + 15);
|
||||
ctx.textAlign = 'start';
|
||||
}
|
||||
|
||||
// ── Layer 5: Agents ───────────────────────────────────────────────
|
||||
function drawAgent(ctx, agent, time) {
|
||||
if (!agent || !agent.position) return;
|
||||
|
||||
|
|
@ -371,14 +267,13 @@ function drawAgent(ctx, agent, time) {
|
|||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.95;
|
||||
ctx.translate(px, py + yOffset);
|
||||
|
||||
// Activity states
|
||||
if (activity === 'offline') {
|
||||
ctx.globalAlpha = 0.5;
|
||||
ctx.filter = 'grayscale(100%)';
|
||||
} else if (activity === 'blocked') {
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
// Draw sprite centered
|
||||
|
|
@ -389,39 +284,28 @@ function drawAgent(ctx, agent, time) {
|
|||
|
||||
ctx.restore();
|
||||
|
||||
// ── Activity icon above agent ──────────────────────────────
|
||||
const iconName = getActivityIcon(agent);
|
||||
const iconImg = activityIcons[iconName];
|
||||
if (iconImg && iconImg.complete && iconImg.naturalWidth > 0) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.drawImage(iconImg, px - 12, py + yOffset - 60, 24, 24);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Blocked: red tint overlay
|
||||
if (activity === 'blocked') {
|
||||
ctx.fillStyle = 'rgba(255,50,50,0.3)';
|
||||
ctx.fillRect(px - 16, py + yOffset - 16, 32, 32);
|
||||
// "!" indicator
|
||||
ctx.font = 'bold 12px "Courier New", monospace';
|
||||
ctx.fillStyle = '#FF4444';
|
||||
ctx.fillText('!', px - 3, py + yOffset - 24);
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('!', px, py + yOffset - 24);
|
||||
}
|
||||
|
||||
// Agent name to the right (horizontal)
|
||||
// Agent name below (centered with shadow)
|
||||
ctx.font = '9px "Courier New", monospace';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillText(agent.name || agent.id, px + 20, py + yOffset + 4);
|
||||
ctx.fillText(agent.name || agent.id, px + 1, py + yOffset + 27);
|
||||
ctx.fillStyle = '#FFF';
|
||||
ctx.fillText(agent.name || agent.id, px + 19, py + yOffset + 3);
|
||||
ctx.fillText(agent.name || agent.id, px, py + yOffset + 26);
|
||||
|
||||
// Thinking bubble
|
||||
if (activity === 'thinking') {
|
||||
ctx.font = 'bold 10px "Courier New", monospace';
|
||||
ctx.textAlign = 'center';
|
||||
const bubbleText = '...';
|
||||
const bubbleW = 24;
|
||||
const bubbleH = 14;
|
||||
const bx = px - bubbleW / 2;
|
||||
|
|
@ -435,7 +319,7 @@ function drawAgent(ctx, agent, time) {
|
|||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.fillText(bubbleText, px, by + 11);
|
||||
ctx.fillText('...', px, py + yOffset - 33);
|
||||
}
|
||||
|
||||
// Sparkles for working
|
||||
|
|
@ -454,7 +338,7 @@ function drawParticles(ctx) {
|
|||
s.x += s.vx;
|
||||
s.y += s.vy;
|
||||
s.life -= s.decay;
|
||||
const alpha = s.life;
|
||||
const alpha = Math.max(0, s.life);
|
||||
ctx.fillStyle = s.color;
|
||||
ctx.globalAlpha = alpha;
|
||||
ctx.fillRect(s.x, s.y, 2, 2);
|
||||
|
|
@ -517,11 +401,6 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) {
|
|||
}
|
||||
}, [onAgentClick]);
|
||||
|
||||
// Load activity icons on mount
|
||||
useEffect(() => {
|
||||
loadActivityIcons();
|
||||
}, []);
|
||||
|
||||
// Animation loop
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
|
@ -529,19 +408,33 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) {
|
|||
const ctx = canvas.getContext('2d');
|
||||
ctx.imageSmoothingEnabled = false; // pixel-art crisp
|
||||
|
||||
// Pre-load tileset
|
||||
loadTileset();
|
||||
let startTime = Date.now();
|
||||
|
||||
function loop() {
|
||||
const time = Date.now() - startTime;
|
||||
ctx.clearRect(0, 0, CANVAS_W, CANVAS_H);
|
||||
|
||||
drawGrass(ctx);
|
||||
drawDirtPaths(ctx);
|
||||
for (const b of BUILDINGS) drawBuilding(ctx, b);
|
||||
drawCampfire(ctx, time);
|
||||
drawDecorations(ctx);
|
||||
// Layer 1: Background (outdoor area around building)
|
||||
drawBackground(ctx);
|
||||
|
||||
// Draw agents
|
||||
// Layer 2: Floor tiles (from tileset)
|
||||
drawFloorLayer(ctx);
|
||||
|
||||
// Layer 3: Walls (from tileset, drawn on top of floors)
|
||||
drawWallLayer(ctx);
|
||||
|
||||
// Layer 3.5: Doorways (openings in walls)
|
||||
drawDoorways(ctx);
|
||||
|
||||
// Layer 4: Furniture
|
||||
drawFurniture(ctx);
|
||||
|
||||
// Room labels
|
||||
drawRoomLabels(ctx);
|
||||
|
||||
// Layer 5: Agents
|
||||
const currentAgents = useStore.getState().agents;
|
||||
for (const agent of Object.values(currentAgents)) {
|
||||
drawAgent(ctx, agent, time);
|
||||
|
|
@ -556,7 +449,7 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) {
|
|||
const hpy = pos.row * TILE_SIZE + TILE_SIZE / 2;
|
||||
const activity = hAgent.activity || 'idle';
|
||||
const name = hAgent.name || hAgent.id;
|
||||
const task = hAgent.task || '';
|
||||
const task = hAgent.currentTask || hAgent.task || '';
|
||||
|
||||
const statusColors = {
|
||||
idle: '#44cc44', working: '#ffcc00', thinking: '#44aaff',
|
||||
|
|
|
|||
421
src/frontend/officeMap.js
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
// Tile map for spatial office using LimeZu Modern Interiors tileset
|
||||
// Tileset: 16 tiles wide × 89 tiles tall, each tile 32×32px
|
||||
// Room-builder: 17 tiles wide × 23 tiles tall, floor textures and wall variants
|
||||
|
||||
// Map dimensions: 20 cols × 15 rows = 640×480 internal px (scaled via canvas CSS)
|
||||
export const MAP_COLS = 20;
|
||||
export const MAP_ROWS = 15;
|
||||
export const TILE_SIZE = 32;
|
||||
|
||||
// ── Tile IDs from tileset.png (16-wide grid) ────────────────────
|
||||
// Tile grid position: tileId = row * 16 + col
|
||||
export const TILES = {
|
||||
EMPTY: -1,
|
||||
|
||||
// ── Ground / outdoor tiles ──────────────────────────────
|
||||
// We'll use the room-builder for grass/outdoor if needed
|
||||
GRASS: -2, // drawn procedurally
|
||||
|
||||
// ── Floor tiles from tileset.png ────────────────────────
|
||||
// Wood floor variants (row 1, cols 0-3): tile IDs 16-19
|
||||
FLOOR_WOOD_LIGHT: 16, // (r1,c0) - light wood planks
|
||||
FLOOR_WOOD_LIGHT2: 18, // (r1,c2) - light wood variant
|
||||
FLOOR_WOOD_LIGHT3: 19, // (r1,c3) - light wood variant
|
||||
|
||||
// Stone/checkered floor (row 5-6): tile IDs ~83-99
|
||||
FLOOR_STONE: 83, // (r5,c3) - stone floor
|
||||
FLOOR_STONE2: 99, // (r6,c3) - stone floor variant
|
||||
|
||||
// Carpet tiles (row 1, cols 6-13): tile IDs 22-29
|
||||
FLOOR_CARPET_GREEN: 22, // (r1,c6) - greenish carpet
|
||||
FLOOR_CARPET_GREEN2: 23, // (r1,c7) - green variant
|
||||
FLOOR_CARPET_BROWN: 25, // (r1,c9) - brown carpet
|
||||
FLOOR_CARPET_BROWN2: 26, // (r1,c10) - brown variant
|
||||
|
||||
// Dark blue/study floor (row 2, cols 6-14): tile IDs 38-46
|
||||
FLOOR_STUDY: 38, // (r2,c6) - dark blue study carpet
|
||||
FLOOR_STUDY2: 39, // (r2,c7) - variant
|
||||
|
||||
// Gray tile floor (row 3, cols 6-11): tile IDs 54-59
|
||||
FLOOR_GRAY_TILE: 54, // (r3,c6) - gray tile
|
||||
FLOOR_GRAY_TILE2: 55, // (r3,c7) - variant
|
||||
|
||||
// Warm wood floor (row 4): tile IDs 67-68
|
||||
FLOOR_WARM_WOOD: 67, // (r4,c3) - warm wood planks
|
||||
FLOOR_WARM_WOOD2: 68, // (r4,c4) - variant
|
||||
|
||||
// Plush carpet (row 4, cols 6-9): tile IDs 70-73
|
||||
FLOOR_PLUSH: 70, // (r4,c6) - plush pink/tan carpet
|
||||
FLOOR_PLUSH2: 71, // (r4,c7) - variant
|
||||
|
||||
// ── Wall tiles from tileset.png ─────────────────────────
|
||||
// Wall sections (row 16+): stone/brick walls
|
||||
// Interior wall (row 16-17): tile IDs ~257-287
|
||||
WALL_BRICK: 258, // (r16,c2) - brick wall body
|
||||
WALL_BRICK2: 259, // (r16,c3) - brick variant
|
||||
WALL_BRICK_TOP: 257, // (r16,c1) - brick wall top edge
|
||||
WALL_BRICK_BOTTOM: 274, // (r17,c2) - brick wall bottom
|
||||
|
||||
// Dark wall (row 26-28): tile IDs ~416-461
|
||||
WALL_DARK: 433, // (r27,c1) - dark wall body
|
||||
WALL_DARK2: 434, // (r27,c2) - variant
|
||||
|
||||
// Light plaster wall (row 28-29): tile IDs ~448-477
|
||||
WALL_PLASTER: 464, // (r29,c0) - plaster wall body
|
||||
WALL_PLASTER_TOP: 466, // (r29,c2) - top with shadow
|
||||
|
||||
// Interior wood wall (room-builder, row 5 col 2-3): room-builder tiles
|
||||
// We reference room-builder walls separately
|
||||
|
||||
// ── Door tiles from tileset.png ─────────────────────────
|
||||
// Doors are typically in the wall sections
|
||||
DOOR_WOOD: 296, // (r18,c8) - red/wood door, closed
|
||||
DOOR_WOOD2: 297, // (r18,c9) - variant
|
||||
|
||||
// ── Window tiles ────────────────────────────────────────
|
||||
WINDOW: 305, // (r19,c1) - window hint
|
||||
WINDOW_GLASS: 300, // (r18,c12) - glass pane
|
||||
|
||||
// ── Furniture tiles (approximate from analysis) ────────
|
||||
// These are in the tileset rows 33-65
|
||||
TABLE_WOOD: 580, // (r36,c4) - table top
|
||||
CHAIR_WOOD: 528, // (r33,c0) - wooden chair
|
||||
DESK: 583, // (r36,c7) - desk surface
|
||||
BOOKSHELF: 736, // (r46,c0) - bookshelf
|
||||
BOOKSHELF2: 737, // (r46,c1) - bookshelf variant
|
||||
PLANT: 584, // (r36,c8) - potted plant
|
||||
PLANT2: 587, // (r36,c11) - plant variant
|
||||
LAMP: 674, // (r42,c2) - lamp/light
|
||||
SOFA_LEFT: 736, // Using bookshelf area as sofa placeholder
|
||||
SOFA_RIGHT: 737,
|
||||
FILING_CABINET: 744, // (r46,c8) - cabinet/locker
|
||||
RUG: 652, // (r40,c12) - decorative rug
|
||||
COFFEE_TABLE: 672, // (r42,c0) - low table
|
||||
PLANT_BIG: 632, // (r39,c8) - large plant
|
||||
MONITOR: 744, // Monitor-like object
|
||||
WHITEBOARD: 709, // (r44,c5) - whiteboard/wall decor
|
||||
};
|
||||
// ── Room builder tiles (separate PNG) ──────────────────────────
|
||||
// These are for floor/wall variants from room-builder.png
|
||||
// Room-builder is 17 tiles wide
|
||||
export const RB_TILES = {
|
||||
// Wall textures (rows 5-15 in room-builder)
|
||||
WALL_RED_BRICK: 80, // (r5,c0) - red brick wall
|
||||
WALL_RED_BRICK2: 81, // (r5,c1) - variant
|
||||
WALL_DARK_WOOD: 84, // (r5,c4) - dark wood panel
|
||||
WALL_CREAM: 112, // (r7,c0) - cream/yellow wall
|
||||
WALL_CREAM2: 113, // (r7,c1) - variant
|
||||
WALL_BROWN_WOOD: 128, // (r8,c0) - brown wood panel
|
||||
WALL_TEAL: 144, // (r9,c0) - teal wall
|
||||
WALL_GREEN: 160, // (r10,c0) - green wall
|
||||
WALL_STONE_WARM: 176, // (r11,c0) - warm stone
|
||||
WALL_BEIGE: 192, // (r12,c0) - beige wall
|
||||
WALL_GRAY_STONE: 208, // (r13,c0) - gray stone
|
||||
WALL_RED_STONE: 240, // (r15,c0) - red stone
|
||||
|
||||
// Floor tiles (scattered in room-builder)
|
||||
FLOOR_STONE_BEIGE: 48, // (r3,c0) area - beige stone
|
||||
FLOOR_STONE_BEIGE2: 49,
|
||||
};
|
||||
|
||||
// ── Room definitions ──────────────────────────────────────────
|
||||
export const ROOMS = {
|
||||
'briefing': { name: 'Briefing Room', col: 0, row: 3, w: 6, h: 3, floor: 'FLOOR_WOOD_LIGHT' },
|
||||
'commons': { name: 'Commons', col: 6, row: 3, w: 8, h: 3, floor: 'FLOOR_CARPET_GREEN' },
|
||||
'war-room': { name: 'War Room', col: 14, row: 3, w: 6, h: 3, floor: 'FLOOR_STONE' },
|
||||
'eng-bay': { name: 'Engineering Bay', col: 0, row: 9, w: 6, h: 6, floor: 'FLOOR_WARM_WOOD' },
|
||||
'quiet': { name: 'Quiet Zone', col: 6, row: 9, w: 8, h: 4, floor: 'FLOOR_STUDY' },
|
||||
'ops': { name: 'Ops Center', col: 14, row: 9, w: 6, h: 6, floor: 'FLOOR_GRAY_TILE' },
|
||||
};
|
||||
|
||||
// ── Hallway tile markers ──────────────────────────────────────
|
||||
const HALL = 999; // hallway marker (rendered with hallway floor)
|
||||
const EMPTY = -1; // outside/void
|
||||
|
||||
// ── Tile type constants for multi-tile rendering ──────────────
|
||||
// We'll use these as references for the 2D map grid
|
||||
const F = {
|
||||
// Floors
|
||||
WL: 16, // wood light
|
||||
WL2: 18, // wood light variant
|
||||
CG: 22, // carpet green
|
||||
CG2: 23, // carpet green variant
|
||||
ST: 83, // stone tile
|
||||
ST2: 99, // stone variant
|
||||
WW: 67, // warm wood
|
||||
WW2: 68, // warm wood variant
|
||||
SD: 38, // study carpet (dark blue)
|
||||
SD2: 39, // study variant
|
||||
GT: 54, // gray tile
|
||||
GT2: 55, // gray tile variant
|
||||
HL: 70, // hallway (plush carpet)
|
||||
|
||||
// Walls (from tileset)
|
||||
WB: 258, // wall brick
|
||||
WB2: 259, // wall brick variant
|
||||
WT: 257, // wall top
|
||||
WWD: 464, // wood wall dark
|
||||
WWD2: 465,// wood wall dark variant
|
||||
|
||||
// Furniture
|
||||
TB: 528, // table/chair base
|
||||
DK: 936, // desk
|
||||
BS: 736, // bookshelf
|
||||
PL: 584, // plant
|
||||
LP: 889, // lamp
|
||||
SF: 674, // sofa
|
||||
FC: 1012, // filing cabinet
|
||||
RG: 672, // rug/coffee table
|
||||
MB: 996, // monitor
|
||||
CB: 1010, // cabinet
|
||||
DO: 296, // door
|
||||
};
|
||||
|
||||
// ── Office Map: 20 columns × 15 rows ─────────────────────────
|
||||
// Each cell is a base tile ID. Walls are drawn on top as border tiles.
|
||||
// Row 0-2: exterior wall / roof area
|
||||
// Row 3-5: upper rooms (Briefing, Commons, War Room)
|
||||
// Row 6-8: hallway
|
||||
// Row 9-14: lower rooms (Engineering Bay, Quiet Zone, Ops Center)
|
||||
|
||||
export const OFFICE_MAP = [
|
||||
// Row 0: Exterior top wall
|
||||
[EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
|
||||
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
|
||||
// Row 1: Exterior wall top edge
|
||||
[EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
|
||||
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
|
||||
// Row 2: Exterior wall (north wall)
|
||||
[EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
|
||||
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
|
||||
|
||||
// Row 3: Upper rooms - floor with wall separators
|
||||
// BR = Briefing Room (col 0-5), Commons (col 6-13), War Room (col 14-19)
|
||||
[F.WL, F.WL, F.WL, F.WL, F.WL, F.WL, F.CG, F.CG, F.CG, F.CG,
|
||||
F.CG, F.CG, F.CG, F.CG, F.ST, F.ST, F.ST, F.ST, F.ST, F.ST],
|
||||
|
||||
// Row 4: Upper rooms continue
|
||||
[F.WL, F.WL, F.WL2, F.WL, F.WL2, F.WL, F.CG, F.CG2, F.CG, F.CG2,
|
||||
F.CG, F.CG2, F.CG, F.CG, F.ST, F.ST2, F.ST, F.ST2, F.ST, F.ST],
|
||||
|
||||
// Row 5: Upper rooms bottom row
|
||||
[F.WL2, F.WL2, F.WL, F.WL2, F.WL, F.WL2, F.CG2, F.CG, F.CG2, F.CG,
|
||||
F.CG2, F.CG, F.CG2, F.CG, F.ST2, F.ST, F.ST2, F.ST, F.ST2, F.ST],
|
||||
|
||||
// Row 6: HALLWAY (connects all rooms)
|
||||
[F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL,
|
||||
F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL],
|
||||
|
||||
// Row 7: HALLWAY center
|
||||
[F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL,
|
||||
F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL],
|
||||
|
||||
// Row 8: HALLWAY bottom
|
||||
[F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL,
|
||||
F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL],
|
||||
|
||||
// Row 9: Lower rooms
|
||||
// Engineering Bay (col 0-5), Quiet Zone (col 6-13), Ops Center (col 14-19)
|
||||
[F.WW, F.WW, F.WW, F.WW, F.WW, F.WW, F.SD, F.SD, F.SD, F.SD,
|
||||
F.SD, F.SD, F.SD, F.SD, F.GT, F.GT, F.GT, F.GT, F.GT, F.GT],
|
||||
|
||||
// Row 10: Lower rooms
|
||||
[F.WW2, F.WW, F.WW2, F.WW, F.WW2, F.WW, F.SD2, F.SD, F.SD2, F.SD,
|
||||
F.SD2, F.SD, F.SD2, F.SD, F.GT2, F.GT, F.GT2, F.GT, F.GT2, F.GT],
|
||||
|
||||
// Row 11: Lower rooms
|
||||
[F.WW, F.WW2, F.WW, F.WW2, F.WW, F.WW2, F.SD, F.SD2, F.SD, F.SD2,
|
||||
F.SD, F.SD2, F.SD, F.SD2, F.GT, F.GT2, F.GT, F.GT2, F.GT, F.GT2],
|
||||
|
||||
// Row 12: Lower rooms / quiet zone ends
|
||||
[F.WW2, F.WW, F.WW2, F.WW, F.WW2, F.WW, F.SD2, F.SD, F.SD2, F.SD,
|
||||
F.SD2, F.SD, F.SD2, F.SD, F.GT2, F.GT, F.GT2, F.GT, F.GT2, F.GT],
|
||||
|
||||
// Row 13: Lower rooms
|
||||
[F.WW, F.WW2, F.WW, F.WW2, F.WW, F.WW2, EMPTY, EMPTY, EMPTY, EMPTY,
|
||||
EMPTY, EMPTY, EMPTY, EMPTY, F.GT, F.GT2, F.GT, F.GT2, F.GT, F.GT2],
|
||||
|
||||
// Row 14: Bottom row - lower rooms + exterior bottom wall
|
||||
[F.WW2, F.WW, F.WW2, F.WW, F.WW2, F.WW, EMPTY, EMPTY, EMPTY, EMPTY,
|
||||
EMPTY, EMPTY, EMPTY, EMPTY, F.GT2, F.GT, F.GT2, F.GT, F.GT2, F.GT],
|
||||
];
|
||||
|
||||
// ── Wall definitions (drawn on top of floors) ────────────────
|
||||
// Each wall entry: { row, col, tileId }
|
||||
// Walls between rooms at column boundaries and row boundaries
|
||||
export const WALLS = [
|
||||
// Exterior walls (row 0-2)
|
||||
// North wall row
|
||||
...Array.from({ length: 20 }, (_, c) => ({ row: 2, col: c, tile: F.WB2 })),
|
||||
|
||||
// Vertical walls between rooms (wall at column 5 between Briefing/Commons)
|
||||
{ row: 3, col: 5, tile: F.WB },
|
||||
{ row: 4, col: 5, tile: F.WB },
|
||||
// Doorway at row 5, col 5 (hallway level)
|
||||
{ row: 3, col: 13, tile: F.WB },
|
||||
{ row: 4, col: 13, tile: F.WB },
|
||||
// Doorway at row 5, col 13
|
||||
|
||||
// Vertical walls below hallway
|
||||
{ row: 9, col: 5, tile: F.WB },
|
||||
{ row: 10, col: 5, tile: F.WB },
|
||||
{ row: 11, col: 5, tile: F.WB },
|
||||
{ row: 12, col: 5, tile: F.WB },
|
||||
{ row: 13, col: 5, tile: F.WB },
|
||||
{ row: 14, col: 5, tile: F.WB },
|
||||
|
||||
{ row: 9, col: 13, tile: F.WB },
|
||||
{ row: 10, col: 13, tile: F.WB },
|
||||
{ row: 11, col: 13, tile: F.WB },
|
||||
{ row: 12, col: 13, tile: F.WB },
|
||||
{ row: 13, col: 13, tile: F.WB },
|
||||
{ row: 14, col: 13, tile: F.WB },
|
||||
|
||||
// Horizontal wall between upper rooms and hallway (row 5 bottom edge)
|
||||
...Array.from({ length: 6 }, (_, c) => ({ row: 5, col: c, tile: F.WB2 })),
|
||||
...Array.from({ length: 8 }, (_, c) => ({ row: 5, col: c + 6, tile: F.WB2 })),
|
||||
...Array.from({ length: 6 }, (_, c) => ({ row: 5, col: c + 14, tile: F.WB2 })),
|
||||
|
||||
// Horizontal wall between hallway and lower rooms (row 8 bottom edge)
|
||||
...Array.from({ length: 6 }, (_, c) => ({ row: 8, col: c, tile: F.WB2 })),
|
||||
...Array.from({ length: 8 }, (_, c) => ({ row: 8, col: c + 6, tile: F.WB2 })),
|
||||
...Array.from({ length: 6 }, (_, c) => ({ row: 8, col: c + 14, tile: F.WB2 })),
|
||||
|
||||
// Exterior walls (left edge)
|
||||
{ row: 3, col: 0, tile: F.WB },
|
||||
{ row: 4, col: 0, tile: F.WB },
|
||||
{ row: 9, col: 0, tile: F.WB },
|
||||
{ row: 10, col: 0, tile: F.WB },
|
||||
{ row: 11, col: 0, tile: F.WB },
|
||||
{ row: 12, col: 0, tile: F.WB },
|
||||
{ row: 13, col: 0, tile: F.WB },
|
||||
{ row: 14, col: 0, tile: F.WB },
|
||||
|
||||
// Exterior walls (right edge)
|
||||
{ row: 3, col: 19, tile: F.WB },
|
||||
{ row: 4, col: 19, tile: F.WB },
|
||||
{ row: 9, col: 19, tile: F.WB },
|
||||
{ row: 10, col: 19, tile: F.WB },
|
||||
{ row: 11, col: 19, tile: F.WB },
|
||||
{ row: 12, col: 19, tile: F.WB },
|
||||
{ row: 13, col: 19, tile: F.WB },
|
||||
{ row: 14, col: 19, tile: F.WB },
|
||||
|
||||
// Bottom wall (row 14 bottom edge)
|
||||
...Array.from({ length: 6 }, (_, c) => ({ row: 14, col: c, tile: F.WB2 })),
|
||||
...Array.from({ length: 6 }, (_, c) => ({ row: 14, col: c + 14, tile: F.WB2 })),
|
||||
|
||||
// Wall separators at hallway left/right (row 6-8, col 0/19 already exterior)
|
||||
];
|
||||
|
||||
// ── Doorways (openings in walls) ──────────────────────────────
|
||||
// These overwrite wall tiles at specific positions
|
||||
export const DOORWAYS = [
|
||||
// Briefing Room → Hallway (col 2, row 5)
|
||||
{ row: 5, col: 2, tile: F.DO },
|
||||
// Commons → Hallway (col 9, row 5 and row 8)
|
||||
{ row: 5, col: 9, tile: F.DO },
|
||||
{ row: 8, col: 9, tile: F.DO },
|
||||
// War Room → Hallway (col 16, row 5)
|
||||
{ row: 5, col: 16, tile: F.DO },
|
||||
// Engineering Bay → Hallway (col 2, row 8)
|
||||
{ row: 8, col: 2, tile: F.DO },
|
||||
// Quiet Zone → Hallway (col 9, row 8) same as Commons lower
|
||||
// Ops Center → Hallway (col 16, row 8)
|
||||
{ row: 8, col: 16, tile: F.DO },
|
||||
];
|
||||
|
||||
// ── Furniture placements ──────────────────────────────────────
|
||||
// { row, col, tileId }
|
||||
export const FURNITURE = [
|
||||
// === Briefing Room (col 0-5, row 3-5) ===
|
||||
// Round table center
|
||||
{ row: 4, col: 2, tile: F.RG },
|
||||
// Chairs around table
|
||||
{ row: 3, col: 1, tile: F.TB },
|
||||
{ row: 3, col: 3, tile: F.TB },
|
||||
{ row: 5, col: 1, tile: F.TB },
|
||||
{ row: 5, col: 3, tile: F.TB },
|
||||
// Plant in corner
|
||||
{ row: 4, col: 4, tile: F.PL },
|
||||
|
||||
// === Commons (col 6-13, row 3-5) ===
|
||||
// Sofa area
|
||||
{ row: 3, col: 7, tile: F.SF },
|
||||
{ row: 3, col: 8, tile: F.SF },
|
||||
// Coffee table
|
||||
{ row: 4, col: 7, tile: F.RG },
|
||||
{ row: 4, col: 8, tile: F.RG },
|
||||
// Plant
|
||||
{ row: 3, col: 10, tile: F.PL },
|
||||
{ row: 5, col: 7, tile: F.PL },
|
||||
// Lamp
|
||||
{ row: 4, col: 11, tile: F.LP },
|
||||
|
||||
// === War Room (col 14-19, row 3-5) ===
|
||||
// Large table
|
||||
{ row: 4, col: 15, tile: F.RG },
|
||||
{ row: 4, col: 16, tile: F.RG },
|
||||
{ row: 4, col: 17, tile: F.RG },
|
||||
// Chairs around
|
||||
{ row: 3, col: 15, tile: F.TB },
|
||||
{ row: 3, col: 16, tile: F.TB },
|
||||
{ row: 3, col: 17, tile: F.TB },
|
||||
{ row: 5, col: 15, tile: F.TB },
|
||||
{ row: 5, col: 16, tile: F.TB },
|
||||
{ row: 5, col: 17, tile: F.TB },
|
||||
// Whiteboard
|
||||
{ row: 3, col: 18, tile: F.MB },
|
||||
|
||||
// === Engineering Bay (col 0-5, row 9-14) ===
|
||||
// Desks with monitors
|
||||
{ row: 10, col: 1, tile: F.DK },
|
||||
{ row: 10, col: 2, tile: F.MB },
|
||||
{ row: 12, col: 1, tile: F.DK },
|
||||
{ row: 12, col: 2, tile: F.MB },
|
||||
{ row: 10, col: 4, tile: F.DK },
|
||||
{ row: 12, col: 4, tile: F.DK },
|
||||
// Bookshelf
|
||||
{ row: 9, col: 4, tile: F.BS },
|
||||
// Filing cabinet
|
||||
{ row: 9, col: 1, tile: F.FC },
|
||||
// Plant
|
||||
{ row: 13, col: 4, tile: F.PL },
|
||||
|
||||
// === Quiet Zone (col 6-13, row 9-12) ===
|
||||
// Individual desks
|
||||
{ row: 9, col: 7, tile: F.DK },
|
||||
{ row: 9, col: 9, tile: F.DK },
|
||||
{ row: 11, col: 7, tile: F.DK },
|
||||
{ row: 11, col: 9, tile: F.DK },
|
||||
// Bookshelf
|
||||
{ row: 10, col: 12, tile: F.BS },
|
||||
// Lamp
|
||||
{ row: 9, col: 12, tile: F.LP },
|
||||
{ row: 11, col: 12, tile: F.LP },
|
||||
// Plant
|
||||
{ row: 10, col: 6, tile: F.PL },
|
||||
|
||||
// === Ops Center (col 14-19, row 9-14) ===
|
||||
// Desks with monitors
|
||||
{ row: 10, col: 15, tile: F.DK },
|
||||
{ row: 10, col: 16, tile: F.MB },
|
||||
{ row: 12, col: 15, tile: F.DK },
|
||||
{ row: 12, col: 16, tile: F.MB },
|
||||
{ row: 10, col: 18, tile: F.DK },
|
||||
{ row: 12, col: 18, tile: F.DK },
|
||||
// Filing cabinets
|
||||
{ row: 9, col: 15, tile: F.FC },
|
||||
{ row: 9, col: 16, tile: F.FC },
|
||||
// Server rack / cabinet
|
||||
{ row: 9, col: 18, tile: F.CB },
|
||||
{ row: 14, col: 18, tile: F.CB },
|
||||
// Plant
|
||||
{ row: 14, col: 15, tile: F.PL },
|
||||
|
||||
// === Hallway decorations ===
|
||||
{ row: 6, col: 2, tile: F.PL },
|
||||
{ row: 6, col: 10, tile: F.PL },
|
||||
{ row: 7, col: 17, tile: F.PL },
|
||||
{ row: 8, col: 11, tile: F.PL },
|
||||
];
|
||||
BIN
src/frontend/public/agents/Adam_idle.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
src/frontend/public/agents/Adam_run.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/frontend/public/agents/Adam_sit.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
src/frontend/public/agents/Alex_idle.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
src/frontend/public/agents/Alex_run.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/frontend/public/agents/Alex_sit.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
src/frontend/public/agents/Amelia_idle.png
Normal file
|
After Width: | Height: | Size: 1,007 B |
BIN
src/frontend/public/agents/Amelia_run.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/frontend/public/agents/Amelia_sit.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/frontend/public/agents/Bob_idle.png
Normal file
|
After Width: | Height: | Size: 964 B |
BIN
src/frontend/public/agents/Bob_run.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/frontend/public/agents/Bob_sit.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 300 B |
|
Before Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 850 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 698 B |
|
Before Width: | Height: | Size: 359 B |
|
Before Width: | Height: | Size: 351 B |
|
Before Width: | Height: | Size: 628 B |
|
Before Width: | Height: | Size: 435 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 575 B |
|
Before Width: | Height: | Size: 388 B |
|
Before Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |