474 lines
13 KiB
JavaScript
474 lines
13 KiB
JavaScript
/**
|
|
* 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: false, // DISABLED - use weather fog instead
|
|
fogColor: 0x000000,
|
|
fogAlpha: 0.15, // Very light animated fog
|
|
exploredAlpha: 0.05, // Almost invisible for explored areas
|
|
visibleRadius: 8, // tiles (increased more)
|
|
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 - ANIMATED FOG EFFECT
|
|
*/
|
|
renderFog() {
|
|
if (!this.fogGraphics) return;
|
|
|
|
this.fogGraphics.clear();
|
|
|
|
// Get camera bounds
|
|
const cam = this.scene.cameras.main;
|
|
const time = this.scene.time.now * 0.0005; // Slow animation
|
|
|
|
// Render fog as smooth animated overlay
|
|
for (let y = 0; y < this.gridHeight; y++) {
|
|
for (let x = 0; x < this.gridWidth; x++) {
|
|
const state = this.fogGrid[y][x];
|
|
|
|
// Skip if visible
|
|
if (state === 2) continue;
|
|
|
|
// Simple 2D tile coordinates
|
|
const screenX = x * this.tileSize;
|
|
const screenY = y * this.tileSize;
|
|
|
|
// Only render if on screen (with margin)
|
|
if (screenX < cam.scrollX - 100 || screenX > cam.scrollX + cam.width + 100) continue;
|
|
if (screenY < cam.scrollY - 100 || screenY > cam.scrollY + cam.height + 100) continue;
|
|
|
|
// Animated fog effect - wave pattern
|
|
const waveX = Math.sin(time + x * 0.1) * 5;
|
|
const waveY = Math.cos(time + y * 0.1) * 5;
|
|
|
|
if (state === 0) {
|
|
// Unexplored - animated fog particles
|
|
const alpha = this.settings.fogAlpha + Math.sin(time + x + y) * 0.05;
|
|
this.fogGraphics.fillStyle(this.settings.fogColor, alpha);
|
|
|
|
// Draw multiple overlapping circles for smooth fog
|
|
this.fogGraphics.fillCircle(
|
|
screenX + this.tileSize / 2 + waveX,
|
|
screenY + this.tileSize / 2 + waveY,
|
|
this.tileSize * 0.8
|
|
);
|
|
} else if (state === 1) {
|
|
// Explored but not visible - very light animated fog
|
|
const alpha = this.settings.exploredAlpha + Math.sin(time + x + y) * 0.02;
|
|
this.fogGraphics.fillStyle(this.settings.fogColor, alpha);
|
|
|
|
// Smaller, lighter circles
|
|
this.fogGraphics.fillCircle(
|
|
screenX + this.tileSize / 2 + waveX,
|
|
screenY + this.tileSize / 2 + waveY,
|
|
this.tileSize * 0.6
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply blur effect for smooth fog
|
|
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');
|
|
}
|
|
}
|