1591 lines
60 KiB
JavaScript
1591 lines
60 KiB
JavaScript
// Flat2DTerrainSystem - Complete 2D top-down tile rendering
|
|
// Replaces isometric TerrainSystem for Stardew Valley style
|
|
|
|
class Flat2DTerrainSystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
this.tileSize = 48;
|
|
this.width = 500; // 🌍 PHASE 28: Expanded to 500x500!
|
|
this.height = 500;
|
|
|
|
// Tile map data
|
|
this.tiles = [];
|
|
|
|
// Rendering containers
|
|
this.groundLayer = null;
|
|
this.pathsLayer = null;
|
|
this.decorLayer = null;
|
|
|
|
// Decoration tracking (for interaction system)
|
|
this.decorationsMap = new Map();
|
|
|
|
// Textures ready flag
|
|
this.texturesReady = false;
|
|
|
|
// 🌍 PHASE 28: Biome support
|
|
this.biomeSystem = null; // Will be set by GameScene
|
|
this.chunkManager = null; // Will be set by GameScene
|
|
this.chunkSize = 50; // Chunk size for rendering (matches ChunkManager)
|
|
|
|
// 🏛️ PHASE 28 SESSION 6: Structure support
|
|
this.structureSystem = null; // Will be set by GameScene
|
|
this.riverSystem = null; // Will be set by GameScene
|
|
this.lakeSystem = null; // Will be set by GameScene
|
|
|
|
// Grid graphics
|
|
this.gridGraphics = null;
|
|
this.gridVisible = true; // Enabled by default for Tiled mapping
|
|
|
|
console.log('🎨 Flat2DTerrainSystem initialized (500x500 world)');
|
|
}
|
|
|
|
async generate() {
|
|
console.log('🗺️ Generating flat 2D map...');
|
|
|
|
// Create textures first
|
|
this.createTileTextures();
|
|
|
|
// 🌍 PHASE 28: Use BiomeSystem if available
|
|
if (this.biomeSystem && this.chunkManager) {
|
|
console.log('🌍 Using BiomeSystem for chunk-based terrain generation');
|
|
// Biome-based generation will happen via ChunkManager
|
|
// No need to generate full map here!
|
|
|
|
// Create simple grass background as fallback
|
|
this.createBiomeBackground();
|
|
} else {
|
|
// Fallback to old Map2DData system
|
|
console.log('📊 Using Map2DData for terrain generation (fallback)');
|
|
|
|
// Load map data
|
|
if (typeof Map2DData !== 'undefined') {
|
|
this.tiles = Map2DData.generateMap();
|
|
console.log('✅ Map data generated:', this.tiles.length, 'rows');
|
|
} else {
|
|
console.error('❌ Map2DData not loaded!');
|
|
this.createFallbackMap();
|
|
}
|
|
|
|
// Render the old-style map
|
|
this.renderMap();
|
|
}
|
|
|
|
console.log('✅ Flat 2D map ready!');
|
|
}
|
|
|
|
// 🗺️ Load map directly from Tiled JSON (User Created)
|
|
loadFromTiled(map) {
|
|
this.tiledMap = map;
|
|
console.log('🗺️ Loading terrain from Tiled map:', map.key);
|
|
|
|
// Update system dimensions
|
|
this.width = map.width;
|
|
this.height = map.height;
|
|
this.tileSize = map.tileWidth;
|
|
|
|
// Prepare tilesets array
|
|
const tilesets = [];
|
|
|
|
// Helper to map tileset names to Phaser keys (similar to TiledTestScene)
|
|
const getTilesetKey = (name) => {
|
|
const mapping = {
|
|
'grass': 'tileset_grass',
|
|
'dirt': 'tileset_dirt',
|
|
'water': 'tileset_water',
|
|
'decorations': 'tileset_decorations',
|
|
'01_Ground': 'tileset_01_Ground',
|
|
'02_Obstacles': 'tileset_02_Obstacles',
|
|
'03_Fences': 'tileset_03_Fences',
|
|
'04_Buildings': 'tileset_04_Buildings',
|
|
'05_Tools_Items': 'tileset_05_Tools_Items',
|
|
'camp_objects': 'tileset_camp_objects',
|
|
'starting_camp': 'tileset_starting_camp',
|
|
'starting_camp_topdown': 'tileset_starting_camp',
|
|
'ground_tiles': 'ground_tiles',
|
|
'objects_pack': 'objects_pack',
|
|
'objects_pack2': 'objects_pack2',
|
|
'walls_pack': 'walls_pack',
|
|
'trees_vegetation': 'trees_vegetation',
|
|
'tree_blue': 'tree_blue',
|
|
'tree_green': 'tree_green',
|
|
'tree_dead': 'tree_dead',
|
|
'flowers': 'flowers',
|
|
'NovaFarmaTiles': 'ground_tiles'
|
|
};
|
|
const key = mapping[name] || mapping[name.toLowerCase()] || `tileset_${name}`;
|
|
console.log(`🔍 Mapping tileset: "${name}" -> Suggested key: "${key}"`);
|
|
return key;
|
|
};
|
|
|
|
// Load tilesets
|
|
map.tilesets.forEach(tilesetData => {
|
|
let textureKey = getTilesetKey(tilesetData.name);
|
|
|
|
// 🕵️ Try to find texture (Prefix matched OR Exact name)
|
|
if (!this.scene.textures.exists(textureKey)) {
|
|
// Fallback: Try the raw tileset name as key
|
|
if (this.scene.textures.exists(tilesetData.name)) {
|
|
textureKey = tilesetData.name;
|
|
}
|
|
}
|
|
|
|
if (this.scene.textures.exists(textureKey)) {
|
|
try {
|
|
const tileset = map.addTilesetImage(tilesetData.name, textureKey);
|
|
if (tileset) {
|
|
tilesets.push(tileset);
|
|
console.log(` ✅ Tileset mapped: "${tilesetData.name}" -> Key: "${textureKey}"`);
|
|
} else {
|
|
console.warn(` ⚠️ Phaser returned null for tileset: "${tilesetData.name}"`);
|
|
}
|
|
} catch (e) {
|
|
console.error(` ❌ Failed to add tileset: ${tilesetData.name}`, e);
|
|
}
|
|
} else {
|
|
console.warn(` ⚠️ Texture not found for tileset: "${tilesetData.name}". Tried keys: "${textureKey}"`);
|
|
// Fallback: try to load it now? Or use a placeholder?
|
|
// If we don't add the tileset, GIDs might be confused if this tileset is used.
|
|
// Try adding with 'tileset_Terrain_Grass' as emergency fallback just to see geometry
|
|
/*
|
|
const fallbackTileset = map.addTilesetImage(tilesetData.name, 'tileset_Terrain_Grass');
|
|
if (fallbackTileset) tilesets.push(fallbackTileset);
|
|
*/
|
|
}
|
|
});
|
|
|
|
// DEBUG: Check GIDs
|
|
if (tilesets.length > 0) {
|
|
console.log(' 🔍 Loaded Tilesets:', tilesets.map(t => ({ name: t.name, firstgid: t.firstgid })));
|
|
}
|
|
|
|
// Create layers
|
|
console.log(` Found ${map.layers.length} layers in map.`);
|
|
map.layers.forEach((layerData, index) => {
|
|
console.log(` Processing Layer ${index}: Name="${layerData.name}", Type="${layerData.type}", Visible=${layerData.visible}`);
|
|
|
|
// Phaser 3.50+ puts tile layers in map.layers. They might not have 'type' property set explicitly to 'tilelayer'.
|
|
// If it's in map.layers, it IS a tile layer.
|
|
if (layerData.visible) {
|
|
// if (layerData.visible && (layerData.type === 'tilelayer' || layerData.type === undefined)) {
|
|
const layer = map.createLayer(layerData.name, tilesets, 0, 0);
|
|
if (layer) {
|
|
console.log(` ✅ Layer created: ${layerData.name}`);
|
|
layer.setAlpha(1);
|
|
layer.setVisible(true);
|
|
|
|
// Depth sorting
|
|
const lowerName = layerData.name.toLowerCase();
|
|
if (lowerName.includes('ground')) {
|
|
layer.setDepth(1);
|
|
this.groundLayer = layer;
|
|
}
|
|
else if (lowerName.includes('path')) {
|
|
layer.setDepth(2);
|
|
this.pathsLayer = layer;
|
|
}
|
|
else if (lowerName.includes('decor') || lowerName.includes('obstacle') || lowerName.includes('object')) {
|
|
// Y-SORTED DEPTH for proper player sorting!
|
|
// Uses same layer as Player (200000 + Y)
|
|
layer.setDepth(200000); // Base depth, tiles will sort by Y
|
|
this.decorLayer = layer;
|
|
}
|
|
else if (lowerName.includes('building') || lowerName.includes('structure') || lowerName.includes('fence')) {
|
|
layer.setDepth(200000); // Also Y-sorted
|
|
}
|
|
else {
|
|
layer.setDepth(2);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 🗺️ Populate this.tiles from map data (for Pathfinding, Farming, etc.)
|
|
// We use the 'Ground' layer as the base grid
|
|
const groundLayerData = map.getLayer('Ground') || map.layers[0];
|
|
if (groundLayerData && groundLayerData.data && Array.isArray(groundLayerData.data)) {
|
|
console.log('📊 Populating terrain tiles grid from Tiled data...');
|
|
this.tiles = [];
|
|
for (let y = 0; y < map.height; y++) {
|
|
this.tiles[y] = [];
|
|
for (let x = 0; x < map.width; x++) {
|
|
// Safety check: ensure row exists
|
|
const tile = (groundLayerData.data[y] && groundLayerData.data[y][x]) || null;
|
|
this.tiles[y][x] = {
|
|
x: x,
|
|
y: y,
|
|
type: (tile && tile.index !== -1) ? 'grass' : 'void',
|
|
index: tile ? tile.index : -1
|
|
};
|
|
}
|
|
}
|
|
console.log(`✅ Tiles grid populated: ${this.width}x${this.height}`);
|
|
}
|
|
|
|
// Disable procedural aspects
|
|
if (this.scene.cameras && this.scene.cameras.main) {
|
|
// Remove gray background so Tiled map is clear
|
|
this.scene.cameras.main.setBackgroundColor('#000000');
|
|
}
|
|
|
|
this.isGenerated = true;
|
|
|
|
// Setup camera bounds
|
|
this.scene.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
|
|
this.scene.cameras.main.centerOn(map.widthInPixels / 2, map.heightInPixels / 2);
|
|
|
|
console.log('✅ Tiled map loaded and rendered!');
|
|
|
|
// Create initial grid (hidden by default)
|
|
this.createGrid();
|
|
}
|
|
|
|
// 🕸️ Create a visual grid helper
|
|
createGrid() {
|
|
if (this.gridGraphics) {
|
|
this.gridGraphics.destroy();
|
|
}
|
|
|
|
this.gridGraphics = this.scene.add.graphics();
|
|
this.gridGraphics.lineStyle(1, 0xFFFFFF, 0.2); // Subtle white line
|
|
|
|
const mapWidthInPixels = this.width * this.tileSize;
|
|
const mapHeightInPixels = this.height * this.tileSize;
|
|
|
|
// Draw vertical lines
|
|
for (let x = 0; x <= this.width; x++) {
|
|
this.gridGraphics.moveTo(x * this.tileSize, 0);
|
|
this.gridGraphics.lineTo(x * this.tileSize, mapHeightInPixels);
|
|
}
|
|
|
|
// Draw horizontal lines
|
|
for (let y = 0; y <= this.height; y++) {
|
|
this.gridGraphics.moveTo(0, y * this.tileSize);
|
|
this.gridGraphics.lineTo(mapWidthInPixels, y * this.tileSize);
|
|
}
|
|
|
|
this.gridGraphics.strokePath();
|
|
this.gridGraphics.setDepth(100); // High depth to stay on top
|
|
this.gridGraphics.setVisible(this.gridVisible);
|
|
|
|
console.log('🕸️ Visual grid created!');
|
|
}
|
|
|
|
toggleGrid() {
|
|
this.gridVisible = !this.gridVisible;
|
|
if (this.gridGraphics) {
|
|
this.gridGraphics.setVisible(this.gridVisible);
|
|
}
|
|
console.log(`🕸️ Grid visibility: ${this.gridVisible}`);
|
|
}
|
|
|
|
// 🌍 PHASE 28: Create simple biome-aware background
|
|
createBiomeBackground() {
|
|
const size = this.tileSize;
|
|
const mapWidth = this.width * size;
|
|
const mapHeight = this.height * size;
|
|
|
|
console.log('🎨 Creating biome-aware background...');
|
|
|
|
// Create solid background (will be covered by chunks)
|
|
const background = this.scene.add.rectangle(0, 0, mapWidth, mapHeight, 0x3CB371);
|
|
background.setOrigin(0, 0);
|
|
background.setDepth(0);
|
|
|
|
// Create containers for chunks
|
|
this.pathsLayer = this.scene.add.container(0, 0);
|
|
this.pathsLayer.setDepth(2);
|
|
|
|
this.decorLayer = this.scene.add.container(0, 0);
|
|
this.decorLayer.setDepth(3);
|
|
|
|
this.groundLayer = background;
|
|
|
|
console.log('✅ Biome background created, ready for chunks');
|
|
}
|
|
|
|
createTileTextures() {
|
|
// 🎨 Create SOLID, OPAQUE tiles (no transparency!)
|
|
|
|
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
|
|
const size = this.tileSize;
|
|
|
|
// Check if PNG tilesets are loaded
|
|
const hasGrass = this.scene.textures.exists('tileset_grass');
|
|
const hasWater = this.scene.textures.exists('tileset_water');
|
|
const hasDirt = this.scene.textures.exists('tileset_dirt');
|
|
|
|
if (hasGrass && hasWater && hasDirt) {
|
|
console.log('✅ PNG Tilesets found! Creating beautiful tiles...');
|
|
|
|
// 🌍 PHASE 28: Create BIOME-SPECIFIC GRASS TILES
|
|
|
|
// GRASSLAND - SUPER VIBRANT GREEN! 🌿
|
|
graphics.clear();
|
|
graphics.fillStyle(0x3CB371, 1.0); // Medium sea green
|
|
graphics.fillRect(0, 0, size, size);
|
|
|
|
// Darker spots for contrast
|
|
for (let i = 0; i < 20; i++) {
|
|
graphics.fillStyle(0x2E8B57, 0.5);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2 + Math.random() * 4);
|
|
}
|
|
// Lighter highlights
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0x90EE90, 0.4);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 1.5);
|
|
}
|
|
graphics.generateTexture('tile2d_grass', size, size);
|
|
|
|
// FOREST BIOME - DARK GREEN
|
|
graphics.clear();
|
|
graphics.fillStyle(0x2d5016, 1.0); // Dark green
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 25; i++) {
|
|
graphics.fillStyle(0x1a3010, 0.4);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3);
|
|
}
|
|
graphics.generateTexture('tile2d_forest', size, size);
|
|
|
|
// DESERT BIOME - TAN/SAND
|
|
graphics.clear();
|
|
graphics.fillStyle(0xd4c4a1, 1.0); // Sand color
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 20; i++) {
|
|
graphics.fillStyle(0xc4b491, 0.3);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 4);
|
|
}
|
|
graphics.generateTexture('tile2d_desert', size, size);
|
|
|
|
// MOUNTAIN BIOME - GRAY STONE
|
|
graphics.clear();
|
|
graphics.fillStyle(0x808080, 1.0); // Gray
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 30; i++) {
|
|
graphics.fillStyle(0x606060, 0.5);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2 + Math.random() * 3);
|
|
}
|
|
graphics.generateTexture('tile2d_mountain', size, size);
|
|
|
|
// SWAMP BIOME - DARK GREEN/BROWN
|
|
graphics.clear();
|
|
graphics.fillStyle(0x3d5a3d, 1.0); // Murky green
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 20; i++) {
|
|
graphics.fillStyle(0x2d4a2d, 0.6);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3 + Math.random() * 4);
|
|
}
|
|
graphics.generateTexture('tile2d_swamp', size, size);
|
|
|
|
// ===== NEW BIOMES - NORMAL (4) =====
|
|
|
|
// SNOW/FROZEN TUNDRA - LIGHT BLUE/WHITE
|
|
graphics.clear();
|
|
graphics.fillStyle(0xE0F7FA, 1.0); // Light cyan
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 30; i++) {
|
|
graphics.fillStyle(0xFFFFFF, 0.7); // White snowflakes
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 1 + Math.random() * 2);
|
|
}
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0xB3E5FC, 0.5); // Ice patches
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 4);
|
|
}
|
|
graphics.generateTexture('tile2d_snow', size, size);
|
|
|
|
// WASTELAND - DARK GRAY/BROWN
|
|
graphics.clear();
|
|
graphics.fillStyle(0x4a4a4a, 1.0); // Dark gray
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 25; i++) {
|
|
graphics.fillStyle(0x333333, 0.6); // Darker rubble
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3 + Math.random() * 4);
|
|
}
|
|
for (let i = 0; i < 10; i++) {
|
|
graphics.fillStyle(0x654321, 0.4); // Brown rust
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_wasteland', size, size);
|
|
|
|
// TROPICAL - YELLOW SAND
|
|
graphics.clear();
|
|
graphics.fillStyle(0xFFE082, 1.0); // Light yellow sand
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 20; i++) {
|
|
graphics.fillStyle(0xFFD54F, 0.5); // Darker sand
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 4);
|
|
}
|
|
for (let i = 0; i < 10; i++) {
|
|
graphics.fillStyle(0xFFF59D, 0.6); // Light sand
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_tropical', size, size);
|
|
|
|
// RADIOACTIVE - NEON GREEN
|
|
graphics.clear();
|
|
graphics.fillStyle(0x39FF14, 1.0); // Neon green
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 20; i++) {
|
|
graphics.fillStyle(0x00FF00, 0.8); // Bright green glow
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3 + Math.random() * 5);
|
|
}
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0x76FF03, 0.6); // Light green spots
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_radioactive', size, size);
|
|
|
|
// ===== NEW BIOMES - ANOMALOUS (9) =====
|
|
|
|
// DINO VALLEY - OLIVE GREEN
|
|
graphics.clear();
|
|
graphics.fillStyle(0x6B8E23, 1.0); // Olive drab
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 25; i++) {
|
|
graphics.fillStyle(0x556B2F, 0.6); // Dark olive
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3);
|
|
}
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0x9ACD32, 0.5); // Yellow green
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_dino_valley', size, size);
|
|
|
|
// MYTHICAL HIGHLANDS - PURPLE
|
|
graphics.clear();
|
|
graphics.fillStyle(0xB39DDB, 1.0); // Light purple
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 20; i++) {
|
|
graphics.fillStyle(0x9575CD, 0.6); // Medium purple
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 4);
|
|
}
|
|
for (let i = 0; i < 10; i++) {
|
|
graphics.fillStyle(0xE1BEE7, 0.7); // Light pink/purple
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_mythical', size, size);
|
|
|
|
// ENDLESS FOREST - VERY DARK GREEN
|
|
graphics.clear();
|
|
graphics.fillStyle(0x1B5E20, 1.0); // Very dark green
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 30; i++) {
|
|
graphics.fillStyle(0x104010, 0.7); // Almost black green
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3);
|
|
}
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0x2E7D32, 0.5); // Slightly lighter green
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_endless_forest', size, size);
|
|
|
|
// LOCH NESS - BLUE GRAY
|
|
graphics.clear();
|
|
graphics.fillStyle(0x546E7A, 1.0); // Blue gray
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 20; i++) {
|
|
graphics.fillStyle(0x455A64, 0.6); // Darker blue gray
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 4);
|
|
}
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0x607D8B, 0.5); // Lighter blue gray
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_loch_ness', size, size);
|
|
|
|
// CATACOMBS - DARK BROWN
|
|
graphics.clear();
|
|
graphics.fillStyle(0x3E2723, 1.0); // Very dark brown
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 25; i++) {
|
|
graphics.fillStyle(0x1B0000, 0.7); // Almost black
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3);
|
|
}
|
|
for (let i = 0; i < 10; i++) {
|
|
graphics.fillStyle(0x6D4C41, 0.5); // Medium brown
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_catacombs', size, size);
|
|
|
|
// EGYPTIAN DESERT - BRIGHT YELLOW
|
|
graphics.clear();
|
|
graphics.fillStyle(0xFFD54F, 1.0); // Bright yellow
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 25; i++) {
|
|
graphics.fillStyle(0xFFCA28, 0.6); // Amber yellow
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 4);
|
|
}
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0xFFE082, 0.5); // Light yellow
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_egyptian_desert', size, size);
|
|
|
|
// AMAZON RAINFOREST - DARK GREEN (jungle)
|
|
graphics.clear();
|
|
graphics.fillStyle(0x1B5E20, 1.0); // Dark green
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 30; i++) {
|
|
graphics.fillStyle(0x2E7D32, 0.7); // Medium green
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3);
|
|
}
|
|
for (let i = 0; i < 20; i++) {
|
|
graphics.fillStyle(0x43A047, 0.6); // Lighter green
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_amazon', size, size);
|
|
|
|
// ATLANTIS - CYAN/BLUE
|
|
graphics.clear();
|
|
graphics.fillStyle(0x00BCD4, 1.0); // Cyan
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 25; i++) {
|
|
graphics.fillStyle(0x0097A7, 0.7); // Dark cyan
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 4);
|
|
}
|
|
for (let i = 0; i < 20; i++) {
|
|
graphics.fillStyle(0x4DD0E1, 0.6); // Light cyan
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
// Bubbles
|
|
for (let i = 0; i < 10; i++) {
|
|
graphics.fillStyle(0xFFFFFF, 0.4);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 1);
|
|
}
|
|
graphics.generateTexture('tile2d_atlantis', size, size);
|
|
|
|
// CHERNOBYL - GRAY (ruined city)
|
|
graphics.clear();
|
|
graphics.fillStyle(0x757575, 1.0); // Medium gray
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 30; i++) {
|
|
graphics.fillStyle(0x616161, 0.7); // Dark gray
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 4);
|
|
}
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0x424242, 0.6); // Very dark gray
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3);
|
|
}
|
|
// Radioactive glow spots
|
|
for (let i = 0; i < 5; i++) {
|
|
graphics.fillStyle(0x39FF14, 0.3); // Green radiation
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_chernobyl', size, size);
|
|
|
|
// GRASS WITH FLOWERS (VIBRANT!)
|
|
graphics.clear();
|
|
graphics.fillStyle(0x3CB371, 1.0);
|
|
graphics.fillRect(0, 0, size, size);
|
|
|
|
for (let i = 0; i < 12; i++) {
|
|
graphics.fillStyle(0x2E8B57, 0.3);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
|
|
// BRIGHT colorful flowers!
|
|
const flowerColors = [0xFF1493, 0xFFD700, 0x00BFFF, 0xFF69B4];
|
|
for (let i = 0; i < 4; i++) {
|
|
graphics.fillStyle(flowerColors[Math.floor(Math.random() * 4)], 1.0);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3);
|
|
}
|
|
graphics.generateTexture('tile2d_grass_flowers', size, size);
|
|
|
|
// DIRT - RICH BROWN! 🟤
|
|
graphics.clear();
|
|
graphics.fillStyle(0x8B4513, 1.0); // Saddle brown
|
|
graphics.fillRect(0, 0, size, size);
|
|
|
|
for (let i = 0; i < 25; i++) {
|
|
graphics.fillStyle(0x654321, 0.5);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 3 + Math.random() * 5);
|
|
}
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0xA0826D, 0.4);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
graphics.generateTexture('tile2d_dirt', size, size);
|
|
|
|
graphics.clear();
|
|
graphics.fillStyle(0x8b4513, 1.0);
|
|
graphics.fillRect(0, 0, size, size);
|
|
graphics.fillStyle(0x3CB371, 0.3);
|
|
graphics.fillRect(0, 0, size, size);
|
|
graphics.generateTexture('tile2d_dirt_edge', size, size);
|
|
|
|
// WATER - BRIGHT TEAL! 💧 (WITH OUTLINE!)
|
|
graphics.clear();
|
|
graphics.fillStyle(0x20B2AA, 1.0); // Light sea green - vibrant teal!
|
|
graphics.fillRect(0, 0, size, size);
|
|
|
|
// Dark outline border (2D style!)
|
|
graphics.lineStyle(2, 0x006994, 0.8);
|
|
graphics.strokeRect(1, 1, size - 2, size - 2);
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
graphics.fillStyle(0x008B8B, 0.4);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 5 + Math.random() * 8);
|
|
}
|
|
for (let i = 0; i < 15; i++) {
|
|
graphics.fillStyle(0x48D1CC, 0.5);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
|
}
|
|
for (let i = 0; i < 12; i++) {
|
|
graphics.fillStyle(0xFFFFFF, 0.6);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 1);
|
|
}
|
|
graphics.generateTexture('tile2d_water', size, size);
|
|
|
|
graphics.clear();
|
|
graphics.fillStyle(0x20B2AA, 1.0);
|
|
graphics.fillRect(0, 0, size, size);
|
|
graphics.fillStyle(0x48D1CC, 0.5);
|
|
graphics.fillRect(0, 0, size, size);
|
|
graphics.generateTexture('tile2d_water_edge', size, size);
|
|
|
|
} else {
|
|
console.warn('⚠️ PNG Tilesets not loaded! Using fallback colors');
|
|
}
|
|
|
|
// STONE (FULLY OPAQUE!)
|
|
graphics.clear();
|
|
graphics.fillStyle(0x808080, 1.0); // 100% opacity!
|
|
graphics.fillRect(0, 0, size, size);
|
|
for (let i = 0; i < 12; i++) {
|
|
graphics.fillStyle(0x606060, 0.6);
|
|
graphics.fillCircle(Math.random() * size, Math.random() * size, 2 + Math.random() * 3);
|
|
}
|
|
graphics.generateTexture('tile2d_stone', size, size);
|
|
|
|
graphics.destroy();
|
|
this.texturesReady = true;
|
|
console.log('✅ Tile textures created - FULLY OPAQUE!');
|
|
}
|
|
|
|
extractTilesFromTileset(sourceKey, targetKey, tileX, tileY, tileSize) {
|
|
// Extract a single tile from tileset PNG and create new texture
|
|
if (!this.scene.textures.exists(sourceKey)) {
|
|
console.warn(`⚠️ Tileset ${sourceKey} not found, using fallback`);
|
|
return;
|
|
}
|
|
|
|
const source = this.scene.textures.get(sourceKey).getSourceImage();
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = tileSize;
|
|
canvas.height = tileSize;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Draw specific tile from tileset
|
|
ctx.drawImage(
|
|
source,
|
|
tileX * tileSize, tileY * tileSize, // Source position
|
|
tileSize, tileSize, // Source size
|
|
0, 0, // Dest position
|
|
tileSize, tileSize // Dest size
|
|
);
|
|
|
|
// Create new texture from extracted tile
|
|
this.scene.textures.addCanvas(targetKey, canvas);
|
|
}
|
|
|
|
renderMap() {
|
|
// 🌍 PHASE 28: Skip if using biome system!
|
|
if (this.biomeSystem && this.chunkManager) {
|
|
console.log('⏭️ Skipping renderMap() - using BiomeSystem chunk rendering instead');
|
|
return;
|
|
}
|
|
|
|
// 🎨 SIMPLE & CLEAN: Use TileSprite for seamless background!
|
|
const size = this.tileSize;
|
|
const mapWidth = this.width * size;
|
|
const mapHeight = this.height * size;
|
|
|
|
// 🛑 SAFETY CHECK: Prevent Out of Memory for extreme dimensions
|
|
if (mapWidth > 16384 || mapHeight > 16384) {
|
|
console.error(`❌ Map dimensions too large for TileSprite: ${mapWidth}x${mapHeight}. Capping at 8192.`);
|
|
// This is a safety cap to prevent browser crash
|
|
}
|
|
|
|
console.log(`🎨 Rendering seamless 2D map (${mapWidth}x${mapHeight})...`);
|
|
|
|
// Create solid grass background (NO TRANSPARENCY!)
|
|
// Use a smaller TileSprite if dimensions are huge
|
|
const bgWidth = Math.min(mapWidth, 8192);
|
|
const bgHeight = Math.min(mapHeight, 8192);
|
|
|
|
const grassBG = this.scene.add.tileSprite(0, 0, mapWidth, mapHeight, 'tile2d_grass');
|
|
grassBG.setOrigin(0, 0);
|
|
grassBG.setDepth(1);
|
|
|
|
// Create containers for paths and decorations
|
|
this.pathsLayer = this.scene.add.container(0, 0);
|
|
this.pathsLayer.setDepth(2);
|
|
|
|
this.decorLayer = this.scene.add.container(0, 0);
|
|
this.decorLayer.setDepth(3);
|
|
|
|
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
|
|
|
|
// Array to store water sprites for animation
|
|
this.waterSprites = [];
|
|
|
|
// Render all tiles on top of grass background
|
|
for (let y = 0; y < this.height; y++) {
|
|
for (let x = 0; x < this.width; x++) {
|
|
const tile = this.tiles[y][x];
|
|
const worldX = x * size;
|
|
const worldY = y * size;
|
|
|
|
// WATER tiles - back to Image, animate with tint!
|
|
if (tile.base === 4 || tile.base === 5) {
|
|
const waterSprite = this.scene.add.image(worldX, worldY, 'tile2d_water');
|
|
waterSprite.setOrigin(0, 0);
|
|
waterSprite.setDisplaySize(size, size);
|
|
this.pathsLayer.add(waterSprite);
|
|
this.waterSprites.push(waterSprite); // Store for animation!
|
|
}
|
|
|
|
// DIRT PATH tiles
|
|
else if (tile.base === 2 || tile.base === 3) {
|
|
const dirtSprite = this.scene.add.image(worldX, worldY, 'tile2d_dirt');
|
|
dirtSprite.setOrigin(0, 0);
|
|
dirtSprite.setDisplaySize(size, size);
|
|
|
|
// Edge blending
|
|
if (tile.base === 3) {
|
|
dirtSprite.setAlpha(0.8); // Blend with grass
|
|
}
|
|
|
|
this.pathsLayer.add(dirtSprite);
|
|
}
|
|
|
|
// DECORATIONS
|
|
if (tile.decoration) {
|
|
this.addDecoration(x, y, tile.decoration);
|
|
}
|
|
}
|
|
}
|
|
|
|
graphics.destroy();
|
|
|
|
// Store reference
|
|
this.groundLayer = grassBG;
|
|
|
|
console.log('✅ Seamless map rendered (NO GRID!)');
|
|
console.log(`🌊 Water tiles animated: ${this.waterSprites.length}`);
|
|
}
|
|
|
|
// 🌍 PHASE 28: Render a single chunk with biome support
|
|
renderChunk(chunk) {
|
|
if (this.tiledMap) return; // Skip chunk rendering if using Tiled map
|
|
if (!chunk || !this.biomeSystem) {
|
|
console.warn('⚠️ Cannot render chunk: missing data or biomeSystem');
|
|
return;
|
|
}
|
|
|
|
console.log(`🎨 renderChunk START: (${chunk.chunkX}, ${chunk.chunkY})`);
|
|
|
|
const size = this.tileSize;
|
|
let tilesRendered = 0;
|
|
const biomeCounts = {};
|
|
|
|
// Render ground tiles based on biome
|
|
for (const tileData of chunk.tiles) {
|
|
const { x, y, biome } = tileData;
|
|
const worldX = x * size;
|
|
const worldY = y * size;
|
|
|
|
// Count biomes
|
|
biomeCounts[biome] = (biomeCounts[biome] || 0) + 1;
|
|
|
|
// 🌈 PHASE 28: Use TransitionSystem for smooth color blending
|
|
let tileColor = null;
|
|
|
|
if (this.transitionSystem) {
|
|
// Get blended color from transition system
|
|
tileColor = this.transitionSystem.getBlendedTileColor(x, y);
|
|
}
|
|
|
|
// Get biome-specific tile texture
|
|
let tileTexture = 'tile2d_grass'; // Default
|
|
|
|
if (biome === 'forest') {
|
|
tileTexture = 'tile2d_forest';
|
|
} else if (biome === 'desert') {
|
|
tileTexture = 'tile2d_desert';
|
|
} else if (biome === 'mountain') {
|
|
tileTexture = 'tile2d_mountain';
|
|
} else if (biome === 'swamp') {
|
|
tileTexture = 'tile2d_swamp';
|
|
}
|
|
// NEW BIOMES - NORMAL (4)
|
|
else if (biome === 'snow') {
|
|
tileTexture = 'tile2d_snow';
|
|
} else if (biome === 'wasteland') {
|
|
tileTexture = 'tile2d_wasteland';
|
|
} else if (biome === 'tropical') {
|
|
tileTexture = 'tile2d_tropical';
|
|
} else if (biome === 'radioactive') {
|
|
tileTexture = 'tile2d_radioactive';
|
|
}
|
|
// NEW BIOMES - ANOMALOUS (9)
|
|
else if (biome === 'dino_valley') {
|
|
tileTexture = 'tile2d_dino_valley';
|
|
} else if (biome === 'mythical') {
|
|
tileTexture = 'tile2d_mythical';
|
|
} else if (biome === 'endless_forest') {
|
|
tileTexture = 'tile2d_endless_forest';
|
|
} else if (biome === 'loch_ness') {
|
|
tileTexture = 'tile2d_loch_ness';
|
|
} else if (biome === 'catacombs') {
|
|
tileTexture = 'tile2d_catacombs';
|
|
} else if (biome === 'egyptian_desert') {
|
|
tileTexture = 'tile2d_egyptian_desert';
|
|
} else if (biome === 'amazon') {
|
|
tileTexture = 'tile2d_amazon';
|
|
} else if (biome === 'atlantis') {
|
|
tileTexture = 'tile2d_atlantis';
|
|
} else if (biome === 'chernobyl') {
|
|
tileTexture = 'tile2d_chernobyl';
|
|
}
|
|
|
|
// Create tile sprite
|
|
const tileSprite = this.scene.add.image(worldX, worldY, tileTexture);
|
|
tileSprite.setOrigin(0, 0);
|
|
tileSprite.setDisplaySize(size, size);
|
|
tileSprite.setDepth(1);
|
|
|
|
// 🌈 Apply blended tint if in transition zone
|
|
if (tileColor !== null) {
|
|
tileSprite.setTint(tileColor);
|
|
}
|
|
|
|
// Store in chunk for cleanup
|
|
if (!chunk.sprites) chunk.sprites = [];
|
|
chunk.sprites.push(tileSprite);
|
|
|
|
tilesRendered++;
|
|
|
|
// 🌊 PHASE 28 SESSION 5: Check for water (rivers & lakes)
|
|
let isWater = false;
|
|
|
|
// Rivers
|
|
if (this.riverSystem && this.riverSystem.isRiver(x, y)) {
|
|
const riverColor = this.riverSystem.getRiverColor(x, y);
|
|
const waterRect = this.scene.add.rectangle(worldX, worldY, size, size, riverColor, 0.75);
|
|
waterRect.setOrigin(0, 0);
|
|
waterRect.setDepth(2);
|
|
chunk.sprites.push(waterRect);
|
|
isWater = true;
|
|
}
|
|
|
|
// Lakes (only if not already river)
|
|
if (!isWater && this.lakeSystem && this.lakeSystem.isLake(x, y)) {
|
|
const lakeColor = this.lakeSystem.getLakeColor(x, y);
|
|
const waterRect = this.scene.add.rectangle(worldX, worldY, size, size, lakeColor, 0.75);
|
|
waterRect.setOrigin(0, 0);
|
|
waterRect.setDepth(2);
|
|
chunk.sprites.push(waterRect);
|
|
isWater = true;
|
|
}
|
|
|
|
// Skip features if water tile
|
|
if (isWater) {
|
|
continue; // Skip to next tile
|
|
}
|
|
|
|
// 🏛️ PHASE 28 SESSION 6: Check for ROADS
|
|
if (this.structureSystem && this.structureSystem.isRoad(x, y)) {
|
|
// Get biome-specific road color
|
|
const baseColor = (biome === 'desert') ? 0xcda869 :
|
|
(biome === 'mountain') ? 0x9090a0 :
|
|
(biome === 'swamp') ? 0x5a4a3d :
|
|
0x8B7355; // Brown dirt road
|
|
|
|
const roadRect = this.scene.add.rectangle(worldX, worldY, size, size, baseColor, 1.0);
|
|
roadRect.setOrigin(0, 0);
|
|
roadRect.setDepth(1.5); // Above ground, below decorations
|
|
chunk.sprites.push(roadRect);
|
|
|
|
// Add some variation to road texture
|
|
if (Math.random() < 0.3) {
|
|
const detail = this.scene.add.rectangle(
|
|
worldX + Math.random() * size,
|
|
worldY + Math.random() * size,
|
|
size * 0.3,
|
|
size * 0.3,
|
|
baseColor - 0x202020,
|
|
0.5
|
|
);
|
|
detail.setOrigin(0, 0);
|
|
detail.setDepth(1.5);
|
|
chunk.sprites.push(detail);
|
|
}
|
|
|
|
// Roads block biome features
|
|
continue;
|
|
}
|
|
|
|
// 🏛️ PHASE 28 SESSION 6: Check for STRUCTURES
|
|
const structureData = this.structureSystem ? this.structureSystem.getStructure(x, y) : null;
|
|
if (structureData) {
|
|
// Structure exists here - render visual marker
|
|
if (structureData.type === 'landmark') {
|
|
// Landmark marker (large, special)
|
|
const landmarkMarker = this.scene.add.rectangle(worldX, worldY, size, size, 0xFFD700, 0.7);
|
|
landmarkMarker.setOrigin(0, 0);
|
|
landmarkMarker.setDepth(5);
|
|
chunk.sprites.push(landmarkMarker);
|
|
|
|
// Add a star symbol for landmarks (simple)
|
|
const star = this.scene.add.text(worldX + size / 2, worldY + size / 2, '★', {
|
|
fontSize: '20px',
|
|
color: '#ffffff'
|
|
});
|
|
star.setOrigin(0.5, 0.5);
|
|
star.setDepth(6);
|
|
chunk.sprites.push(star);
|
|
} else if (structureData.type === 'structure') {
|
|
// Regular structure marker
|
|
const structureColor = this.getStructureColor(structureData.structureType);
|
|
const structureMarker = this.scene.add.rectangle(worldX, worldY, size, size, structureColor, 0.8);
|
|
structureMarker.setOrigin(0, 0);
|
|
structureMarker.setDepth(4);
|
|
chunk.sprites.push(structureMarker);
|
|
}
|
|
|
|
// Structures block biome features
|
|
continue;
|
|
}
|
|
|
|
// 🌈 Apply mixed features for transitions
|
|
let features = [];
|
|
|
|
if (this.transitionSystem) {
|
|
features = this.transitionSystem.getMixedFeatures(x, y);
|
|
} else if (this.biomeSystem) {
|
|
features = this.biomeSystem.applyBiomeFeatures(x, y);
|
|
}
|
|
|
|
features.forEach(feature => {
|
|
this.addBiomeFeature(x, y, feature, chunk);
|
|
});
|
|
}
|
|
|
|
console.log(`✅ renderChunk END: ${tilesRendered} tiles rendered`);
|
|
console.log(`📊 Biomes in chunk:`, biomeCounts);
|
|
|
|
console.log(`✅ Chunk (${chunk.chunkX}, ${chunk.chunkY}) rendered with biomes`);
|
|
|
|
// 🐛 DEBUG: Add visible border around chunk
|
|
if (this.scene.add) {
|
|
const chunkWorldX = chunk.chunkX * (this.chunkSize * size);
|
|
const chunkWorldY = chunk.chunkY * (this.chunkSize * size);
|
|
const chunkPixelSize = this.chunkSize * size;
|
|
|
|
const border = this.scene.add.rectangle(
|
|
chunkWorldX,
|
|
chunkWorldY,
|
|
chunkPixelSize,
|
|
chunkPixelSize
|
|
);
|
|
border.setOrigin(0, 0);
|
|
border.setStrokeStyle(2, 0xFF0000, 0.5); // Red semi-transparent border
|
|
border.setDepth(100); // High depth to see it
|
|
border.setFillStyle(0x000000, 0); // No fill
|
|
|
|
if (!chunk.sprites) chunk.sprites = [];
|
|
chunk.sprites.push(border);
|
|
|
|
console.log(`🔲 Debug border added at (${chunkWorldX}, ${chunkWorldY})`);
|
|
}
|
|
}
|
|
|
|
// Add biome-specific feature
|
|
addBiomeFeature(gridX, gridY, feature, chunk) {
|
|
const size = this.tileSize;
|
|
const worldX = gridX * size + size / 2;
|
|
const worldY = gridY * size + size / 2;
|
|
|
|
let sprite;
|
|
|
|
switch (feature.type) {
|
|
case 'tree':
|
|
sprite = this.createTree(worldX, worldY);
|
|
break;
|
|
case 'rock':
|
|
sprite = this.createRock(worldX, worldY, feature.size);
|
|
break;
|
|
case 'bush':
|
|
sprite = this.createBush(worldX, worldY);
|
|
break;
|
|
case 'cactus':
|
|
sprite = this.createCactus(worldX, worldY);
|
|
break;
|
|
case 'deadTree':
|
|
sprite = this.createDeadTree(worldX, worldY);
|
|
break;
|
|
case 'boulder':
|
|
sprite = this.createBoulder(worldX, worldY);
|
|
break;
|
|
case 'mushroom':
|
|
sprite = this.createMushroom(worldX, worldY);
|
|
break;
|
|
case 'vine':
|
|
sprite = this.createVine(worldX, worldY);
|
|
break;
|
|
}
|
|
|
|
if (sprite && chunk) {
|
|
sprite.setDepth(10 + worldY);
|
|
if (!chunk.sprites) chunk.sprites = [];
|
|
chunk.sprites.push(sprite);
|
|
}
|
|
}
|
|
|
|
// Simple rock creation
|
|
createRock(x, y, size = 'small') {
|
|
const graphics = this.scene.add.graphics();
|
|
const rockSize = size === 'large' ? 15 : 8;
|
|
|
|
graphics.fillStyle(0x808080);
|
|
graphics.fillCircle(x, y, rockSize);
|
|
graphics.fillStyle(0x606060, 0.6);
|
|
graphics.fillCircle(x - 3, y - 3, rockSize * 0.6);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
// Boulder (large rock)
|
|
createBoulder(x, y) {
|
|
const graphics = this.scene.add.graphics();
|
|
|
|
graphics.fillStyle(0x707070);
|
|
graphics.fillCircle(x, y, 20);
|
|
graphics.fillStyle(0x505050);
|
|
graphics.fillCircle(x - 5, y - 5, 12);
|
|
graphics.fillStyle(0x909090, 0.5);
|
|
graphics.fillCircle(x + 5, y + 5, 8);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
// Cactus
|
|
createCactus(x, y) {
|
|
const graphics = this.scene.add.graphics();
|
|
|
|
// Main body
|
|
graphics.fillStyle(0x4a7c59);
|
|
graphics.fillRect(x - 5, y - 15, 10, 30);
|
|
|
|
// Arms
|
|
graphics.fillRect(x - 12, y - 8, 7, 10);
|
|
graphics.fillRect(x + 5, y - 5, 7, 10);
|
|
|
|
// Highlights
|
|
graphics.fillStyle(0x5a8c69, 0.6);
|
|
graphics.fillRect(x - 3, y - 12, 3, 24);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
// Mushroom
|
|
createMushroom(x, y) {
|
|
const graphics = this.scene.add.graphics();
|
|
|
|
// Stem
|
|
graphics.fillStyle(0xeeeeee);
|
|
graphics.fillRect(x - 2, y, 4, 8);
|
|
|
|
// Cap
|
|
graphics.fillStyle(0xd63031);
|
|
graphics.fillCircle(x, y, 6);
|
|
|
|
// Spots
|
|
graphics.fillStyle(0xffffff);
|
|
graphics.fillCircle(x - 3, y - 1, 1.5);
|
|
graphics.fillCircle(x + 2, y + 1, 1.2);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
// Vine (swamp feature)
|
|
createVine(x, y) {
|
|
const graphics = this.scene.add.graphics();
|
|
|
|
graphics.lineStyle(2, 0x2d4a2d);
|
|
graphics.beginPath();
|
|
graphics.moveTo(x, y - 10);
|
|
graphics.bezierCurveTo(
|
|
x + 5, y - 5,
|
|
x - 5, y + 5,
|
|
x, y + 10
|
|
);
|
|
graphics.strokePath();
|
|
|
|
return graphics;
|
|
}
|
|
|
|
// 🏛️ PHASE 28 SESSION 6: Get color for structure type
|
|
getStructureColor(structureType) {
|
|
const colors = {
|
|
// Grassland structures
|
|
'farm': 0x8B4513,
|
|
'house': 0xA0522D,
|
|
'barn': 0x654321,
|
|
'windmill': 0xD2691E,
|
|
'well': 0x708090,
|
|
|
|
// Forest structures
|
|
'cabin': 0x8B4513,
|
|
'ruins': 0x696969,
|
|
'treehouse': 0x8B7355,
|
|
'camp': 0x8B4513,
|
|
'shrine': 0x9370DB,
|
|
|
|
// Desert structures
|
|
'pyramid': 0xDAA520,
|
|
'oasis_camp': 0x8B7355,
|
|
'tomb': 0xCD853F,
|
|
'pillar': 0xD2B48C,
|
|
|
|
// Mountain structures
|
|
'mine': 0x2F4F4F,
|
|
'cave': 0x363636,
|
|
'tower': 0x708090,
|
|
'altar': 0x9370DB,
|
|
|
|
// Swamp structures
|
|
'hut': 0x556B2F,
|
|
'totem': 0x8B4513,
|
|
'bog_shrine': 0x6B8E23,
|
|
'abandoned_dock': 0x654321
|
|
};
|
|
|
|
return colors[structureType] || 0x808080; // Default gray
|
|
}
|
|
|
|
getTileTexture(tileType) {
|
|
const types = Map2DData.tileTypes;
|
|
|
|
switch (tileType) {
|
|
case types.GRASS: return 'tile2d_grass';
|
|
case types.GRASS_FLOWERS: return 'tile2d_grass_flowers';
|
|
case types.DIRT: return 'tile2d_dirt';
|
|
case types.DIRT_EDGE: return 'tile2d_dirt_edge';
|
|
case types.WATER: return 'tile2d_water';
|
|
case types.WATER_EDGE: return 'tile2d_water_edge';
|
|
case types.STONE: return 'tile2d_stone';
|
|
default: return 'tile2d_grass';
|
|
}
|
|
}
|
|
|
|
addDecoration(gridX, gridY, decorType) {
|
|
const size = this.tileSize;
|
|
const worldX = gridX * size + size / 2;
|
|
const worldY = gridY * size + size / 2;
|
|
|
|
const types = Map2DData.tileTypes;
|
|
|
|
let sprite;
|
|
|
|
switch (decorType) {
|
|
case types.TREE:
|
|
sprite = this.createTree(worldX, worldY);
|
|
break;
|
|
case types.FLOWER_RED:
|
|
sprite = this.createFlower(worldX, worldY, 0xff6b6b);
|
|
break;
|
|
case types.FLOWER_YELLOW:
|
|
sprite = this.createFlower(worldX, worldY, 0xffd93d);
|
|
break;
|
|
case types.FLOWER_BLUE:
|
|
sprite = this.createFlower(worldX, worldY, 0x6bcbff);
|
|
break;
|
|
case types.LILY_PAD:
|
|
sprite = this.createLilyPad(worldX, worldY);
|
|
break;
|
|
case types.BUSH:
|
|
sprite = this.createBush(worldX, worldY);
|
|
break;
|
|
case 'puddle':
|
|
sprite = this.createPuddle(worldX, worldY);
|
|
break;
|
|
}
|
|
|
|
if (sprite) {
|
|
// 🎨 2.5D DEPTH SORTING - Y position = depth!
|
|
// Objects lower on screen appear in front
|
|
sprite.setDepth(10 + worldY);
|
|
this.decorLayer.add(sprite);
|
|
}
|
|
}
|
|
|
|
createTree(x, y) {
|
|
// 🌳 TREE VARIETY SYSTEM!
|
|
const treeTypes = ['cherry', 'oak', 'pine', 'dead', 'apple'];
|
|
const randomType = Phaser.Utils.Array.GetRandom(treeTypes);
|
|
|
|
switch (randomType) {
|
|
case 'oak': return this.createOakTree(x, y);
|
|
case 'pine': return this.createPineTree(x, y);
|
|
case 'dead': return this.createDeadTree(x, y);
|
|
case 'apple': return this.createAppleTree(x, y);
|
|
default: return this.createCherryTree(x, y);
|
|
}
|
|
}
|
|
|
|
createCherryTree(x, y) {
|
|
// 🌸 PNG SPRITE!
|
|
if (this.scene.textures.exists('tree_cherry')) {
|
|
const tree = this.scene.add.image(x, y, 'tree_cherry');
|
|
const scale = Phaser.Math.FloatBetween(0.25, 0.4); // Even SMALLER! (was 0.8-1.2)
|
|
tree.setScale(scale);
|
|
tree.setOrigin(0.5, 0.85);
|
|
|
|
// Shadow
|
|
const shadow = this.scene.add.ellipse(x, y + 15, 30 * scale, 10, 0x000000, 0.2);
|
|
shadow.setDepth(tree.depth - 1);
|
|
|
|
return tree;
|
|
}
|
|
|
|
// FALLBACK: Procedural
|
|
return this.createProceduralCherryTree(x, y);
|
|
}
|
|
|
|
createProceduralCherryTree(x, y) {
|
|
// Old procedural code (backup)
|
|
const graphics = this.scene.add.graphics();
|
|
const growthScale = Phaser.Math.FloatBetween(0.8, 1.4); // BIGGER!
|
|
|
|
const heightOffset = -20 * growthScale;
|
|
const crownSize = 30 * growthScale; // MUCH BIGGER!
|
|
|
|
// Shadow (ellipse on ground)
|
|
graphics.fillStyle(0x000000, 0.2);
|
|
graphics.fillEllipse(x, y + 30, 40 * growthScale, 12); // Bigger shadow
|
|
|
|
// Trunk (with shading for volume)
|
|
const trunkW = 12 * growthScale; // Thicker
|
|
const trunkH = 30 * growthScale; // Taller
|
|
|
|
// Dark side (right)
|
|
graphics.fillStyle(0x6D3F1A);
|
|
graphics.fillRect(x + trunkW / 4, y + 10 + heightOffset, trunkW / 2, trunkH);
|
|
|
|
// Light side (left)
|
|
graphics.fillStyle(0x8B5A2B);
|
|
graphics.fillRect(x - trunkW / 2, y + 10 + heightOffset, trunkW / 2, trunkH);
|
|
|
|
// CROWN - Layered circles for 2.5D!
|
|
const crownY = y - 10 * growthScale + heightOffset; // Higher crown
|
|
|
|
// Dark base (shadow underneath)
|
|
graphics.fillStyle(0xE75480);
|
|
graphics.fillCircle(x + 4 * growthScale, crownY + 4 * growthScale, crownSize);
|
|
|
|
// Main pink crown
|
|
graphics.fillStyle(0xFF69B4);
|
|
graphics.fillCircle(x, crownY, crownSize);
|
|
|
|
// Top highlight (light from top-left)
|
|
graphics.fillStyle(0xFFB6C1, 0.8);
|
|
graphics.fillCircle(x - 6 * growthScale, crownY - 6 * growthScale, crownSize * 0.6);
|
|
|
|
// Bright specular spot
|
|
graphics.fillStyle(0xFFFFFF, 0.5);
|
|
graphics.fillCircle(x - 8 * growthScale, crownY - 8 * growthScale, crownSize * 0.3);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
createOakTree(x, y) {
|
|
// 🌲 PNG SPRITE!
|
|
if (this.scene.textures.exists('tree_oak')) {
|
|
const tree = this.scene.add.image(x, y, 'tree_oak');
|
|
const scale = Phaser.Math.FloatBetween(0.28, 0.42); // Even SMALLER!
|
|
tree.setScale(scale);
|
|
tree.setOrigin(0.5, 0.85);
|
|
const shadow = this.scene.add.ellipse(x, y + 15, 35 * scale, 12, 0x000000, 0.2);
|
|
shadow.setDepth(tree.depth - 1);
|
|
return tree;
|
|
}
|
|
|
|
// FALLBACK:
|
|
const graphics = this.scene.add.graphics();
|
|
const growthScale = Phaser.Math.FloatBetween(0.8, 1.4); // BIGGER!
|
|
|
|
// Shadow
|
|
graphics.fillStyle(0x000000, 0.15);
|
|
graphics.fillEllipse(x, y + 24, 25 * growthScale, 8);
|
|
|
|
// Thick trunk
|
|
graphics.fillStyle(0x654321);
|
|
graphics.fillRect(x - 5 * growthScale, y, 10 * growthScale, 24 * growthScale);
|
|
|
|
// Large round crown
|
|
graphics.fillStyle(0x228B22); // Forest green
|
|
graphics.fillCircle(x, y - 10 * growthScale, 18 * growthScale);
|
|
|
|
// Darker outline
|
|
graphics.lineStyle(2, 0x006400);
|
|
graphics.strokeCircle(x, y - 10 * growthScale, 18 * growthScale);
|
|
|
|
// Highlight
|
|
graphics.fillStyle(0x32CD32, 0.6);
|
|
graphics.fillCircle(x - 5 * growthScale, y - 15 * growthScale, 8 * growthScale);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
createPineTree(x, y) {
|
|
// 🌲 PNG SPRITE!
|
|
if (this.scene.textures.exists('tree_pine')) {
|
|
const tree = this.scene.add.image(x, y, 'tree_pine');
|
|
const scale = Phaser.Math.FloatBetween(0.28, 0.45); // Even SMALLER!
|
|
tree.setScale(scale);
|
|
tree.setOrigin(0.5, 0.9); // Taller
|
|
const shadow = this.scene.add.ellipse(x, y + 20, 30 * scale, 10, 0x000000, 0.2);
|
|
shadow.setDepth(tree.depth - 1);
|
|
return tree;
|
|
}
|
|
|
|
// FALLBACK:
|
|
const graphics = this.scene.add.graphics();
|
|
const growthScale = Phaser.Math.FloatBetween(0.8, 1.5); // BIGGER (Pine taller)
|
|
|
|
// Shadow
|
|
graphics.fillStyle(0x000000, 0.15);
|
|
graphics.fillEllipse(x, y + 20, 15 * growthScale, 6);
|
|
|
|
// Trunk
|
|
graphics.fillStyle(0x8B4513);
|
|
graphics.fillRect(x - 3 * growthScale, y + 5, 6 * growthScale, 15 * growthScale);
|
|
|
|
// Stacked triangles (pine shape)
|
|
graphics.fillStyle(0x2F4F2F); // Dark green
|
|
|
|
// Bottom tier
|
|
graphics.fillTriangle(
|
|
x, y - 5 * growthScale,
|
|
x - 14 * growthScale, y + 10,
|
|
x + 14 * growthScale, y + 10
|
|
);
|
|
|
|
// Middle tier
|
|
graphics.fillTriangle(
|
|
x, y - 15 * growthScale,
|
|
x - 10 * growthScale, y,
|
|
x + 10 * growthScale, y
|
|
);
|
|
|
|
// Top tier
|
|
graphics.fillTriangle(
|
|
x, y - 25 * growthScale,
|
|
x - 7 * growthScale, y - 10 * growthScale,
|
|
x + 7 * growthScale, y - 10 * growthScale
|
|
);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
createDeadTree(x, y) {
|
|
// 💀 PNG SPRITE!
|
|
if (this.scene.textures.exists('tree_dead')) {
|
|
const tree = this.scene.add.image(x, y, 'tree_dead');
|
|
const scale = Phaser.Math.FloatBetween(0.35, 0.55); // SMALLER!
|
|
tree.setScale(scale);
|
|
tree.setOrigin(0.5, 0.85);
|
|
const shadow = this.scene.add.ellipse(x, y + 18, 28 * scale, 10, 0x000000, 0.3);
|
|
shadow.setDepth(tree.depth - 1);
|
|
return tree;
|
|
}
|
|
|
|
// FALLBACK:
|
|
const graphics = this.scene.add.graphics();
|
|
const growthScale = Phaser.Math.FloatBetween(0.7, 1.2); // BIGGER!
|
|
|
|
// Shadow
|
|
graphics.fillStyle(0x000000, 0.2);
|
|
graphics.fillEllipse(x, y + 20, 18 * growthScale, 6);
|
|
|
|
// Dark trunk
|
|
graphics.fillStyle(0x3D3D3D);
|
|
graphics.fillRect(x - 4 * growthScale, y, 8 * growthScale, 22 * growthScale);
|
|
|
|
// Branches (bare)
|
|
graphics.lineStyle(3 * growthScale, 0x2D2D2D);
|
|
graphics.beginPath();
|
|
graphics.moveTo(x, y - 5 * growthScale);
|
|
graphics.lineTo(x - 10 * growthScale, y - 15 * growthScale);
|
|
graphics.moveTo(x, y - 8 * growthScale);
|
|
graphics.lineTo(x + 12 * growthScale, y - 12 * growthScale);
|
|
graphics.strokePath();
|
|
|
|
return graphics;
|
|
}
|
|
|
|
createAppleTree(x, y) {
|
|
// 🍎 2D FLAT STYLE - Smaller and simpler!
|
|
const graphics = this.scene.add.graphics();
|
|
const scale = 0.6; // Much smaller!
|
|
|
|
// Shadow
|
|
graphics.fillStyle(0x000000, 0.15);
|
|
graphics.fillEllipse(x, y + 14, 18 * scale, 6);
|
|
|
|
// Trunk (simple rectangle)
|
|
graphics.fillStyle(0x6d4c41); // Brown
|
|
graphics.fillRect(x - 3 * scale, y - 2, 6 * scale, 16 * scale);
|
|
|
|
// Crown - simple rounded shape (2D style!)
|
|
graphics.fillStyle(0x4a9d5f); // Green
|
|
graphics.fillCircle(x, y - 10 * scale, 12 * scale);
|
|
|
|
// Secondary crown circles for volume (Stardew style)
|
|
graphics.fillCircle(x - 6 * scale, y - 8 * scale, 9 * scale);
|
|
graphics.fillCircle(x + 6 * scale, y - 8 * scale, 9 * scale);
|
|
|
|
// Apples (simple red dots)
|
|
graphics.fillStyle(0xFF3333);
|
|
const apples = [
|
|
{ x: -5, y: -12 }, { x: 4, y: -10 },
|
|
{ x: 0, y: -8 }, { x: 6, y: -9 }
|
|
];
|
|
apples.forEach(pos => {
|
|
graphics.fillCircle(
|
|
x + pos.x * scale,
|
|
y + pos.y * scale,
|
|
2.5 * scale
|
|
);
|
|
});
|
|
|
|
return graphics;
|
|
}
|
|
|
|
createFlower(x, y, color) {
|
|
const graphics = this.scene.add.graphics();
|
|
|
|
// Petals
|
|
graphics.fillStyle(color);
|
|
for (let i = 0; i < 5; i++) {
|
|
const angle = (Math.PI * 2 * i) / 5;
|
|
const px = x + Math.cos(angle) * 3;
|
|
const py = y + Math.sin(angle) * 3;
|
|
graphics.fillCircle(px, py, 2);
|
|
}
|
|
|
|
// Center
|
|
graphics.fillStyle(0xFFEB3B);
|
|
graphics.fillCircle(x, y, 2);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
createLilyPad(x, y) {
|
|
const graphics = this.scene.add.graphics();
|
|
|
|
// Lily pad (green circle)
|
|
graphics.fillStyle(0x4a8d2f);
|
|
graphics.fillCircle(x, y, 8);
|
|
|
|
// Pink flower
|
|
graphics.fillStyle(0xFF69B4);
|
|
for (let i = 0; i < 5; i++) {
|
|
const angle = (Math.PI * 2 * i) / 5;
|
|
const px = x + Math.cos(angle) * 3;
|
|
const py = y + Math.sin(angle) * 3;
|
|
graphics.fillCircle(px, py, 2);
|
|
}
|
|
graphics.fillStyle(0xFFD700);
|
|
graphics.fillCircle(x, y, 1.5);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
createBush(x, y) {
|
|
const graphics = this.scene.add.graphics();
|
|
|
|
graphics.fillStyle(0x3a6b1f, 0.9);
|
|
graphics.fillCircle(x, y, 10);
|
|
graphics.fillCircle(x - 6, y + 2, 8);
|
|
graphics.fillCircle(x + 6, y + 2, 8);
|
|
|
|
graphics.fillStyle(0x4a8d2f, 0.7);
|
|
graphics.fillCircle(x, y - 3, 6);
|
|
|
|
return graphics;
|
|
}
|
|
|
|
createPuddle(x, y) {
|
|
// Use existing puddle sprite if available
|
|
if (this.scene.textures.exists('luza_sprite')) {
|
|
const sprite = this.scene.add.image(x, y, 'luza_sprite');
|
|
sprite.setScale(0.3); // SMALL puddles!
|
|
sprite.setAlpha(0.3); // More transparent
|
|
return sprite;
|
|
}
|
|
|
|
// Fallback - small ellipse
|
|
const graphics = this.scene.add.graphics();
|
|
graphics.fillStyle(0x4488bb, 0.4);
|
|
graphics.fillEllipse(x, y, 8, 5); // Smaller!
|
|
return graphics;
|
|
}
|
|
|
|
createFallbackMap() {
|
|
// Create simple fallback if Map2DData fails
|
|
for (let y = 0; y < this.height; y++) {
|
|
this.tiles[y] = [];
|
|
for (let x = 0; x < this.width; x++) {
|
|
this.tiles[y][x] = {
|
|
base: 0, // Grass
|
|
decoration: null,
|
|
walkable: true
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
getTile(x, y) {
|
|
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
|
|
return null;
|
|
}
|
|
// Safety check: ensure tiles array is initialized
|
|
if (!this.tiles || !this.tiles[y]) {
|
|
return null;
|
|
}
|
|
return this.tiles[y][x];
|
|
}
|
|
|
|
isWalkable(x, y) {
|
|
const tile = this.getTile(x, y);
|
|
return tile ? tile.walkable : false;
|
|
}
|
|
|
|
update(time, delta) {
|
|
// 🌊 ANIMATED WATER - shimmer effect with tint!
|
|
if (this.waterSprites && this.waterSprites.length > 0) {
|
|
this.waterSprites.forEach(sprite => {
|
|
if (sprite && sprite.active) {
|
|
// Wave shimmer using tint color
|
|
const wave = Math.sin(time * 0.002) * 0.15 + 0.85;
|
|
const tintValue = Math.floor(wave * 255);
|
|
sprite.setTint(
|
|
0x2A7FBC | (tintValue << 16) | (tintValue << 8) | tintValue
|
|
);
|
|
|
|
// Subtle alpha pulsing
|
|
sprite.setAlpha(0.95 + Math.sin(time * 0.001) * 0.05);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
if (this.groundLayer) this.groundLayer.destroy();
|
|
if (this.pathsLayer) this.pathsLayer.destroy();
|
|
if (this.decorLayer) this.decorLayer.destroy();
|
|
}
|
|
}
|