/** * FOG OF WAR SYSTEM * Exploration and visibility system for unexplored areas */ class FogOfWarSystem { constructor(scene) { this.scene = scene; this.enabled = true; // Fog grid (matches terrain grid) this.gridWidth = 100; this.gridHeight = 100; this.tileSize = 64; // Fog states: 0 = unexplored, 1 = explored, 2 = visible this.fogGrid = []; this.fogSprites = new Map(); // Settings this.settings = { enabled: true, fogColor: 0x000000, fogAlpha: 0.8, exploredAlpha: 0.3, visibleRadius: 5, // tiles smoothEdges: true, persistMemory: true, refogDungeons: true }; // Fog layer this.fogLayer = null; this.fogGraphics = null; // Memory of explored areas this.exploredAreas = new Set(); this.loadSettings(); this.init(); console.log('✅ Fog of War System initialized'); } init() { if (!this.settings.enabled) return; // Initialize fog grid this.initFogGrid(); // Create fog layer this.createFogLayer(); // Load explored areas from memory this.loadExploredAreas(); console.log('🌫️ Fog of War active'); } /** * Initialize fog grid */ initFogGrid() { for (let y = 0; y < this.gridHeight; y++) { this.fogGrid[y] = []; for (let x = 0; x < this.gridWidth; x++) { this.fogGrid[y][x] = 0; // Unexplored } } } /** * Create fog layer */ createFogLayer() { // Create layer for fog this.fogLayer = this.scene.add.layer(); this.fogLayer.setDepth(9000); // Above game, below UI // Create fog graphics this.fogGraphics = this.scene.add.graphics(); this.fogGraphics.setDepth(9001); // Initial full fog this.renderFullFog(); } /** * Render full fog (all unexplored) */ renderFullFog() { if (!this.fogGraphics) return; this.fogGraphics.clear(); this.fogGraphics.fillStyle(this.settings.fogColor, this.settings.fogAlpha); // Cover entire map const width = this.scene.cameras.main.width; const height = this.scene.cameras.main.height; this.fogGraphics.fillRect( -width, -height, width * 3, height * 3 ); } /** * Update fog based on player position */ updateFog(playerX, playerY) { if (!this.settings.enabled) return; const gridX = Math.floor(playerX); const gridY = Math.floor(playerY); // Reveal area around player this.revealArea(gridX, gridY, this.settings.visibleRadius); // Update fog rendering this.renderFog(); } /** * Reveal area around a point */ revealArea(centerX, centerY, radius) { const radiusSq = radius * radius; for (let y = centerY - radius; y <= centerY + radius; y++) { for (let x = centerX - radius; x <= centerX + radius; x++) { // Check if within grid bounds if (x < 0 || x >= this.gridWidth || y < 0 || y >= this.gridHeight) { continue; } // Check if within circular radius const dx = x - centerX; const dy = y - centerY; const distSq = dx * dx + dy * dy; if (distSq <= radiusSq) { // Mark as explored if (this.fogGrid[y][x] === 0) { this.fogGrid[y][x] = 1; // Explored this.exploredAreas.add(`${x},${y}`); } // Mark as currently visible this.fogGrid[y][x] = 2; // Visible } } } // Save explored areas if (this.settings.persistMemory) { this.saveExploredAreas(); } } /** * Render fog based on current state */ renderFog() { if (!this.fogGraphics) return; this.fogGraphics.clear(); // Get camera bounds const cam = this.scene.cameras.main; const offsetX = this.scene.terrainOffsetX || 0; const offsetY = this.scene.terrainOffsetY || 0; // Render fog tiles for (let y = 0; y < this.gridHeight; y++) { for (let x = 0; x < this.gridWidth; x++) { const state = this.fogGrid[y][x]; // Convert grid to screen coordinates const iso = this.scene.iso || { toScreen: (x, y) => ({ x: x * 32, y: y * 32 }) }; const screenPos = iso.toScreen(x, y); const screenX = screenPos.x + offsetX; const screenY = screenPos.y + offsetY; // Only render if on screen if (screenX < cam.scrollX - 100 || screenX > cam.scrollX + cam.width + 100) continue; if (screenY < cam.scrollY - 100 || screenY > cam.scrollY + cam.height + 100) continue; if (state === 0) { // Unexplored - full fog this.fogGraphics.fillStyle(this.settings.fogColor, this.settings.fogAlpha); this.fogGraphics.fillRect(screenX - 32, screenY - 16, 64, 32); } else if (state === 1) { // Explored but not visible - light fog this.fogGraphics.fillStyle(this.settings.fogColor, this.settings.exploredAlpha); this.fogGraphics.fillRect(screenX - 32, screenY - 16, 64, 32); } // state === 2 (visible) - no fog } } // Smooth edges if enabled if (this.settings.smoothEdges) { this.fogGraphics.setBlendMode(Phaser.BlendModes.NORMAL); } } /** * Reset fog in area (for dungeons/caves) */ refogArea(x, y, width, height) { if (!this.settings.refogDungeons) return; for (let gy = y; gy < y + height; gy++) { for (let gx = x; gx < x + width; gx++) { if (gx >= 0 && gx < this.gridWidth && gy >= 0 && gy < this.gridHeight) { this.fogGrid[gy][gx] = 0; // Back to unexplored this.exploredAreas.delete(`${gx},${gy}`); } } } this.renderFog(); } /** * Clear all fog (reveal entire map) */ revealAll() { for (let y = 0; y < this.gridHeight; y++) { for (let x = 0; x < this.gridWidth; x++) { this.fogGrid[y][x] = 2; // All visible this.exploredAreas.add(`${x},${y}`); } } this.renderFog(); } /** * Reset all fog (hide entire map) */ resetAll() { for (let y = 0; y < this.gridHeight; y++) { for (let x = 0; x < this.gridWidth; x++) { this.fogGrid[y][x] = 0; // All unexplored } } this.exploredAreas.clear(); this.renderFullFog(); } /** * Check if area is explored */ isExplored(x, y) { if (x < 0 || x >= this.gridWidth || y < 0 || y >= this.gridHeight) { return false; } return this.fogGrid[y][x] > 0; } /** * Check if area is visible */ isVisible(x, y) { if (x < 0 || x >= this.gridWidth || y < 0 || y >= this.gridHeight) { return false; } return this.fogGrid[y][x] === 2; } /** * Get exploration percentage */ getExplorationPercentage() { let explored = 0; const total = this.gridWidth * this.gridHeight; for (let y = 0; y < this.gridHeight; y++) { for (let x = 0; x < this.gridWidth; x++) { if (this.fogGrid[y][x] > 0) { explored++; } } } return (explored / total) * 100; } /** * Enable fog of war */ enable() { this.settings.enabled = true; if (!this.fogLayer) { this.createFogLayer(); } this.fogLayer.setVisible(true); this.saveSettings(); } /** * Disable fog of war */ disable() { this.settings.enabled = false; if (this.fogLayer) { this.fogLayer.setVisible(false); } this.saveSettings(); } /** * Set visible radius */ setVisibleRadius(radius) { this.settings.visibleRadius = Math.max(1, Math.min(20, radius)); this.saveSettings(); } /** * Set fog color */ setFogColor(color) { this.settings.fogColor = color; this.renderFog(); this.saveSettings(); } /** * Set fog alpha */ setFogAlpha(alpha) { this.settings.fogAlpha = Phaser.Math.Clamp(alpha, 0, 1); this.renderFog(); this.saveSettings(); } /** * Set explored alpha */ setExploredAlpha(alpha) { this.settings.exploredAlpha = Phaser.Math.Clamp(alpha, 0, 1); this.renderFog(); this.saveSettings(); } /** * Update (called every frame) */ update() { if (!this.settings.enabled) return; // Update fog based on player position if (this.scene.player) { const pos = this.scene.player.getPosition(); this.updateFog(pos.x, pos.y); } // Fade out visible areas that are no longer in range this.fadeDistantAreas(); } /** * Fade distant areas back to explored state */ fadeDistantAreas() { if (!this.scene.player) return; const pos = this.scene.player.getPosition(); const playerX = Math.floor(pos.x); const playerY = Math.floor(pos.y); const radius = this.settings.visibleRadius; const radiusSq = radius * radius; for (let y = 0; y < this.gridHeight; y++) { for (let x = 0; x < this.gridWidth; x++) { if (this.fogGrid[y][x] === 2) { // Check if still in visible range const dx = x - playerX; const dy = y - playerY; const distSq = dx * dx + dy * dy; if (distSq > radiusSq) { // Fade back to explored this.fogGrid[y][x] = 1; } } } } } /** * Save explored areas to localStorage */ saveExploredAreas() { if (!this.settings.persistMemory) return; const data = Array.from(this.exploredAreas); localStorage.setItem('novafarma_explored_areas', JSON.stringify(data)); } /** * Load explored areas from localStorage */ loadExploredAreas() { if (!this.settings.persistMemory) return; const saved = localStorage.getItem('novafarma_explored_areas'); if (saved) { try { const data = JSON.parse(saved); this.exploredAreas = new Set(data); // Apply to fog grid for (const key of this.exploredAreas) { const [x, y] = key.split(',').map(Number); if (x >= 0 && x < this.gridWidth && y >= 0 && y < this.gridHeight) { this.fogGrid[y][x] = 1; // Explored } } console.log(`🗺️ Loaded ${this.exploredAreas.size} explored tiles`); } catch (error) { console.error('Failed to load explored areas:', error); } } } /** * Save settings */ saveSettings() { localStorage.setItem('novafarma_fog_of_war', JSON.stringify(this.settings)); } /** * Load settings */ loadSettings() { const saved = localStorage.getItem('novafarma_fog_of_war'); if (saved) { this.settings = { ...this.settings, ...JSON.parse(saved) }; } } /** * Destroy system */ destroy() { if (this.fogLayer) this.fogLayer.destroy(); if (this.fogGraphics) this.fogGraphics.destroy(); this.saveExploredAreas(); console.log('🌫️ Fog of War System destroyed'); } }