335 lines
10 KiB
JavaScript
335 lines
10 KiB
JavaScript
/**
|
|
* 🗺️ MAP REVEAL SYSTEM
|
|
* Fog of war system that reveals map as player explores
|
|
* - Reveals tiles around player
|
|
* - Persistent (saves discovered areas)
|
|
* - Minimap integration
|
|
* - Full map view (M key)
|
|
*/
|
|
|
|
class MapRevealSystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// Revealed tiles (500x500 grid)
|
|
this.revealed = Array(500).fill(null).map(() => Array(500).fill(false));
|
|
|
|
// Reveal radius around player
|
|
this.revealRadius = 15; // tiles
|
|
|
|
// Map UI elements
|
|
this.mapOpen = false;
|
|
this.mapContainer = null;
|
|
|
|
// Minimap
|
|
this.minimap = null;
|
|
this.minimapSize = 200;
|
|
this.minimapScale = 2; // pixels per tile on minimap
|
|
|
|
console.log('🗺️ MapRevealSystem initialized');
|
|
}
|
|
|
|
// Reveal tiles around player
|
|
revealArea(playerX, playerY) {
|
|
const gridX = Math.floor(playerX);
|
|
const gridY = Math.floor(playerY);
|
|
|
|
let newTilesRevealed = 0;
|
|
|
|
for (let dx = -this.revealRadius; dx <= this.revealRadius; dx++) {
|
|
for (let dy = -this.revealRadius; dy <= this.revealRadius; dy++) {
|
|
const x = gridX + dx;
|
|
const y = gridY + dy;
|
|
|
|
// Check if within world bounds
|
|
if (x < 0 || x >= 500 || y < 0 || y >= 500) continue;
|
|
|
|
// Check if within circular radius
|
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
if (dist > this.revealRadius) continue;
|
|
|
|
// Reveal tile
|
|
if (!this.revealed[y][x]) {
|
|
this.revealed[y][x] = true;
|
|
newTilesRevealed++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newTilesRevealed > 0) {
|
|
// Update minimap
|
|
this.updateMinimap();
|
|
}
|
|
}
|
|
|
|
// Create minimap (bottom-right corner)
|
|
createMinimap() {
|
|
if (this.minimap) return;
|
|
|
|
const size = this.minimapSize;
|
|
const x = this.scene.cameras.main.width - size - 20;
|
|
const y = this.scene.cameras.main.height - size - 20;
|
|
|
|
// Background
|
|
this.minimapBg = this.scene.add.rectangle(x, y, size, size, 0x000000, 0.7);
|
|
this.minimapBg.setOrigin(0);
|
|
this.minimapBg.setScrollFactor(0);
|
|
this.minimapBg.setDepth(9000);
|
|
|
|
// Border
|
|
this.minimapBorder = this.scene.add.rectangle(x, y, size, size);
|
|
this.minimapBorder.setOrigin(0);
|
|
this.minimapBorder.setStrokeStyle(2, 0xFFFFFF);
|
|
this.minimapBorder.setScrollFactor(0);
|
|
this.minimapBorder.setDepth(9001);
|
|
|
|
// Canvas for map rendering
|
|
this.minimapTexture = this.scene.add.renderTexture(x, y, size, size);
|
|
this.minimapTexture.setOrigin(0);
|
|
this.minimapTexture.setScrollFactor(0);
|
|
this.minimapTexture.setDepth(9002);
|
|
|
|
// Label
|
|
this.minimapLabel = this.scene.add.text(x + size / 2, y - 15, 'Map (M)', {
|
|
fontSize: '14px',
|
|
color: '#ffffff',
|
|
backgroundColor: '#000000',
|
|
padding: { x: 5, y: 2 }
|
|
});
|
|
this.minimapLabel.setOrigin(0.5);
|
|
this.minimapLabel.setScrollFactor(0);
|
|
this.minimapLabel.setDepth(9003);
|
|
|
|
this.minimap = {
|
|
bg: this.minimapBg,
|
|
border: this.minimapBorder,
|
|
texture: this.minimapTexture,
|
|
label: this.minimapLabel,
|
|
x, y, size
|
|
};
|
|
|
|
this.updateMinimap();
|
|
}
|
|
|
|
// Update minimap rendering
|
|
updateMinimap() {
|
|
if (!this.minimap) return;
|
|
|
|
const player = this.scene.player;
|
|
if (!player) return;
|
|
|
|
const playerX = Math.floor(player.gridX);
|
|
const playerY = Math.floor(player.gridY);
|
|
|
|
// Clear
|
|
this.minimap.texture.clear();
|
|
|
|
// Calculate view area (centered on player)
|
|
const viewSize = Math.floor(this.minimap.size / this.minimapScale);
|
|
const startX = Math.max(0, playerX - viewSize / 2);
|
|
const startY = Math.max(0, playerY - viewSize / 2);
|
|
const endX = Math.min(500, startX + viewSize);
|
|
const endY = Math.min(500, startY + viewSize);
|
|
|
|
// Draw revealed tiles
|
|
for (let y = startY; y < endY; y++) {
|
|
for (let x = startX; x < endX; x++) {
|
|
if (!this.revealed[y] || !this.revealed[y][x]) continue;
|
|
|
|
const screenX = (x - startX) * this.minimapScale;
|
|
const screenY = (y - startY) * this.minimapScale;
|
|
|
|
// Get biome color
|
|
const biome = this.scene.biomeSystem ? this.scene.biomeSystem.getBiomeAt(x, y) : 'Grassland';
|
|
const color = this.getBiomeMinimapColor(biome);
|
|
|
|
this.minimap.texture.fill(color, 1, screenX, screenY, this.minimapScale, this.minimapScale);
|
|
}
|
|
}
|
|
|
|
// Draw player position
|
|
const playerScreenX = (playerX - startX) * this.minimapScale;
|
|
const playerScreenY = (playerY - startY) * this.minimapScale;
|
|
this.minimap.texture.fill(0xFFFF00, 1, playerScreenX - 2, playerScreenY - 2, 4, 4);
|
|
}
|
|
|
|
// Get minimap color for biome
|
|
getBiomeMinimapColor(biome) {
|
|
const colors = {
|
|
'Grassland': 0x3CB371,
|
|
'Forest': 0x2d5016,
|
|
'Desert': 0xd4c4a1,
|
|
'Mountain': 0x808080,
|
|
'Swamp': 0x3d5a3d
|
|
};
|
|
return colors[biome] || 0x3CB371;
|
|
}
|
|
|
|
// Toggle full map view (M key)
|
|
toggleFullMap() {
|
|
if (this.mapOpen) {
|
|
this.closeFullMap();
|
|
} else {
|
|
this.openFullMap();
|
|
}
|
|
}
|
|
|
|
// Open full map
|
|
openFullMap() {
|
|
if (this.mapOpen) return;
|
|
|
|
this.mapOpen = true;
|
|
|
|
// Semi-transparent background
|
|
this.fullMapBg = this.scene.add.rectangle(
|
|
this.scene.cameras.main.centerX,
|
|
this.scene.cameras.main.centerY,
|
|
this.scene.cameras.main.width,
|
|
this.scene.cameras.main.height,
|
|
0x000000,
|
|
0.9
|
|
);
|
|
this.fullMapBg.setScrollFactor(0);
|
|
this.fullMapBg.setDepth(15000);
|
|
|
|
// Map title
|
|
this.fullMapTitle = this.scene.add.text(
|
|
this.scene.cameras.main.centerX,
|
|
50,
|
|
'World Map (Press M to close)',
|
|
{
|
|
fontSize: '32px',
|
|
color: '#FFD700',
|
|
backgroundColor: '#000000',
|
|
padding: { x: 20, y: 10 }
|
|
}
|
|
);
|
|
this.fullMapTitle.setOrigin(0.5);
|
|
this.fullMapTitle.setScrollFactor(0);
|
|
this.fullMapTitle.setDepth(15001);
|
|
|
|
// Render full map
|
|
const mapSize = 600;
|
|
const mapX = this.scene.cameras.main.centerX - mapSize / 2;
|
|
const mapY = this.scene.cameras.main.centerY - mapSize / 2;
|
|
|
|
this.fullMapTexture = this.scene.add.renderTexture(mapX, mapY, mapSize, mapSize);
|
|
this.fullMapTexture.setScrollFactor(0);
|
|
this.fullMapTexture.setDepth(15002);
|
|
|
|
// Draw entire world
|
|
const scale = mapSize / 500;
|
|
for (let y = 0; y < 500; y += 5) {
|
|
for (let x = 0; x < 500; x += 5) {
|
|
if (!this.revealed[y] || !this.revealed[y][x]) continue;
|
|
|
|
const biome = this.scene.biomeSystem ? this.scene.biomeSystem.getBiomeAt(x, y) : 'Grassland';
|
|
const color = this.getBiomeMinimapColor(biome);
|
|
|
|
this.fullMapTexture.fill(color, 1, x * scale, y * scale, scale * 5, scale * 5);
|
|
}
|
|
}
|
|
|
|
// Player position
|
|
if (this.scene.player) {
|
|
const px = Math.floor(this.scene.player.gridX) * scale;
|
|
const py = Math.floor(this.scene.player.gridY) * scale;
|
|
this.fullMapTexture.fill(0xFFFF00, 1, px - 3, py - 3, 6, 6);
|
|
}
|
|
|
|
// Stats
|
|
const exploredTiles = this.revealed.flat().filter(r => r).length;
|
|
const totalTiles = 500 * 500;
|
|
const percent = ((exploredTiles / totalTiles) * 100).toFixed(1);
|
|
|
|
this.fullMapStats = this.scene.add.text(
|
|
this.scene.cameras.main.centerX,
|
|
mapY + mapSize + 30,
|
|
`Explored: ${exploredTiles} / ${totalTiles} tiles (${percent}%)`,
|
|
{
|
|
fontSize: '20px',
|
|
color: '#ffffff',
|
|
backgroundColor: '#000000',
|
|
padding: { x: 15, y: 8 }
|
|
}
|
|
);
|
|
this.fullMapStats.setOrigin(0.5);
|
|
this.fullMapStats.setScrollFactor(0);
|
|
this.fullMapStats.setDepth(15003);
|
|
}
|
|
|
|
// Close full map
|
|
closeFullMap() {
|
|
if (!this.mapOpen) return;
|
|
|
|
this.mapOpen = false;
|
|
|
|
if (this.fullMapBg) {
|
|
this.fullMapBg.destroy();
|
|
this.fullMapTitle.destroy();
|
|
this.fullMapTexture.destroy();
|
|
this.fullMapStats.destroy();
|
|
}
|
|
}
|
|
|
|
// Update (called every frame)
|
|
update() {
|
|
if (this.scene.player) {
|
|
this.revealArea(this.scene.player.gridX, this.scene.player.gridY);
|
|
}
|
|
}
|
|
|
|
// Get exploration statistics
|
|
getStats() {
|
|
const exploredTiles = this.revealed.flat().filter(r => r).length;
|
|
const totalTiles = 500 * 500;
|
|
const percent = ((exploredTiles / totalTiles) * 100).toFixed(2);
|
|
|
|
return {
|
|
explored: exploredTiles,
|
|
total: totalTiles,
|
|
percent: parseFloat(percent)
|
|
};
|
|
}
|
|
|
|
// Export/Import for save system
|
|
exportData() {
|
|
// Convert 2D array to compressed format
|
|
const compressed = [];
|
|
for (let y = 0; y < 500; y++) {
|
|
for (let x = 0; x < 500; x++) {
|
|
if (this.revealed[y][x]) {
|
|
compressed.push(`${x},${y}`);
|
|
}
|
|
}
|
|
}
|
|
return { revealed: compressed };
|
|
}
|
|
|
|
importData(data) {
|
|
if (!data || !data.revealed) return;
|
|
|
|
// Clear current
|
|
this.revealed = Array(500).fill(null).map(() => Array(500).fill(false));
|
|
|
|
// Decompress
|
|
for (const key of data.revealed) {
|
|
const [x, y] = key.split(',').map(Number);
|
|
if (x >= 0 && x < 500 && y >= 0 && y < 500) {
|
|
this.revealed[y][x] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
this.closeFullMap();
|
|
if (this.minimap) {
|
|
this.minimap.bg.destroy();
|
|
this.minimap.border.destroy();
|
|
this.minimap.texture.destroy();
|
|
this.minimap.label.destroy();
|
|
this.minimap = null;
|
|
}
|
|
}
|
|
}
|