// ======================================================== // NOVE GLOBALNE KONSTANTE ZA LOKACIJE // ======================================================== const FARM_SIZE = 100; // 100x100 VELIKA PLATFORMA! const FARM_CENTER_X = 50; // Center mape (50,50) const FARM_CENTER_Y = 50; // Center mape (50,50) const CITY_SIZE = 15; const CITY_START_X = 65; // Desni del mape (npr. med 65 in 80) const CITY_START_Y = 65; // ======================================================== // NOVE KONSTANTE ZA RUDNIK IN RUDE // ======================================================== const TILE_STONE_ORE = 82; // ID za navadni kamen (Ore Tile) const TILE_IRON_ORE = 83; // ID za železovo rudo const TILE_PAVEMENT = 16; // ID za prehodno ploščico (tla rudnika) const TILE_MINE_WALL = 81; // ID za zid rudnika (Solid/Kolizija) // ID-ji Virov const ITEM_STONE = 20; // ID za kamen, ki ga igralec dobi const ITEM_IRON = 21; // ID za železo const TREE_DENSITY_THRESHOLD = 0.45; // Višja vrednost = manj gosto (manj gozda) const ROCK_DENSITY_THRESHOLD = 0.60; // Prag za skupine skal // Terrain Generator System class TerrainSystem { constructor(scene, width = 100, height = 100) { this.scene = scene; this.width = width; this.height = height; this.iso = new IsometricUtils(48, 24); this.noise = new PerlinNoise(Date.now()); this.tiles = []; this.decorations = []; this.decorationsMap = new Map(); this.cropsMap = new Map(); this.tileHealthMap = new Map(); // Global register zdravja ploščic this.visibleTiles = new Map(); this.visibleDecorations = new Map(); this.visibleCrops = new Map(); this.tilePool = { active: [], inactive: [], get: () => { if (this.tilePool.inactive.length > 0) { const s = this.tilePool.inactive.pop(); s.setVisible(true); return s; } const s = this.scene.add.sprite(0, 0, 'dirt'); s.setOrigin(0.5, 0.5); return s; }, release: (sprite) => { sprite.setVisible(false); this.tilePool.inactive.push(sprite); } }; this.decorationPool = { active: [], inactive: [], get: () => { if (this.decorationPool.inactive.length > 0) { const s = this.decorationPool.inactive.pop(); s.setVisible(true); s.clearTint(); return s; } return this.scene.add.sprite(0, 0, 'tree'); }, release: (sprite) => { sprite.setVisible(false); this.decorationPool.inactive.push(sprite); } }; this.cropPool = { active: [], inactive: [], get: () => { if (this.cropPool.inactive.length > 0) { const s = this.cropPool.inactive.pop(); s.setVisible(true); return s; } return this.scene.add.sprite(0, 0, 'crop_stage_1'); }, release: (sprite) => { sprite.setVisible(false); this.cropPool.inactive.push(sprite); } }; this.terrainTypes = { WATER: { name: 'water', height: 0, color: 0x4444ff }, SAND: { name: 'sand', height: 0.2, color: 0xdddd44 }, GRASS_FULL: { name: 'grass_full', height: 0.35, color: 0x44aa44 }, GRASS_TOP: { name: 'grass_top', height: 0.45, color: 0x66cc66 }, DIRT: { name: 'dirt', height: 0.5, color: 0x8b4513 }, STONE: { name: 'stone', height: 0.7, color: 0x888888 }, PAVEMENT: { name: 'pavement', height: 0.6, color: 0x777777 }, RUINS: { name: 'ruins', height: 0.6, color: 0x555555 }, WALL_EDGE: { name: 'WALL_EDGE', height: 0.8, color: 0x505050, solid: true }, // OBZIDJE PATH: { name: 'path', height: -1, color: 0xc2b280 }, FARMLAND: { name: 'farmland', height: -1, color: 0x5c4033 }, // MINE TYPES MINE_FLOOR: { name: 'mine_floor', height: 0, color: 0x333333, id: TILE_PAVEMENT }, MINE_WALL: { name: 'mine_wall', height: 1, color: 0x1a1a1a, id: TILE_MINE_WALL }, ORE_STONE: { name: 'ore_stone', height: 0.5, color: 0x555555, id: TILE_STONE_ORE }, ORE_IRON: { name: 'ore_iron', height: 0.5, color: 0x884444, id: TILE_IRON_ORE } }; this.offsetX = 0; this.offsetY = 0; this.generatedChunks = new Set(); this.chunkSize = 10; // Init tiles array with NULLs this.tiles = Array.from({ length: this.height }, () => Array(this.width).fill(null)); } createTileTextures() { const tileWidth = 48; const tileHeight = 60; const P = 2; // PADDING (Margin) za preprečevanje črt const types = Object.values(this.terrainTypes); types.forEach((type) => { if (this.scene.textures.exists(type.name)) return; const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false }); // Koordinate z upoštevanjem paddinga const xs = P; const xe = 48 + P; const midX = 24 + P; const topY = P; const midY = 12 + P; const bottomY = 24 + P; const depth = 20; // 1. STRANICE (Faces) // Left Face const cLeft = 0x8B4513; // RJAVA DIRT - Left face graphics.fillStyle(cLeft); graphics.beginPath(); graphics.moveTo(midX, bottomY); graphics.lineTo(midX, bottomY + depth); graphics.lineTo(xs, midY + depth); graphics.lineTo(xs, midY); graphics.closePath(); graphics.fill(); graphics.lineStyle(2, cLeft); // Overdraw graphics.strokePath(); // Right Face const cRight = 0x6B3410; // RJAVA DIRT - Right face (temnejša) graphics.fillStyle(cRight); graphics.beginPath(); graphics.moveTo(xe, midY); graphics.lineTo(xe, midY + depth); graphics.lineTo(midX, bottomY + depth); graphics.lineTo(midX, bottomY); graphics.closePath(); graphics.fill(); graphics.lineStyle(2, cRight); graphics.strokePath(); // 2. ZGORNJA PLOSKEV (Top Face) graphics.fillStyle(type.color); graphics.beginPath(); graphics.moveTo(xs, midY); graphics.lineTo(midX, topY); graphics.lineTo(xe, midY); graphics.lineTo(midX, bottomY); graphics.closePath(); graphics.fill(); graphics.lineStyle(2, type.color); // Overdraw graphics.strokePath(); // Highlight graphics.lineStyle(1, 0xffffff, 0.15); graphics.beginPath(); graphics.moveTo(xs, midY); graphics.lineTo(midX, topY); graphics.lineTo(xe, midY); graphics.strokePath(); // 3. DETAJLI if (type.name.includes('grass')) { graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).lighten(10).color); for (let i = 0; i < 8; i++) { const rx = xs + 10 + Math.random() * 28; const ry = topY + 4 + Math.random() * 16; graphics.fillRect(rx, ry, 2, 2); } } if (type.name.includes('stone') || type.name.includes('ruins')) { graphics.fillStyle(0x444444); for (let i = 0; i < 6; i++) { const rx = xs + 8 + Math.random() * 30; const ry = topY + 4 + Math.random() * 16; graphics.fillRect(rx, ry, 3, 3); } } if (type.name.includes('pavement')) { graphics.lineStyle(1, 0x555555, 0.5); graphics.beginPath(); graphics.moveTo(xs + 12, midY + 6); graphics.lineTo(xs + 36, midY - 6); graphics.strokePath(); } graphics.generateTexture(type.name, tileWidth + P * 2, tileHeight + P * 2); graphics.destroy(); }); } generate() { this.createTileTextures(); console.log('🌍 Initializing World (Zone Streaming Mode)...'); // Generate ONLY the starting area (Farm) // Farm is at 20,20. Let's load 3x3 chunks around it. const centerCx = Math.floor(FARM_CENTER_X / this.chunkSize); const centerCy = Math.floor(FARM_CENTER_Y / this.chunkSize); for (let cy = centerCy - 2; cy <= centerCy + 2; cy++) { for (let cx = centerCx - 2; cx <= centerCx + 2; cx++) { this.generateChunk(cx, cy); } } console.log(`✅ World Init Complete. Loaded ${this.generatedChunks.size} chunks.`); } generateChunk(cx, cy) { const key = `${cx},${cy}`; if (this.generatedChunks.has(key)) return; // Bounds check if (cx < 0 || cy < 0 || cx * this.chunkSize >= this.width || cy * this.chunkSize >= this.height) return; this.generatedChunks.add(key); // console.log(`🔄 Streaming Chunk: [${cx}, ${cy}]`); const startX = cx * this.chunkSize; const startY = cy * this.chunkSize; const endX = Math.min(startX + this.chunkSize, this.width); const endY = Math.min(startY + this.chunkSize, this.height); const validPositions = []; // Local valid positions for this chunk for (let y = startY; y < endY; y++) { for (let x = startX; x < endX; x++) { // --- PER TILE GENERATION LOGIC (Moved from old loop) --- const nx = x * 0.1; const ny = y * 0.1; const elevation = this.noise.noise(nx, ny); let terrainType = this.terrainTypes.GRASS_FULL; // Edges of WORLD if (x < 3 || x >= this.width - 3 || y < 3 || y >= this.height - 3) { terrainType = this.terrainTypes.GRASS_FULL; } else { if (elevation < -0.6) terrainType = this.terrainTypes.WATER; // Deep Ocean else if (elevation < -0.4 && Math.random() < 0.2) { // ISOLATED ISLANDS (Nodes) // If we are in deep water but get random bump -> Island terrainType = this.terrainTypes.SAND; } else if (elevation > 0.1) terrainType = this.terrainTypes.SAND; else if (elevation > 0.2) terrainType = this.terrainTypes.GRASS_FULL; else if (elevation > 0.3) terrainType = this.terrainTypes.GRASS_TOP; else if (elevation > 0.7) terrainType = this.terrainTypes.DIRT; else if (elevation > 0.85) terrainType = this.terrainTypes.STONE; } // Farm Override - ZELENA PLATFORMA! if (Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 && Math.abs(y - FARM_CENTER_Y) <= FARM_SIZE / 2) { terrainType = this.terrainTypes.GRASS_FULL; // ZELENA TRAVA! } // City Override if (x >= CITY_START_X && x < CITY_START_X + CITY_SIZE && y >= CITY_START_Y && y < CITY_START_Y + CITY_SIZE) { const isEdge = (x === CITY_START_X || x === CITY_START_X + CITY_SIZE - 1 || y === CITY_START_Y || y === CITY_START_Y + CITY_SIZE - 1); if (isEdge) { terrainType = { name: 'WALL_EDGE', color: 0x505050, solid: true }; } else { terrainType = this.terrainTypes.PAVEMENT; if (Math.random() < 0.15) { terrainType = this.terrainTypes.RUINS; } } } // Create Tile Data this.tiles[y][x] = { type: terrainType.name, texture: terrainType.name, hasDecoration: false, hasCrop: false, solid: terrainType.solid || false }; // Track valid positions for decorations if (terrainType.name !== 'water' && terrainType.name !== 'sand' && terrainType.name !== 'stone' && !terrainType.solid) { // Exclude Farm/City from random decor logic const isFarm = Math.abs(x - FARM_CENTER_X) <= (FARM_SIZE / 2 + 2) && Math.abs(y - FARM_CENTER_Y) <= (FARM_SIZE / 2 + 2); const isCity = x >= CITY_START_X - 2 && x < CITY_START_X + CITY_SIZE + 2 && y >= CITY_START_Y - 2 && y < CITY_START_Y + CITY_SIZE + 2; if (!isFarm && !isCity) { validPositions.push({ x, y }); } } // Direct Placement Calls (Trees/Rocks) - legacy method support // this.placeTree(x, y, terrainType.name); // Optional: keep this if preferred over random batch } } // --- CHUNK DECORATION PASS --- // Instead of global counts, we use probability/density per chunk // 10x10 = 100 tiles. // Approx density: 0.2 trees per tile = 20 trees per chunk. // 1. Random Decorations validPositions.forEach(pos => { // Trees if (Math.random() < 0.05) { // 5% chance per valid tile this.placeTree(pos.x, pos.y, 'grass'); // force check inside } // Rocks if (Math.random() < 0.02) { this.placeRock(pos.x, pos.y, 'grass'); } // Flowers if (Math.random() < 0.05) { const flowers = ['flower_red', 'flower_yellow', 'flower_blue']; const flowerType = flowers[Math.floor(Math.random() * flowers.length)]; this.addDecoration(pos.x, pos.y, flowerType); } // Path Stones if (Math.random() < 0.02) { this.addDecoration(pos.x, pos.y, 'path_stone'); } }); } updateChunks(camera) { // Calculate which chunks are in view const view = camera.worldView; const buffer = 100; // Load slightly outside view const p1 = this.iso.toGrid(view.x - buffer, view.y - buffer); const p2 = this.iso.toGrid(view.x + view.width + buffer, view.y + view.height + buffer); const minCx = Math.floor(Math.min(p1.x, p2.x) / this.chunkSize); const maxCx = Math.ceil(Math.max(p1.x, p2.x) / this.chunkSize); const minCy = Math.floor(Math.min(p1.y, p2.y) / this.chunkSize); const maxCy = Math.ceil(Math.max(p1.y, p2.y) / this.chunkSize); for (let cy = minCy; cy <= maxCy; cy++) { for (let cx = minCx; cx <= maxCx; cx++) { this.generateChunk(cx, cy); } } } // Retained helper methods... damageDecoration(x, y, amount) { const key = `${x},${y}`; const decor = this.decorationsMap.get(key); if (!decor) return false; decor.hp -= amount; if (this.visibleDecorations.has(key)) { const sprite = this.visibleDecorations.get(key); sprite.setTint(0xff0000); this.scene.time.delayedCall(100, () => sprite.clearTint()); this.scene.tweens.add({ targets: sprite, x: sprite.x + 2, duration: 50, yoyo: true, repeat: 1 }); } if (decor.hp <= 0) { this.removeDecoration(x, y); // Chance to drop Blueprint if (this.scene.blueprintSystem) { this.scene.blueprintSystem.tryDropBlueprint(x, y, 'mining'); } return 'destroyed'; } return 'hit'; } removeDecoration(x, y) { const key = `${x},${y}`; const decor = this.decorationsMap.get(key); if (!decor) return; if (this.visibleDecorations.has(key)) { const sprite = this.visibleDecorations.get(key); sprite.setVisible(false); this.decorationPool.release(sprite); this.visibleDecorations.delete(key); } this.decorationsMap.delete(key); const index = this.decorations.indexOf(decor); if (index > -1) this.decorations.splice(index, 1); if (this.tiles[y] && this.tiles[y][x]) this.tiles[y][x].hasDecoration = false; return decor.type; } init(offsetX, offsetY) { this.offsetX = offsetX; this.offsetY = offsetY; } setTile(x, y, type) { if (x >= 0 && x < this.width && y >= 0 && y < this.height) { this.tiles[y][x].type = type; } } placeTree(x, y, tileType) { // 1. Safety Checks if (!tileType || !tileType.includes('grass')) return; const isFarm = Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 + 2; const isCity = x >= CITY_START_X - 2 && x < CITY_START_X + CITY_SIZE + 2 && y >= CITY_START_Y - 2 && y < CITY_START_Y + CITY_SIZE + 2; if (isFarm || isCity) return; // 2. Noise for clustering (Forests) const noiseVal = this.noise.noise(x * 0.1 + 123.45, y * 0.1 + 678.90); // KLJUČNO PREVERJANJE: Ali tu že stoji drug Sprite? const key = `${x},${y}`; if (this.decorationsMap.has(key)) return; if (this.tiles[y][x].hasDecoration) return; // 3. Selection Logic let shouldPlace = false; let type = window.SPRITE_TREE_HEALTHY || 'tree_green_final'; // USER LOGIC: Gostota 40% če smo nad pragom (Gozd) if (noiseVal > TREE_DENSITY_THRESHOLD && Math.random() < 0.4) { shouldPlace = true; } // Fallback: Redka posamična drevesa (1.5%) else if (Math.random() < 0.015) { shouldPlace = true; } if (shouldPlace) { // Variants Logic const r = Math.random(); // 50% možnosti, da je drevo komaj začelo rasti (Sapling) if (r < 0.50) type = window.SPRITE_TREE_SAPLING || 'tree_sapling'; else if (r < 0.60) type = window.SPRITE_TREE_DEAD || 'tree_dead_final'; else if (r < 0.65) type = window.SPRITE_TREE_BLUE || 'tree_blue_final'; // Ostalo (35%) je odraslo drevo } // 4. Placement if (shouldPlace) { this.addDecoration(x, y, type); } } placeRock(x, y, tileType) { if (!tileType || !tileType.includes('grass') && !tileType.includes('dirt')) return; const isFarm = Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 + 2; const isCity = x >= CITY_START_X - 2 && x < CITY_START_X + CITY_SIZE + 2 && y >= CITY_START_Y - 2 && y < CITY_START_Y + CITY_SIZE + 2; if (isFarm || isCity) return; // KLJUČNO PREVERJANJE: Ali tu že stoji drug Sprite? const key = `${x},${y}`; if (this.decorationsMap.has(key)) return; if (this.tiles[y][x].hasDecoration) return; // Noise for Rock Clusters const noiseVal = this.noise.noise(x * 0.15 + 99.99, y * 0.15 + 88.88); let shouldPlace = false; let type = 'rock_asset'; // Default lep kamen // USER LOGIC: Gostota 40% če smo nad pragom (Skalovje) if (noiseVal > ROCK_DENSITY_THRESHOLD && Math.random() < 0.4) { shouldPlace = true; } // Fallback: Redke posamične skale (1%) else if (Math.random() < 0.01) { shouldPlace = true; } if (shouldPlace) { this.addDecoration(x, y, type); } } placeStructure(gridX, gridY, type) { if (type === 'ruin') { for (let y = 0; y < 6; y++) { for (let x = 0; x < 6; x++) { if (Math.random() > 0.6) this.addDecoration(gridX + x, gridY + y, 'fence'); this.setTile(gridX + x, gridY + y, 'stone'); } } } if (type === 'arena') { const size = 12; for (let y = 0; y < size; y++) { for (let x = 0; x < size; x++) { const tx = gridX + x; const ty = gridY + y; this.setTile(tx, ty, 'stone'); if (x === 0 || x === size - 1 || y === 0 || y === size - 1) { if (!(x === Math.floor(size / 2) && y === size - 1)) { this.addDecoration(tx, ty, 'fence'); } } } } this.addDecoration(gridX + 6, gridY + 6, 'gravestone'); } if (type === 'ruin_room') { for (let y = 0; y < 5; y++) { for (let x = 0; x < 5; x++) { const tx = gridX + x; const ty = gridY + y; if (x > 0 && x < 4 && y > 0 && y < 4) { this.setTile(tx, ty, 'ruins'); } if (x === 0 || x === 4 || y === 0 || y === 4) { const isCenter = (x === 2 || y === 2); if (isCenter && Math.random() > 0.5) continue; if (Math.random() > 0.3) { this.addDecoration(tx, ty, 'fence'); } else { // User rocks in ruins if (Math.random() > 0.5) { const rType = Math.random() > 0.5 ? 'rock_1' : 'rock_2'; this.addDecoration(tx, ty, rType); } } } } } } } addDecoration(gridX, gridY, type) { if (gridX < 0 || gridX >= this.width || gridY < 0 || gridY >= this.height) return; const key = `${gridX},${gridY}`; if (this.decorationsMap.has(key)) return; let scale = 1.0; if (type === 'rock_1' || type === 'rock_2') scale = 1.5; // Povečano (bilo 0.5) else if (type === 'tree_green_new' || type === 'tree_blue_new' || type === 'tree_dead_new') scale = 0.04; else if (type === 'flowers_new') scale = 0.02; else if (type === 'fence') scale = 0.025; else if (type === 'gravestone') scale = 0.03; else if (type === 'hill_sprite') scale = 0.025; else if (type.includes('_final')) scale = 1.0; // New Final Trees else { // Old Assets (Low Res) if (type.includes('tree')) scale = 1.2 + Math.random() * 0.4; else if (type.includes('rock')) scale = 0.8; else scale = 1.0; } // Calculate Plant Day for Saplings (Growth System) let plantDay = -1; if (type.includes('sapling')) { const w = this.scene.weatherSystem; plantDay = w ? w.getDayCount() : 1; // Random init offset for initial generation if (!w) plantDay = 1 - Math.floor(Math.random() * 2); } // Determine if decoration is SOLID (blocking movement) const typeLower = type.toLowerCase(); // Small decorations are NOT solid (can walk through) const isSmallDecor = typeLower.includes('flower') || typeLower.includes('small_rock') || typeLower.includes('path_stone') || typeLower.includes('mushroom') || typeLower.includes('puddle'); const isSolid = !isSmallDecor && ( typeLower.includes('tree') || typeLower.includes('sapling') || typeLower.includes('rock') || typeLower.includes('stone') || typeLower.includes('fence') || typeLower.includes('wall') || typeLower.includes('signpost') || typeLower.includes('hill') || typeLower.includes('chest') || typeLower.includes('spawner') || typeLower.includes('ruin') || typeLower.includes('arena') || typeLower.includes('house') || typeLower.includes('gravestone') || typeLower.includes('bush') || typeLower.includes('fallen_log') || typeLower.includes('furnace') || // WORKSTATION typeLower.includes('mint') // WORKSTATION ); const decorData = { gridX: gridX, gridY: gridY, type: type, id: key, maxHp: 10, hp: 10, scale: scale, plantDay: plantDay, // Added for Growth System solid: isSolid // TRDNOST (collision blocking) }; this.decorations.push(decorData); this.decorationsMap.set(key, decorData); if (this.tiles[gridY] && this.tiles[gridY][gridX]) { this.tiles[gridY][gridX].hasDecoration = true; } // Register Workstation if (typeLower.includes('furnace') || typeLower.includes('mint')) { if (this.scene.workstationSystem) { // Determine type exactly let stationType = 'furnace'; if (typeLower.includes('mint')) stationType = 'mint'; this.scene.workstationSystem.addStation(gridX, gridY, stationType); } } } setTileType(x, y, typeName) { if (!this.tiles[y] || !this.tiles[y][x]) return; this.tiles[y][x].type = typeName; const key = `${x},${y}`; if (this.visibleTiles.has(key)) { const sprite = this.visibleTiles.get(key); sprite.setTexture(typeName); } } addCrop(x, y, cropData) { const key = `${x},${y}`; this.cropsMap.set(key, cropData); this.tiles[y][x].hasCrop = true; } removeCrop(x, y) { const key = `${x},${y}`; if (this.cropsMap.has(key)) { if (this.visibleCrops.has(key)) { const sprite = this.visibleCrops.get(key); sprite.setVisible(false); this.cropPool.release(sprite); this.visibleCrops.delete(key); } this.cropsMap.delete(key); this.tiles[y][x].hasCrop = false; } } updateCropVisual(x, y, stage) { const key = `${x},${y}`; if (this.visibleCrops.has(key)) { const sprite = this.visibleCrops.get(key); sprite.setTexture(`crop_stage_${stage}`); } } getTile(x, y) { if (this.tiles[y] && this.tiles[y][x]) { return this.tiles[y][x]; } return null; } updateCulling(camera) { this.updateChunks(camera); const view = camera.worldView; let buffer = 200; if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') buffer = 50; const left = view.x - buffer - this.offsetX; const top = view.y - buffer - this.offsetY; const right = view.x + view.width + buffer - this.offsetX; const bottom = view.y + view.height + buffer - this.offsetY; const p1 = this.iso.toGrid(left, top); const p2 = this.iso.toGrid(right, top); const p3 = this.iso.toGrid(left, bottom); const p4 = this.iso.toGrid(right, bottom); const minGridX = Math.floor(Math.min(p1.x, p2.x, p3.x, p4.x)); const maxGridX = Math.ceil(Math.max(p1.x, p2.x, p3.x, p4.x)); const minGridY = Math.floor(Math.min(p1.y, p2.y, p3.y, p4.y)); const maxGridY = Math.ceil(Math.max(p1.y, p2.y, p3.y, p4.y)); const startX = Math.max(0, minGridX); const endX = Math.min(this.width, maxGridX); const startY = Math.max(0, minGridY); const endY = Math.min(this.height, maxGridY); const neededTileKeys = new Set(); const neededDecorKeys = new Set(); const neededCropKeys = new Set(); const voxelOffset = 12; for (let y = startY; y < endY; y++) { for (let x = startX; x < endX; x++) { const key = `${x},${y}`; const tile = this.tiles[y][x]; if (tile) { neededTileKeys.add(key); if (!this.visibleTiles.has(key)) { const sprite = this.tilePool.get(); // Use water texture with animation support if (tile.type === 'water') { // Check if water frames exist if (this.scene.textures.exists('water_frame_0')) { sprite.setTexture('water_frame_0'); // Mark sprite for animation sprite.isWater = true; sprite.waterFrame = 0; } else { sprite.setTexture('water'); } } else { sprite.setTexture(tile.type); } const screenPos = this.iso.toScreen(x, y); sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY)); sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_FLOOR)); // Tiles = Floor this.visibleTiles.set(key, sprite); } } const decor = this.decorationsMap.get(key); if (decor) { neededDecorKeys.add(key); if (!this.visibleDecorations.has(key)) { const sprite = this.decorationPool.get(); const screenPos = this.iso.toScreen(x, y); sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY - voxelOffset)); if (decor.type.includes('house') || decor.type.includes('market') || decor.type.includes('structure')) { sprite.setOrigin(0.5, 0.8); } else { // DREVESA: Origin na dno, da Y-sort deluje sprite.setOrigin(0.5, 1.0); } sprite.setTexture(decor.type); sprite.setScale(decor.scale || 1.0); if (decor.alpha !== undefined) { sprite.setAlpha(decor.alpha); } // Layer Objects sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_OBJECTS)); this.visibleDecorations.set(key, sprite); } } const crop = this.cropsMap.get(key); if (crop) { neededCropKeys.add(key); if (!this.visibleCrops.has(key)) { const sprite = this.cropPool.get(); const screenPos = this.iso.toScreen(x, y); sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY - voxelOffset)); const cropType = crop.type || 'wheat'; // Če je wheat, uporabimo 'crop_stage_' za nazaj združljivost s TextureGeneratorjem? // TextureGenerator dela 'crop_stage_X'. // Če dodam 'wheat_stage_X', moram posodobiti TextureGenerator. // Za zdaj: let textureKey = `crop_stage_${crop.stage}`; if (cropType === 'corn') textureKey = `corn_stage_${crop.stage}`; if (cropType === 'wheat' && this.scene.textures.exists('wheat_stage_1')) textureKey = `wheat_stage_${crop.stage}`; sprite.setTexture(textureKey); sprite.setOrigin(0.5, 1); // Layer Objects (da igralec hodi okoli njih) sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_OBJECTS)); this.visibleCrops.set(key, sprite); } } } } for (const [key, sprite] of this.visibleTiles) { if (!neededTileKeys.has(key)) { sprite.setVisible(false); this.tilePool.release(sprite); this.visibleTiles.delete(key); } } for (const [key, sprite] of this.visibleDecorations) { if (!neededDecorKeys.has(key)) { sprite.setVisible(false); this.decorationPool.release(sprite); this.visibleDecorations.delete(key); } } for (const [key, sprite] of this.visibleCrops) { if (!neededCropKeys.has(key)) { sprite.setVisible(false); this.cropPool.release(sprite); this.visibleCrops.delete(key); } } } getTile(x, y) { if (x >= 0 && x < this.width && y >= 0 && y < this.height) { return this.tiles[y][x]; } return null; } update(delta) { // Water animation (250ms per frame = 4 FPS) this.waterAnimTimer = (this.waterAnimTimer || 0) + delta; if (this.waterAnimTimer > 250) { this.waterAnimTimer = 0; this.waterCurrentFrame = ((this.waterCurrentFrame || 0) + 1) % 4; // Update all water tiles for (const [key, sprite] of this.visibleTiles) { if (sprite.isWater) { sprite.setTexture(`water_frame_${this.waterCurrentFrame}`); } } } this.growthTimer = (this.growthTimer || 0) + delta; if (this.growthTimer < 5000) return; this.growthTimer = 0; const weather = this.scene.weatherSystem; if (!weather) return; const currentDay = weather.getDayCount(); const healthyType = window.SPRITE_TREE_HEALTHY || 'tree_green_final'; for (const decor of this.decorationsMap.values()) { if (decor.type.includes('sapling')) { if (decor.plantDay !== undefined && decor.plantDay > -100 && (currentDay - decor.plantDay >= 3)) { decor.type = healthyType; decor.scale = 1.0; decor.hp = 10; decor.plantDay = -1; const key = decor.id; if (this.visibleDecorations.has(key)) { const sprite = this.visibleDecorations.get(key); sprite.setTexture(decor.type); sprite.setScale(decor.scale); this.scene.tweens.add({ targets: sprite, scaleY: { from: 0.1, to: decor.scale }, duration: 800, ease: 'Bounce.out' }); console.log(`🌳 Tree grew on Day ${currentDay}`); } } } } } placeStructure(x, y, type) { // Generate textures if needed if (type === 'chest' && !this.scene.textures.exists('chest')) { TextureGenerator.createChestSprite(this.scene, 'chest'); } if (type === 'spawner' && !this.scene.textures.exists('spawner')) { TextureGenerator.createSpawnerSprite(this.scene, 'spawner'); } if (type === 'ruin' && !this.scene.textures.exists('ruin')) { TextureGenerator.createStructureSprite(this.scene, 'ruin', 'ruin'); } if (type === 'arena' && !this.scene.textures.exists('arena')) { // Arena uses ruin texture for now TextureGenerator.createStructureSprite(this.scene, 'arena', 'ruin'); } if (type.startsWith('signpost')) { const textMap = { 'signpost_city': '→', 'signpost_farm': '←', 'signpost_both': '⇅' }; const text = textMap[type] || '?'; if (!this.scene.textures.exists(type)) { TextureGenerator.createSignpostSprite(this.scene, type, text); } } // Place as decoration this.addDecoration(x, y, type); console.log(`🏛️ Place ${type} at (${x}, ${y})`); } setSolid(x, y, isSolid) { if (x >= 0 && x < this.width && y >= 0 && y < this.height) { this.tiles[y][x].solid = isSolid; } } isSolid(x, y) { if (x >= 0 && x < this.width && y >= 0 && y < this.height) { return this.tiles[y][x].solid || false; } return true; // Out of bounds = solid } }