/** * worldState.js - World game state management * * Tracks player position, current map, dialog, inventory, puzzles, and other game state */ // Default/initial world state export const worldState = { // Current mode mode: 'world', // 'world' | 'workshop' | 'dialog' | 'puzzle' // Player player: { x: 4, y: 10, // tile position in current map px: 0, py: 0, // pixel offset for smooth movement (interpolation) direction: 'down', // 'up' | 'down' | 'left' | 'right' moving: false, frame: 0, // animation frame (0-3 for walking cycles) speed: 150 // milliseconds per tile movement }, // Map currentMap: 'lab', // Camera camera: { x: 0, y: 0 }, // Dialog dialog: null, // { lines: [...], currentLine: 0, speakerName: '' } or null // Inventory of crafted components inventory: [], // array of component IDs from customComponents (stored in circuit editor) // Puzzle state solvedPuzzles: [], // array of puzzleIds that have been solved activePuzzle: null, // { puzzleId, requiredOutputs, doorX, doorY } or null when no puzzle active // Game flags flags: { // Examples: // 'met_professor': false, // 'guard_talked': false, // 'merchant_met': false }, // Timing lastMoveTime: 0, animTimer: 0 }; /** * Reset world state to initial defaults */ export function resetWorldState() { worldState.mode = 'world'; worldState.player.x = 4; worldState.player.y = 10; worldState.player.px = 0; worldState.player.py = 0; worldState.player.direction = 'down'; worldState.player.moving = false; worldState.player.frame = 0; worldState.currentMap = 'lab'; worldState.camera.x = 0; worldState.camera.y = 0; worldState.dialog = null; worldState.inventory = []; worldState.solvedPuzzles = []; worldState.activePuzzle = null; worldState.flags = {}; worldState.lastMoveTime = 0; worldState.animTimer = 0; } /** * Check if player is currently in movement animation */ export function isPlayerMoving() { return worldState.player.moving; } /** * Set player position and reset movement state */ export function setPlayerPosition(x, y) { worldState.player.x = x; worldState.player.y = y; worldState.player.px = 0; worldState.player.py = 0; worldState.player.moving = false; worldState.player.frame = 0; } /** * Start a dialog sequence */ export function startDialog(lines, speakerName = '') { worldState.dialog = { lines: Array.isArray(lines) ? lines : [lines], currentLine: 0, speakerName: speakerName }; worldState.mode = 'dialog'; } /** * Advance dialog to next line * Returns false when dialog sequence ends and should be closed */ export function advanceDialog() { if (!worldState.dialog) return false; worldState.dialog.currentLine++; // Dialog finished if (worldState.dialog.currentLine >= worldState.dialog.lines.length) { worldState.dialog = null; worldState.mode = 'world'; return false; } return true; } /** * Get current dialog line text */ export function getCurrentDialogLine() { if (!worldState.dialog) return ''; return worldState.dialog.lines[worldState.dialog.currentLine] || ''; } /** * Add component to inventory */ export function addToInventory(componentId) { if (!worldState.inventory.includes(componentId)) { worldState.inventory.push(componentId); } } /** * Remove component from inventory */ export function removeFromInventory(componentId) { const idx = worldState.inventory.indexOf(componentId); if (idx !== -1) { worldState.inventory.splice(idx, 1); } } /** * Check if component is in inventory */ export function hasInInventory(componentId) { return worldState.inventory.includes(componentId); } /** * Mark a puzzle as solved */ export function solvePuzzle(puzzleId) { if (!worldState.solvedPuzzles.includes(puzzleId)) { worldState.solvedPuzzles.push(puzzleId); } } /** * Check if a puzzle has been solved */ export function isPuzzleSolved(puzzleId) { return worldState.solvedPuzzles.includes(puzzleId); } /** * Set the active puzzle that player is attempting */ export function setActivePuzzle(puzzleId, requiredOutputs, doorX, doorY) { worldState.activePuzzle = { puzzleId: puzzleId, requiredOutputs: requiredOutputs, doorX: doorX, doorY: doorY }; worldState.mode = 'puzzle'; } /** * Clear the active puzzle */ export function clearActivePuzzle() { worldState.activePuzzle = null; worldState.mode = 'world'; } /** * Get the active puzzle */ export function getActivePuzzle() { return worldState.activePuzzle; } /** * Set a game flag */ export function setFlag(key, value) { worldState.flags[key] = value; } /** * Get a game flag */ export function getFlag(key, defaultValue = false) { return worldState.flags[key] !== undefined ? worldState.flags[key] : defaultValue; } /** * Check if a flag is true */ export function isFlagSet(key) { return getFlag(key) === true; } /** * Move player by tile offset (for movement updates) * Returns true if movement started, false if blocked */ export function movePlayer(dx, dy, isWalkable) { if (worldState.player.moving) return false; const newX = worldState.player.x + dx; const newY = worldState.player.y + dy; // Check if new position is walkable if (!isWalkable(newX, newY)) { return false; } // Update direction if (dx > 0) worldState.player.direction = 'right'; if (dx < 0) worldState.player.direction = 'left'; if (dy > 0) worldState.player.direction = 'down'; if (dy < 0) worldState.player.direction = 'up'; // Start movement animation worldState.player.x = newX; worldState.player.y = newY; worldState.player.moving = true; worldState.player.frame = 0; worldState.lastMoveTime = Date.now(); return true; } /** * Update player movement animation * Call this in game loop, delta is time elapsed in ms */ export function updatePlayerAnimation(delta) { if (!worldState.player.moving) return; const elapsed = Date.now() - worldState.lastMoveTime; const progress = Math.min(elapsed / worldState.player.speed, 1); // Update pixel offset for smooth movement const tileSize = 32; // Assuming 32x32 tiles worldState.player.px = (worldState.player.direction === 'right' ? 1 : worldState.player.direction === 'left' ? -1 : 0) * tileSize * progress; worldState.player.py = (worldState.player.direction === 'down' ? 1 : worldState.player.direction === 'up' ? -1 : 0) * tileSize * progress; // Update animation frame worldState.player.frame = Math.floor(progress * 4) % 4; // Movement complete if (progress >= 1) { worldState.player.moving = false; worldState.player.px = 0; worldState.player.py = 0; worldState.player.frame = 0; } } /** * Warp player to a new map and position */ export function warpToMap(mapId, x, y) { worldState.currentMap = mapId; setPlayerPosition(x, y); } /** * Get complete world state snapshot (for debugging/saving) */ export function getWorldStateSnapshot() { return JSON.parse(JSON.stringify(worldState)); } /** * Load world state from snapshot */ export function loadWorldStateSnapshot(snapshot) { Object.assign(worldState, JSON.parse(JSON.stringify(snapshot))); }