class UIScene extends Phaser.Scene { constructor() { super({ key: 'UIScene' }); } create() { console.log('🖥️ UIScene: Initialized!'); // Pridobi reference na GameScene podatke (ko bodo na voljo) this.gameScene = this.scene.get('GameScene'); // Setup UI Container // Shared Overlay (DayNight + Weather) // Rendered here to be screen-space (HUD) and ignore zoom this.overlayGraphics = this.add.graphics(); this.overlayGraphics.setDepth(-1000); // Behind UI elements this.drawUI(); this.createInventoryBar(); this.createGoldDisplay(); this.createVirtualJoystick(); this.createClock(); this.createMinimap(); // NEW: Mini mapa // this.createDebugInfo(); this.createSettingsButton(); // ESC Pause Menu this.pauseMenuActive = false; this.input.keyboard.on('keydown-ESC', () => { this.togglePauseMenu(); }); // Listeners for Age if (this.gameScene) { this.gameScene.events.on('update-age-ui', (data) => this.updateAge(data.gen, data.age)); } // Resize event this.scale.on('resize', this.resize, this); // Crafting Menu (C) this.input.keyboard.on('keydown-C', () => { this.toggleCraftingMenu(); }); // Save (F5) this.input.keyboard.on('keydown-F5', () => { if (this.gameScene) this.gameScene.saveGame(); }); // Factory Reset (F8) - Fix za "zginla drevesa" this.input.keyboard.on('keydown-F8', () => { console.log('🔥 FACTORY RESET - Clearing Save & Reloading...'); localStorage.removeItem('novafarma_savefile'); window.location.reload(); }); // Collection (J) this.input.keyboard.on('keydown-J', () => { this.toggleCollectionMenu(); }); // Skill Tree (K) this.input.keyboard.on('keydown-K', () => { this.toggleSkillTree(); }); // Define Recipes this.craftingRecipes = [ { id: 'axe', name: 'Stone Axe', req: { 'wood': 3, 'stone': 3 }, output: 1, type: 'tool', desc: 'Used for chopping trees.' }, { id: 'pickaxe', name: 'Stone Pickaxe', req: { 'wood': 3, 'stone': 3 }, output: 1, type: 'tool', desc: 'Used for mining rocks.' }, { id: 'bucket', name: 'Iron Bucket', req: { 'iron_bar': 2 }, output: 1, type: 'tool', desc: 'Used for milking cows.' }, { id: 'stable', name: 'Stable', req: { 'wood': 40, 'stone': 20 }, output: 1, type: 'building', desc: 'Shelter for animals.', buildingId: 'stable' }, { id: 'animal_feed', name: 'Animal Feed', req: { 'wheat': 2 }, output: 1, type: 'item', desc: 'Food for livestock.' }, { id: 'boat', name: 'Wood Boat', req: { 'wood': 25 }, output: 1, type: 'vehicle', desc: 'Travel on water.' }, { id: 'hoe', name: 'Stone Hoe', req: { 'wood': 2, 'stone': 2 }, output: 1, type: 'tool', desc: 'Prepares soil for planting.' }, { id: 'sword', name: 'Stone Sword', req: { 'wood': 5, 'stone': 2 }, output: 1, type: 'weapon', desc: 'Deals damage to zombies.' }, { id: 'fence', name: 'Wood Fence', req: { 'wood': 2 }, output: 1, type: 'building', desc: 'Simple barrier.' }, { id: 'chest', name: 'Wooden Chest', req: { 'wood': 20 }, output: 1, type: 'furniture', desc: 'Stores items.' }, { id: 'furnace', name: 'Furnace', req: { 'stone': 20 }, output: 1, type: 'machine', desc: 'Smelts ores into bars.' }, { id: 'mint', name: 'Mint', req: { 'stone': 50, 'iron_bar': 5 }, output: 1, type: 'machine', desc: 'Mints coins from bars.' }, { id: 'grave', name: 'Grave', req: { 'stone': 10 }, output: 1, type: 'furniture', desc: 'Resting place for zombies.' } ]; // OXYGEN EVENTS if (this.gameScene) { this.gameScene.events.on('update-inventory', () => this.updateInventoryUI()); this.gameScene.events.on('show-floating-text', (data) => this.showFloatingText(data)); this.gameScene.events.on('update-oxygen', (data) => this.updateOxygen(data)); this.gameScene.events.on('hide-oxygen', () => this.hideOxygen()); } this.createOxygenBar(); this.createZombieStatsPanel(); this.createFarmStatsPanel(); } // ... (rest of class) ... toggleCraftingMenu() { if (!this.craftingContainer) this.createCraftingMenu(); this.craftingContainer.setVisible(!this.craftingContainer.visible); // Pause/Resume game? // if (this.gameScene) this.gameScene.physics.world.isPaused = this.craftingContainer.visible; } toggleCrafting() { this.toggleCraftingMenu(); } toggleInventory() { // Za zdaj odpre crafting meni, ker nimamo ločenega "Big Inventory" this.toggleCraftingMenu(); } createCraftingMenu() { const w = 600; const h = 400; const x = this.scale.width / 2; const y = this.scale.height / 2; this.craftingContainer = this.add.container(x, y); this.craftingContainer.setDepth(2000); // Top of everything // 1. Background (Main Window) const bg = this.add.graphics(); bg.fillStyle(0x1a1a2e, 0.98); // Dark Blue theme bg.fillRect(-w / 2, -h / 2, w, h); bg.lineStyle(2, 0x4e4e6e, 1); bg.strokeRect(-w / 2, -h / 2, w, h); this.craftingContainer.add(bg); // Header const titleBg = this.add.rectangle(0, -h / 2 + 25, w, 50, 0x16213e); this.craftingContainer.add(titleBg); const title = this.add.text(0, -h / 2 + 25, 'WORKBENCH', { fontSize: '24px', fontFamily: 'Courier New', fontStyle: 'bold', color: '#ffffff' }).setOrigin(0.5); this.craftingContainer.add(title); // Close Button const closeBtn = this.add.text(w / 2 - 20, -h / 2 + 25, 'X', { fontSize: '24px', color: '#ff4444', fontStyle: 'bold' }).setOrigin(0.5); closeBtn.setInteractive({ useHandCursor: true }) .on('pointerdown', () => this.toggleCraftingMenu()); this.craftingContainer.add(closeBtn); // 2. Layout Containers // Left Panel (List) this.recipeListContainer = this.add.container(-w / 2 + 20, -h / 2 + 70); this.craftingContainer.add(this.recipeListContainer); // Right Panel (Details) this.detailsContainer = this.add.container(20, -h / 2 + 70); this.craftingContainer.add(this.detailsContainer); // Initial render this.selectedRecipe = null; this.refreshRecipeList(); this.craftingContainer.setVisible(false); } refreshRecipeList() { this.recipeListContainer.removeAll(true); // Filter recipes const unlocked = this.craftingRecipes.filter(r => { // Check Blueprint System if (this.gameScene.blueprintSystem) { return this.gameScene.blueprintSystem.isUnlocked(r.id); } return true; // Fallback }); let y = 0; unlocked.forEach((recipe, i) => { const btnBg = this.add.rectangle(130, y + 20, 260, 36, 0x2a2a3e); btnBg.setInteractive({ useHandCursor: true }); // Hover effect btnBg.on('pointerover', () => btnBg.setFillStyle(0x3a3a5e)); btnBg.on('pointerout', () => { if (this.selectedRecipe !== recipe) btnBg.setFillStyle(0x2a2a3e); else btnBg.setFillStyle(0x4a4a6e); }); // Click btnBg.on('pointerdown', () => { this.selectedRecipe = recipe; this.refreshRecipeList(); // Redraw selection highlight this.showRecipeDetails(recipe); }); // Highlight selected if (this.selectedRecipe === recipe) { btnBg.setFillStyle(0x4a4a6e); btnBg.setStrokeStyle(1, 0xffff00); } else { btnBg.setStrokeStyle(1, 0x4e4e6e); } const nameText = this.add.text(10, y + 10, recipe.name.toUpperCase(), { fontSize: '14px', fontFamily: 'monospace', fill: '#ffffff' }); this.recipeListContainer.add(btnBg); this.recipeListContainer.add(nameText); y += 40; }); // Select first if none selected if (!this.selectedRecipe && unlocked.length > 0) { this.selectedRecipe = unlocked[0]; this.showRecipeDetails(unlocked[0]); } } showRecipeDetails(recipe) { this.detailsContainer.removeAll(true); if (!recipe) return; // Title const title = this.add.text(0, 0, recipe.name, { fontSize: '22px', fontStyle: 'bold', fill: '#FFD700', stroke: '#000', strokeThickness: 2 }); this.detailsContainer.add(title); // Description const desc = this.add.text(0, 35, recipe.desc, { fontSize: '14px', fill: '#aaaaaa', wordWrap: { width: 250 } }); this.detailsContainer.add(desc); // Requirements Header this.detailsContainer.add(this.add.text(0, 90, 'REQUIRED MATERIALS:', { fontSize: '14px', fill: '#ffffff', fontStyle: 'bold' })); // Requirements List let y = 120; let canCraft = true; const inv = this.gameScene.inventorySystem; for (const [item, count] of Object.entries(recipe.req)) { const has = inv ? inv.getItemCount(item) : 0; const hasEnough = has >= count; if (!hasEnough) canCraft = false; const color = hasEnough ? '#55ff55' : '#ff5555'; const icon = hasEnough ? '✓' : '✗'; const reqText = this.add.text(0, y, `${icon} ${count}x ${item.toUpperCase()} (Have: ${has})`, { fontSize: '14px', fill: color, fontFamily: 'monospace' } ); this.detailsContainer.add(reqText); y += 20; } // CRAFT BUTTON const btnY = 300; const btnBg = this.add.rectangle(130, btnY, 200, 50, canCraft ? 0x00aa00 : 0x550000); if (canCraft) { btnBg.setInteractive({ useHandCursor: true }); btnBg.on('pointerover', () => btnBg.setFillStyle(0x00cc00)); btnBg.on('pointerout', () => btnBg.setFillStyle(0x00aa00)); btnBg.on('pointerdown', () => this.tryCraft(recipe)); } const btnText = this.add.text(130, btnY, 'CRAFT ITEM', { fontSize: '20px', fill: canCraft ? '#ffffff' : '#888888', fontStyle: 'bold' }).setOrigin(0.5); this.detailsContainer.add(btnBg); this.detailsContainer.add(btnText); } tryCraft(recipe) { if (!this.gameScene || !this.gameScene.inventorySystem) return; const inv = this.gameScene.inventorySystem; // Double check cost for (const [item, count] of Object.entries(recipe.req)) { if (!inv.hasItem(item, count)) { console.log(`Craft fail: Missing ${item}`); return; } } // Consume for (const [item, count] of Object.entries(recipe.req)) { inv.removeItem(item, count); } // Add Item inv.addItem(recipe.id, recipe.output); console.log(`Crafted ${recipe.name}!`); // Sound & Visuals if (this.gameScene.soundManager) this.gameScene.soundManager.beepPickup(); this.cameras.main.flash(200, 255, 255, 255); // Refresh UI this.refreshRecipeList(); this.showRecipeDetails(recipe); } resize(gameSize) { this.width = gameSize.width; this.height = gameSize.height; this.cameras.main.setViewport(0, 0, this.width, this.height); // Re-create UI elements at new positions this.createClock(); this.createGoldDisplay(); this.createResourcesDisplay(); this.createTimeControlPanel(); this.createInventoryBar(); this.createInventoryBar(); this.createDebugInfo(); this.createSettingsButton(); // Refresh data if (this.gameScene) { if (this.gameScene.inventorySystem) { this.updateInventory(this.gameScene.inventorySystem.slots); } // Clock/Gold update automatically in next frame // Overlay fixes itself via Systems if (this.overlayGraphics) { this.overlayGraphics.clear(); } } } drawUI() { const x = 20; const y = 20; const width = 150; // Zmanjšana širina (300 -> 150) const height = 15; // Zmanjšana višina (30 -> 15) const padding = 10; // 1. Health Bar this.healthBar = this.drawBar(x, y, width, height, 0xff0000, 100, 'HP'); // 2. Hunger Bar this.hungerBar = this.drawBar(x, y + height + padding, width, height, 0xff8800, 80, 'HUN'); // 3. Thirst Bar this.thirstBar = this.drawBar(x, y + (height + padding) * 2, width, height, 0x0088ff, 90, 'H2O'); // 4. XP Bar this.XPBar = this.drawBar(x, y + (height + padding) * 3, width, height, 0xFFD700, 0, 'XP'); // 5. Level Display this.LevelDisplay = this.add.text(x, y + (height + padding) * 4, 'LV: 1', { fontSize: '18px', fontFamily: 'Courier New', fill: '#FFD700', fontStyle: 'bold' }); } drawBar(x, y, width, height, color, initialPercent = 100, label = '') { // Label if (label) { this.add.text(x, y - 12, label, { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff' }); } // Background const bg = this.add.graphics(); bg.fillStyle(0x000000, 0.5); bg.fillRect(x, y, width, height); bg.lineStyle(2, 0xffffff, 0.2); bg.strokeRect(x, y, width, height); // Fill const fill = this.add.graphics(); // Initial draw fill.fillStyle(color, 1); const maxWidth = width - 4; const currentWidth = (maxWidth * initialPercent) / 100; fill.fillRect(x + 2, y + 2, currentWidth, height - 4); return { bg, fill, x, y, width, height, color }; } setBarValue(bar, percent) { // Clamp 0-100 percent = Phaser.Math.Clamp(percent, 0, 100); bar.fill.clear(); bar.fill.fillStyle(bar.color, 1); const maxWidth = bar.width - 4; const currentWidth = (maxWidth * percent) / 100; bar.fill.fillRect(bar.x + 2, bar.y + 2, currentWidth, bar.height - 4); } createInventoryBar() { // Clean up if (this.inventorySlots) { this.inventorySlots.forEach(slot => { if (slot.itemSprite) slot.itemSprite.destroy(); if (slot.itemText) slot.itemText.destroy(); if (slot.countText) slot.countText.destroy(); slot.destroy(); }); } const slotCount = 9; const slotSize = 48; // 48x48 sloti const padding = 5; const totalWidth = (slotCount * slotSize) + ((slotCount - 1) * padding); const startX = (this.scale.width - totalWidth) / 2; const startY = this.scale.height - slotSize - 20; this.inventorySlots = []; if (this.selectedSlot === undefined) this.selectedSlot = 0; for (let i = 0; i < slotCount; i++) { const x = startX + i * (slotSize + padding); // Slot Background const slot = this.add.graphics(); // Draw function to update style based on selection slot.userData = { x, y: startY, size: slotSize, index: i }; this.drawSlot(slot, false); // Add number text this.add.text(x + 2, startY + 2, (i + 1).toString(), { fontSize: '10px', fontFamily: 'monospace', fill: '#ffffff' }); this.inventorySlots.push(slot); } // Select first one initially this.selectSlot(0); // Keyboard inputs 1-9 this.input.keyboard.on('keydown', (event) => { const num = parseInt(event.key); if (!isNaN(num) && num >= 1 && num <= 9) { this.selectSlot(num - 1); } }); // Mouse scroll for inventory (optional) this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => { if (deltaY > 0) { this.selectSlot((this.selectedSlot + 1) % slotCount); } else if (deltaY < 0) { this.selectSlot((this.selectedSlot - 1 + slotCount) % slotCount); } }); } drawSlot(graphics, isSelected) { const { x, y, size } = graphics.userData; graphics.clear(); // Background graphics.fillStyle(0x000000, 0.6); graphics.fillRect(x, y, size, size); // Border if (isSelected) { graphics.lineStyle(3, 0xffff00, 1); // Yellow thick border for selection } else { graphics.lineStyle(2, 0x888888, 0.5); // Grey thin border } graphics.strokeRect(x, y, size, size); } selectSlot(index) { // Deselect ALL slots first for (let i = 0; i < this.inventorySlots.length; i++) { if (this.inventorySlots[i]) { this.drawSlot(this.inventorySlots[i], false); } } this.selectedSlot = index; // Select new if (this.inventorySlots[this.selectedSlot]) { this.drawSlot(this.inventorySlots[this.selectedSlot], true); } } updateInventory(slots) { if (!this.inventorySlots) return; for (let i = 0; i < this.inventorySlots.length; i++) { const slotGraphics = this.inventorySlots[i]; // Clean up old visual elements if (slotGraphics.itemSprite) { slotGraphics.itemSprite.destroy(); slotGraphics.itemSprite = null; } if (slotGraphics.itemText) { slotGraphics.itemText.destroy(); slotGraphics.itemText = null; } if (slotGraphics.countText) { slotGraphics.countText.destroy(); slotGraphics.countText = null; } if (slots[i]) { const { x, y, size } = slotGraphics.userData; const type = slots[i].type; const textureKey = `item_${type}`; // e.g., item_axe, item_wood // Check if texture exists if (this.textures.exists(textureKey)) { // Draw Sprite const sprite = this.add.sprite(x + size / 2, y + size / 2, textureKey); // Larger scale for better visibility (fill most of the slot) const scale = (size - 4) / Math.max(sprite.width, sprite.height); sprite.setScale(scale * 1.2); // 20% bigger! // Crisp 2D rendering sprite.setTexture(textureKey); slotGraphics.itemSprite = sprite; } else { // Fallback Text - bigger and bolder const text = this.add.text(x + size / 2, y + size / 2, type.substring(0, 3).toUpperCase(), { fontSize: '16px', align: 'center', color: '#ffff00', stroke: '#000', strokeThickness: 3, fontStyle: 'bold' } ).setOrigin(0.5); slotGraphics.itemText = text; } // Draw Count (if > 1) - bigger and bolder if (slots[i].count > 1) { const countText = this.add.text(x + size - 2, y + size - 2, slots[i].count.toString(), { fontSize: '14px', align: 'right', color: '#ffffff', stroke: '#000', strokeThickness: 3, fontStyle: 'bold' } ).setOrigin(1, 1); slotGraphics.countText = countText; } } } } createClock() { if (this.clockBg) this.clockBg.destroy(); if (this.clockText) this.clockText.destroy(); // Clock box top right const x = this.scale.width - 150; const y = 20; // Background const bg = this.add.graphics(); bg.fillStyle(0x000000, 0.5); bg.fillRect(x, y, 130, 40); bg.lineStyle(2, 0xffffff, 0.8); bg.strokeRect(x, y, 130, 40); this.clockBg = bg; // Save ref this.clockText = this.add.text(x + 65, y + 20, 'Day 1 - 08:00', { fontSize: '14px', fontFamily: 'Courier New', fill: '#ffffff', fontStyle: 'bold' }); this.clockText.setOrigin(0.5, 0.5); } createResourcesDisplay() { const startX = this.scale.width - 160; const startY = 110; // Below gold display const spacing = 35; // Container this.resourcesContainer = this.add.container(0, 0); this.resourcesContainer.setDepth(1000); // Resources to display const resources = [ { key: 'wood', label: '🪵', color: '#8B4513' }, { key: 'stone', label: '🪨', color: '#808080' }, { key: 'iron', label: '⚙️', color: '#C0C0C0' } ]; this.resourceTexts = {}; resources.forEach((res, i) => { const y = startY + (i * spacing); // Background const bg = this.add.graphics(); bg.fillStyle(0x1a1a1a, 0.7); bg.fillRect(startX, y, 140, 28); bg.lineStyle(2, parseInt(res.color.replace('#', '0x')), 0.6); bg.strokeRect(startX, y, 140, 28); this.resourcesContainer.add(bg); // Icon/Label const icon = this.add.text(startX + 10, y + 14, res.label, { fontSize: '18px' }).setOrigin(0, 0.5); this.resourcesContainer.add(icon); // Count text const text = this.add.text(startX + 40, y + 14, `${res.key.toUpperCase()}: 0`, { fontSize: '14px', fontFamily: 'Courier New', fill: res.color, fontStyle: 'bold' }).setOrigin(0, 0.5); this.resourcesContainer.add(text); this.resourceTexts[res.key] = text; }); } updateResourceDisplay(resource, amount) { if (!this.resourceTexts) return; const text = this.resourceTexts[resource]; if (text) { text.setText(`${resource.toUpperCase()}: ${amount}`); } } createGoldDisplay() { if (this.goldBg) this.goldBg.destroy(); if (this.goldText) this.goldText.destroy(); const x = this.scale.width - 150; const y = 70; // Below clock // Background const bg = this.add.graphics(); bg.fillStyle(0xDAA520, 0.2); // Goldish bg bg.fillRect(x, y, 130, 30); bg.lineStyle(2, 0xFFD700, 0.8); bg.lineStyle(2, 0xFFD700, 0.8); bg.strokeRect(x, y, 130, 30); this.goldBg = bg; // Save ref this.goldText = this.add.text(x + 65, y + 15, 'GOLD: 0', { fontSize: '16px', fontFamily: 'Courier New', fill: '#FFD700', // Gold color fontStyle: 'bold' }); this.goldText.setOrigin(0.5, 0.5); // GENERATION / AGE UI (Below Gold) if (this.genText) this.genText.destroy(); this.genText = this.add.text(x + 65, y + 40, 'Gen: 1 | Age: 18', { fontSize: '12px', color: '#AAAAAA' }).setOrigin(0.5); } updateAge(gen, age) { if (this.genText) this.genText.setText(`Gen: ${gen} | Age: ${age}`); } updateGold(amount) { if (this.goldText) { this.goldText.setText(`GOLD: ${amount}`); } } createDebugInfo() { if (this.debugText) this.debugText.destroy(); if (this.debugBg) this.debugBg.destroy(); const x = this.scale.width - 170; const y = 120; // Below Gold and Clock area // Background this.debugBg = this.add.graphics(); this.debugBg.fillStyle(0x000000, 0.7); this.debugBg.fillRect(x, y, 160, 70); this.debugBg.setDepth(2999); this.debugText = this.add.text(x + 10, y + 10, 'Waiting for stats...', { fontSize: '12px', fontFamily: 'monospace', fill: '#ffffff', stroke: '#000000', strokeThickness: 2 }); this.debugText.setDepth(3000); } update() { if (!this.gameScene) return; // Sync HP Bar if (this.gameScene.player && this.healthBar) { const hp = this.gameScene.player.hp !== undefined ? this.gameScene.player.hp : 100; const maxHp = this.gameScene.player.maxHp || 100; this.setBarValue(this.healthBar, (hp / maxHp) * 100); } if (this.gameScene.statsSystem && this.hungerBar && this.thirstBar) { const stats = this.gameScene.statsSystem; this.setBarValue(this.hungerBar, stats.hunger); this.setBarValue(this.thirstBar, stats.thirst); } // Update Zombie Stats Panel (starter zombie worker) if (this.gameScene.npcs && this.gameScene.npcs.length > 0) { const zombieWorker = this.gameScene.npcs.find(npc => npc.type === 'zombie' && npc.isTamed); if (zombieWorker) { this.updateZombieStats(zombieWorker); } } // Update Farm Stats Panel if (this.gameScene.farmStats) { this.updateFarmStats(this.gameScene.farmStats); } // Update Clock Display (HH:MM format) if (this.clockText && this.gameScene.timeSystem) { const ts = this.gameScene.timeSystem; const hour = Math.floor(ts.hour); const minute = Math.floor((ts.hour - hour) * 60); const hourStr = hour.toString().padStart(2, '0'); const minStr = minute.toString().padStart(2, '0'); // Day/Night indicator const isNight = hour < 6 || hour >= 18; const period = isNight ? '🌙' : '☀️'; this.clockText.setText(`Day ${ts.day} - ${hourStr}:${minStr} ${period}`); } // Update Resources Display if (this.resourceTexts && this.gameScene.inventorySystem) { const inv = this.gameScene.inventorySystem; this.updateResourceDisplay('wood', inv.getItemCount('wood')); this.updateResourceDisplay('stone', inv.getItemCount('stone')); this.updateResourceDisplay('iron', inv.getItemCount('iron')); } // Update Minimap this.updateMinimap(); } createTimeControlPanel() { const x = this.scale.width - 170; const y = 250; // Below resources this.timeControlContainer = this.add.container(x, y); this.timeControlContainer.setDepth(1000); // Background const bg = this.add.graphics(); bg.fillStyle(0x1a1a2a, 0.8); bg.fillRect(0, 0, 150, 100); bg.lineStyle(2, 0x4a90e2, 0.8); bg.strokeRect(0, 0, 150, 100); this.timeControlContainer.add(bg); // Title const title = this.add.text(75, 15, 'TIME SPEED', { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff', fontStyle: 'bold' }).setOrigin(0.5, 0.5); this.timeControlContainer.add(title); // Speed buttons const speeds = [ { label: '1x', value: 1.0, y: 40 }, { label: '2x', value: 2.0, y: 60 }, { label: '5x', value: 5.0, y: 80 } ]; this.timeSpeedButtons = []; speeds.forEach((speed, i) => { const btn = this.add.text(75, speed.y, speed.label, { fontSize: '14px', fontFamily: 'Courier New', fill: '#4a90e2', fontStyle: 'bold', backgroundColor: '#1a1a2a', padding: { x: 20, y: 5 } }).setOrigin(0.5, 0.5); btn.setInteractive({ useHandCursor: true }); btn.on('pointerdown', () => { this.setTimeSpeed(speed.value); this.highlightSpeedButton(i); }); this.timeControlContainer.add(btn); this.timeSpeedButtons.push(btn); }); this.highlightSpeedButton(0); // Pause button const pauseBtn = this.add.text(10, 15, '⏸️', { fontSize: '18px' }).setOrigin(0, 0.5); pauseBtn.setInteractive({ useHandCursor: true }); pauseBtn.on('pointerdown', () => this.toggleTimePause()); this.timeControlContainer.add(pauseBtn); this.pauseBtn = pauseBtn; } setTimeSpeed(speed) { if (this.gameScene && this.gameScene.timeSystem) { this.gameScene.timeSystem.timeScale = speed; console.log(`⏱️ Time speed: ${speed}x`); } } highlightSpeedButton(index) { this.timeSpeedButtons.forEach((btn, i) => { if (i === index) { btn.setStyle({ fill: '#ffff00', backgroundColor: '#2a4a6a' }); } else { btn.setStyle({ fill: '#4a90e2', backgroundColor: '#1a1a2a' }); } }); } toggleTimePause() { if (!this.gameScene || !this.gameScene.timeSystem) return; const ts = this.gameScene.timeSystem; if (ts.timeScale === 0) { ts.timeScale = this.lastTimeScale || 1.0; this.pauseBtn.setText('▶️'); console.log('▶️ Time resumed'); } else { this.lastTimeScale = ts.timeScale; ts.timeScale = 0; this.pauseBtn.setText('⏸️'); console.log('⏸️ Time paused'); } } toggleBuildMenu(isVisible) { if (!this.buildMenuContainer) { this.createBuildMenuInfo(); } if (isVisible) { this.buildMenuContainer.setVisible(true); this.buildMenuContainer.y = -100; // Start off-screen this.tweens.add({ targets: this.buildMenuContainer, y: 100, // Target pos duration: 300, ease: 'Back.easeOut' }); } else { // Slide out (optional) or just hide this.buildMenuContainer.setVisible(false); } } createBuildMenuInfo() { this.buildMenuContainer = this.add.container(this.width / 2, 100); const bg = this.add.graphics(); bg.fillStyle(0x000000, 0.7); bg.fillRect(-150, 0, 300, 100); bg.lineStyle(2, 0x00FF00, 1); bg.strokeRect(-150, 0, 300, 100); this.buildMenuContainer.add(bg); const title = this.add.text(0, 10, 'BUILD MODE [B]', { fontSize: '18px', fill: '#00FF00', fontStyle: 'bold' }).setOrigin(0.5, 0); this.buildMenuContainer.add(title); const info = this.add.text(0, 40, '[1] Fence (2 Wood)\n[2] Wall (2 Stone)\n[3] House (20W 20S 50G)', { fontSize: '14px', fill: '#ffffff', align: 'center' } ).setOrigin(0.5, 0); this.buildMenuContainer.add(info); this.selectedBuildingText = this.add.text(0, 80, 'Selected: Fence', { fontSize: '14px', fill: '#FFFF00' }).setOrigin(0.5, 0); this.buildMenuContainer.add(this.selectedBuildingText); this.buildMenuContainer.setVisible(false); } updateBuildSelection(name) { if (this.selectedBuildingText) { this.selectedBuildingText.setText(`Selected: ${name.toUpperCase()}`); } } showProjectMenu(ruinData, onContribute) { if (!this.projectMenuContainer) { this.createProjectMenu(); } // Update info const costText = `Req: ${ruinData.reqWood} Wood, ${ruinData.reqStone} Stone`; this.projectInfoText.setText(`RESTORING RUINS\n${costText}`); this.onContributeCallback = onContribute; this.projectMenuContainer.setVisible(true); this.projectMenuContainer.setDepth(10000); } createProjectMenu() { this.projectMenuContainer = this.add.container(this.width / 2, this.height / 2); // BG const bg = this.add.graphics(); bg.fillStyle(0x222222, 0.9); bg.fillRect(-150, -100, 300, 200); bg.lineStyle(2, 0x00FFFF, 1); bg.strokeRect(-150, -100, 300, 200); this.projectMenuContainer.add(bg); // Title const title = this.add.text(0, -80, 'PROJECT: RESTORATION', { fontSize: '20px', fill: '#00FFFF', fontStyle: 'bold' }).setOrigin(0.5); this.projectMenuContainer.add(title); // Info this.projectInfoText = this.add.text(0, -20, 'Req: ???', { fontSize: '16px', fill: '#ffffff', align: 'center' }).setOrigin(0.5); this.projectMenuContainer.add(this.projectInfoText); // Button const btnBg = this.add.rectangle(0, 50, 200, 40, 0x00aa00); btnBg.setInteractive(); btnBg.on('pointerdown', () => { if (this.onContributeCallback) this.onContributeCallback(); // Close menu? Or keep open to see result? // For now close this.projectMenuContainer.setVisible(false); }); this.projectMenuContainer.add(btnBg); const btnText = this.add.text(0, 50, 'CONTRIBUTE', { fontSize: '18px', fill: '#ffffff' }).setOrigin(0.5); this.projectMenuContainer.add(btnText); // Close Button const closeBtn = this.add.text(130, -90, 'X', { fontSize: '20px', fill: '#ff0000' }).setOrigin(0.5); closeBtn.setInteractive(); closeBtn.on('pointerdown', () => this.projectMenuContainer.setVisible(false)); this.projectMenuContainer.add(closeBtn); this.projectMenuContainer.setVisible(false); } showTradeMenu(inventorySystem) { if (!this.tradeMenuContainer) { this.createTradeMenu(inventorySystem); } this.updateTradeMenu(inventorySystem); this.tradeMenuContainer.setVisible(true); this.tradeMenuContainer.setDepth(10000); } createTradeMenu(inventorySystem) { this.tradeMenuContainer = this.add.container(this.width / 2, this.height / 2); // BG const bg = this.add.graphics(); bg.fillStyle(0x222222, 0.95); bg.fillRect(-200, -150, 400, 300); bg.lineStyle(2, 0xFFD700, 1); // Gold border bg.strokeRect(-200, -150, 400, 300); this.tradeMenuContainer.add(bg); // Title const title = this.add.text(0, -130, 'MERCHANT SHOP', { fontSize: '24px', fill: '#FFD700', fontStyle: 'bold' }).setOrigin(0.5); this.tradeMenuContainer.add(title); // Close Button const closeBtn = this.add.text(180, -140, 'X', { fontSize: '20px', fill: '#ff0000', fontStyle: 'bold' }).setOrigin(0.5); closeBtn.setInteractive({ useHandCursor: true }); closeBtn.on('pointerdown', () => this.tradeMenuContainer.setVisible(false)); this.tradeMenuContainer.add(closeBtn); // Content Container (for items) this.tradeItemsContainer = this.add.container(0, 0); this.tradeMenuContainer.add(this.tradeItemsContainer); } updateTradeMenu(inventorySystem) { this.tradeItemsContainer.removeAll(true); // Items to Sell (Player has -> Merchant wants) // Hardcoded prices for now const prices = { 'wheat': { price: 10, type: 'sell' }, 'wood': { price: 2, type: 'sell' }, 'seeds': { price: 5, type: 'buy' } }; const startY = -80; let index = 0; // Header const header = this.add.text(-180, startY, 'ITEM PRICE ACTION', { fontSize: '16px', fill: '#888888' }); this.tradeItemsContainer.add(header); // 1. Sell Wheat this.createTradeRow(inventorySystem, 'wheat', prices.wheat.price, 'SELL', index++, startY + 30); // 2. Sell Wood this.createTradeRow(inventorySystem, 'wood', prices.wood.price, 'SELL', index++, startY + 30); // 3. Buy Seeds this.createTradeRow(inventorySystem, 'seeds', prices.seeds.price, 'BUY', index++, startY + 30); } createTradeRow(inv, itemKey, price, action, index, yOffset) { const y = yOffset + (index * 40); // Name const name = this.add.text(-180, y, itemKey.toUpperCase(), { fontSize: '18px', fill: '#ffffff' }); this.tradeItemsContainer.add(name); // Price const priceText = this.add.text(-50, y, `${price}g`, { fontSize: '18px', fill: '#FFD700' }); this.tradeItemsContainer.add(priceText); // Button const btnX = 100; const btnBg = this.add.rectangle(btnX, y + 10, 80, 30, action === 'BUY' ? 0x008800 : 0x880000); btnBg.setInteractive({ useHandCursor: true }); const btnLabel = this.add.text(btnX, y + 10, action, { fontSize: '16px', fill: '#ffffff' }).setOrigin(0.5); btnBg.on('pointerdown', () => { if (action === 'SELL') { if (inv.hasItem(itemKey, 1)) { inv.removeItem(itemKey, 1); inv.gold += price; inv.updateUI(); // Refresh visuals? } else { // Fail feedback btnLabel.setText('NO ITEM'); this.scene.time.delayedCall(500, () => btnLabel.setText(action)); } } else if (action === 'BUY') { if (inv.gold >= price) { inv.gold -= price; inv.addItem(itemKey, 1); inv.updateUI(); } else { // Fail feedback btnLabel.setText('NO GOLD'); this.scene.time.delayedCall(500, () => btnLabel.setText(action)); } } }); this.tradeItemsContainer.add(btnBg); this.tradeItemsContainer.add(btnLabel); } // --- SETTINGS MENU --- createSettingsButton() { if (this.settingsBtn) this.settingsBtn.destroy(); this.settingsBtn = this.add.text(10, 10, '⚙️ SETTINGS', { fontSize: '16px', fill: '#ffffff', backgroundColor: '#000000', padding: { x: 5, y: 5 } }); this.settingsBtn.setInteractive({ useHandCursor: true }); this.settingsBtn.on('pointerdown', () => this.toggleSettingsMenu()); } toggleSettingsMenu() { if (!this.settingsContainer) { this.createSettingsMenu(); } this.settingsContainer.setVisible(!this.settingsContainer.visible); if (this.settingsContainer.visible) { this.settingsContainer.setDepth(20000); // Always on top } } createSettingsMenu() { this.settingsContainer = this.add.container(this.width / 2, this.height / 2); // BG const bg = this.add.graphics(); bg.fillStyle(0x000000, 0.9); bg.fillRect(-150, -120, 300, 240); bg.lineStyle(2, 0x888888, 1); bg.strokeRect(-150, -120, 300, 240); this.settingsContainer.add(bg); // Title const title = this.add.text(0, -90, 'SETTINGS', { fontSize: '24px', fontStyle: 'bold', fill: '#ffffff' }).setOrigin(0.5); this.settingsContainer.add(title); // Close const closeBtn = this.add.text(130, -110, 'X', { fontSize: '20px', fill: '#ff0000' }).setOrigin(0.5); closeBtn.setInteractive({ useHandCursor: true }); closeBtn.on('pointerdown', () => this.toggleSettingsMenu()); this.settingsContainer.add(closeBtn); // Options let y = -40; // 1. View Distance (Simple Toggle for now: Low/High) this.createSettingToggle(0, y, 'VIEW DISTANCE', () => this.gameScene.settings.viewDistance, // getter (val) => { // setter this.gameScene.settings.viewDistance = val; // Apply immediately? Or next frame. // TerrainSystem reads it in updateCulling? We need to ensure it does. if (this.gameScene.terrainSystem) this.gameScene.terrainSystem.lastCullX = -9999; // Force update }, ['LOW', 'HIGH'] ); y += 50; // 2. Weather Particles this.createSettingToggle(0, y, 'PARTICLES', () => this.gameScene.settings.particles, (val) => { this.gameScene.settings.particles = val; // Restart rain if active if (this.gameScene.weatherSystem && (this.gameScene.weatherSystem.currentWeather === 'rain' || this.gameScene.weatherSystem.currentWeather === 'storm')) { this.gameScene.weatherSystem.startRain(this.gameScene.weatherSystem.currentWeather === 'storm'); } }, ['NONE', 'LOW', 'HIGH'] ); y += 50; // 3. Shadows this.createSettingToggle(0, y, 'SHADOWS', () => this.gameScene.settings.shadows ? 'ON' : 'OFF', (val) => { this.gameScene.settings.shadows = (val === 'ON'); // Trigger redraw? Complex. For now just saves state. // Maybe reload scene? }, ['ON', 'OFF'] ); } createSettingToggle(x, y, label, getter, setter, options) { const labelText = this.add.text(x - 80, y, label, { fontSize: '16px', fill: '#aaaaaa' }).setOrigin(1, 0.5); this.settingsContainer.add(labelText); const currentVal = getter(); const valueText = this.add.text(x + 50, y, currentVal, { fontSize: '16px', fill: '#ffffff', fontStyle: 'bold' }).setOrigin(0.5, 0.5); this.settingsContainer.add(valueText); // Click to cycle const hitArea = this.add.rectangle(x + 50, y, 100, 30, 0xffffff, 0.1); hitArea.setInteractive({ useHandCursor: true }); hitArea.on('pointerdown', () => { const cur = getter(); let idx = options.indexOf(cur); idx = (idx + 1) % options.length; const next = options[idx]; setter(next); valueText.setText(next); }); this.settingsContainer.add(hitArea); } updateQuestTracker(quest) { if (!this.questContainer) { this.createQuestTracker(); } if (!quest) { this.questContainer.setVisible(false); return; } this.questContainer.setVisible(true); this.questTitle.setText(quest.title.toUpperCase()); let objText = ''; quest.objectives.forEach(obj => { const status = obj.done ? '✅' : '⬜'; let desc = ''; if (obj.type === 'collect') desc = `${obj.item}: ${obj.current}/${obj.amount}`; else if (obj.type === 'action') desc = `${obj.action}: ${obj.current}/${obj.amount}`; else if (obj.type === 'kill') desc = `Slay ${obj.target}: ${obj.current}/${obj.amount}`; objText += `${status} ${desc}\n`; }); this.questObjectives.setText(objText); } createQuestTracker() { const x = this.width - 240; const y = 20; this.questContainer = this.add.container(x, y); // BG const bg = this.add.graphics(); bg.fillStyle(0x000000, 0.6); bg.fillRect(0, 0, 220, 100); bg.lineStyle(2, 0xffaa00, 0.8); bg.strokeRect(0, 0, 220, 100); this.questContainer.add(bg); this.questTrackerBg = bg; // ref to resize later if needed // Title Header const header = this.add.text(10, 5, 'CURRENT QUEST', { fontSize: '10px', fill: '#aaaaaa' }); this.questContainer.add(header); // Title this.questTitle = this.add.text(10, 20, 'Quest Title', { fontSize: '16px', fill: '#ffaa00', fontStyle: 'bold' }); this.questContainer.add(this.questTitle); // Objectives this.questObjectives = this.add.text(10, 45, 'Objectives...', { fontSize: '14px', fill: '#ffffff', lineSpacing: 5 }); this.questContainer.add(this.questObjectives); } createVirtualJoystick() { const x = 120; const y = this.height - 120; // Warning: this.height might not be updated on resize? Use this.scale.height or just initial. // UIScene is usually overlay, so simple coords ok. But resize needs handling. const r = 60; // Visuals this.joyBase = this.add.circle(x, y, r, 0xffffff, 0.1).setScrollFactor(0).setDepth(2000).setInteractive(); this.joyStick = this.add.circle(x, y, r / 2, 0xffffff, 0.5).setScrollFactor(0).setDepth(2001); this.virtualJoystick = { up: false, down: false, left: false, right: false }; this.joyDragging = false; // Events this.input.on('pointermove', (pointer) => { if (!this.joyDragging) return; this.updateJoystick(pointer.x, pointer.y, x, y, r); }); this.input.on('pointerup', () => { if (this.joyDragging) { this.joyDragging = false; this.joyStick.setPosition(x, y); this.virtualJoystick = { up: false, down: false, left: false, right: false }; } }); this.joyBase.on('pointerdown', (pointer) => { this.joyDragging = true; this.updateJoystick(pointer.x, pointer.y, x, y, r); }); } updateJoystick(px, py, cx, cy, r) { const angle = Phaser.Math.Angle.Between(cx, cy, px, py); const dist = Math.min(r, Phaser.Math.Distance.Between(cx, cy, px, py)); const sx = cx + Math.cos(angle) * dist; const sy = cy + Math.sin(angle) * dist; this.joyStick.setPosition(sx, sy); // Normalize angle to degrees let deg = Phaser.Math.RadToDeg(angle); this.virtualJoystick = { up: false, down: false, left: false, right: false }; // Mapping to Isometric direction keys // UP Key (Top-Left on screen) -> Angle -135 (-180 to -90) // RIGHT Key (Top-Right on screen) -> Angle -45 (-90 to 0) // DOWN Key (Bottom-Right on screen) -> Angle 45 (0 to 90) // LEFT Key (Bottom-Left on screen) -> Angle 135 (90 to 180) if (deg > -170 && deg <= -80) this.virtualJoystick.up = true; // Tuned slightly else if (deg > -80 && deg <= 10) this.virtualJoystick.right = true; else if (deg > 10 && deg <= 100) this.virtualJoystick.down = true; else this.virtualJoystick.left = true; } showQuestDialog(quest, onAccept) { const width = 400; const height = 250; const x = this.cameras.main.centerX; const y = this.cameras.main.centerY; const container = this.add.container(x, y); container.setDepth(5000); const bg = this.add.rectangle(0, 0, width, height, 0x222222, 0.95); bg.setStrokeStyle(4, 0x444444); const title = this.add.text(0, -90, quest.title.toUpperCase(), { fontSize: '24px', fontStyle: 'bold', color: '#ffcc00' }).setOrigin(0.5); const desc = this.add.text(0, -30, quest.description, { fontSize: '16px', color: '#dddddd', align: 'center', wordWrap: { width: width - 60 } }).setOrigin(0.5); let rText = "Reward: "; if (quest.reward.gold) rText += `${quest.reward.gold} G `; if (quest.reward.item) rText += `+ ${quest.reward.amount || 1} ${quest.reward.item}`; const reward = this.add.text(0, 40, rText, { fontSize: '16px', color: '#00ff00', fontStyle: 'italic' }).setOrigin(0.5); // Buttons const btnAccept = this.add.rectangle(-70, 90, 120, 40, 0x228B22).setInteractive({ useHandCursor: true }); const txtAccept = this.add.text(-70, 90, 'ACCEPT', { fontSize: '18px', fontStyle: 'bold' }).setOrigin(0.5); const btnClose = this.add.rectangle(70, 90, 120, 40, 0x8B0000).setInteractive({ useHandCursor: true }); const txtClose = this.add.text(70, 90, 'DECLINE', { fontSize: '18px', fontStyle: 'bold' }).setOrigin(0.5); btnAccept.on('pointerdown', () => { onAccept(); container.destroy(); }); btnClose.on('pointerdown', () => { container.destroy(); }); container.add([bg, title, desc, reward, btnAccept, txtAccept, btnClose, txtClose]); // Appear anim container.setScale(0); this.tweens.add({ targets: container, scale: 1, duration: 200, ease: 'Back.out' }); } toggleCollectionMenu() { if (!this.collectionContainer) this.createCollectionMenu(); this.collectionContainer.setVisible(!this.collectionContainer.visible); if (this.collectionContainer.visible) { this.refreshCollection(); } } // --- SKILL TREE SYSTEM --- toggleSkillTree() { if (!this.skillTreeContainer) this.createSkillTreeMenu(); this.skillTreeContainer.setVisible(!this.skillTreeContainer.visible); if (this.skillTreeContainer.visible) { this.refreshSkillTree(); } } createSkillTreeMenu() { const w = 600; const h = 450; const x = this.scale.width / 2; const y = this.scale.height / 2; this.skillTreeContainer = this.add.container(x, y); this.skillTreeContainer.setDepth(2200); // Background const bg = this.add.graphics(); bg.fillStyle(0x002200, 0.95); // Dark Green Matrix style bg.fillRoundedRect(-w / 2, -h / 2, w, h, 15); bg.lineStyle(3, 0x00FF00, 1); bg.strokeRoundedRect(-w / 2, -h / 2, w, h, 15); this.skillTreeContainer.add(bg); // Header const title = this.add.text(0, -h / 2 + 30, 'HYBRID DNA EVOLUTION', { fontSize: '24px', fontFamily: 'monospace', fontStyle: 'bold', color: '#00FF00' }).setOrigin(0.5); this.skillTreeContainer.add(title); // Info Panel this.skillPointsText = this.add.text(0, -h / 2 + 60, 'Available Points: 0', { fontSize: '18px', fontFamily: 'monospace', color: '#FFFF00' }).setOrigin(0.5); this.skillTreeContainer.add(this.skillPointsText); // Skills Container this.skillsGrid = this.add.container(-w / 2 + 50, -h / 2 + 100); this.skillTreeContainer.add(this.skillsGrid); // Close Button const closeBtn = this.add.text(w / 2 - 30, -h / 2 + 30, 'X', { fontSize: '24px', color: '#FF0000', fontStyle: 'bold' }).setOrigin(0.5); closeBtn.setInteractive({ useHandCursor: true }).on('pointerdown', () => this.toggleSkillTree()); this.skillTreeContainer.add(closeBtn); this.skillTreeContainer.setVisible(false); } refreshSkillTree() { if (!this.gameScene || !this.gameScene.hybridSkillSystem) return; const sys = this.gameScene.hybridSkillSystem; this.skillPointsText.setText(`LEVEL: ${sys.level} | POINTS: ${sys.points}`); this.skillsGrid.removeAll(true); const skills = Object.entries(sys.skills); let y = 0; skills.forEach(([id, skill]) => { // Bg const rowBg = this.add.rectangle(250, y + 25, 500, 50, 0x003300); this.skillsGrid.add(rowBg); // Name & Level const nameText = this.add.text(0, y + 10, `${skill.name} (Lv ${skill.level}/${skill.max})`, { fontSize: '18px', fontFamily: 'monospace', color: '#00FF00', fontStyle: 'bold' }); this.skillsGrid.add(nameText); // Description const descText = this.add.text(0, y + 32, skill.desc, { fontSize: '12px', fontFamily: 'monospace', color: '#88FF88' }); this.skillsGrid.add(descText); // Upgrade Button if (skill.level < skill.max) { const canUpgrade = sys.points > 0; const btnX = 450; const btnColor = canUpgrade ? 0x00AA00 : 0x555555; const btn = this.add.rectangle(btnX, y + 25, 80, 30, btnColor); if (canUpgrade) { btn.setInteractive({ useHandCursor: true }); btn.on('pointerdown', () => { if (sys.tryUpgradeSkill(id)) { this.refreshSkillTree(); // Update UI } }); } this.skillsGrid.add(btn); const btnText = this.add.text(btnX, y + 25, 'UPGRADE', { fontSize: '14px', color: '#FFFFFF' }).setOrigin(0.5); this.skillsGrid.add(btnText); } else { const maxText = this.add.text(450, y + 25, 'MAXED', { fontSize: '14px', color: '#FFFF00', fontStyle: 'bold' }).setOrigin(0.5); this.skillsGrid.add(maxText); } y += 60; }); } createCollectionMenu() { const w = 500; const h = 400; const x = this.scale.width / 2; const y = this.scale.height / 2; this.collectionContainer = this.add.container(x, y); this.collectionContainer.setDepth(2100); // Book Background const bg = this.add.graphics(); bg.fillStyle(0x3e2723, 1); // Dark brown book bg.fillRoundedRect(-w / 2, -h / 2, w, h, 10); bg.lineStyle(4, 0xdec20b, 1); // Gold trim bg.strokeRoundedRect(-w / 2, -h / 2, w, h, 10); // Pages bg.fillStyle(0xf5e6c8, 1); // Paper color bg.fillRoundedRect(-w / 2 + 20, -h / 2 + 20, w - 40, h - 40, 5); this.collectionContainer.add(bg); // Title const title = this.add.text(0, -h / 2 + 40, 'COLLECTION ALBUM', { fontSize: '28px', fontFamily: 'serif', color: '#3e2723', fontStyle: 'bold' }).setOrigin(0.5); this.collectionContainer.add(title); // Grid Container this.collectionGrid = this.add.container(-w / 2 + 40, -h / 2 + 80); this.collectionContainer.add(this.collectionGrid); // Close Button const closeBtn = this.add.text(w / 2 - 40, -h / 2 + 40, 'X', { fontSize: '24px', color: '#ff0000', fontStyle: 'bold' }).setOrigin(0.5); closeBtn.setInteractive({ useHandCursor: true }) .on('pointerdown', () => this.toggleCollectionMenu()); this.collectionContainer.add(closeBtn); this.collectionContainer.setVisible(false); } refreshCollection() { if (!this.gameScene || !this.gameScene.collectionSystem) return; this.collectionGrid.removeAll(true); const system = this.gameScene.collectionSystem; const items = Object.entries(system.items); // Display stats const progress = system.getProgress(); const statText = this.add.text(0, 340, `Collected: ${progress.unlocked} / ${progress.total} (${Math.round(progress.percent)}%)`, { fontSize: '16px', color: '#3e2723', fontStyle: 'italic' }).setOrigin(0.5); this.collectionContainer.add(statText); // Needs to be added to container, not grid // Grid Layout let col = 0; let row = 0; const visibleCols = 6; const cellSize = 60; items.forEach(([id, data]) => { const isUnlocked = system.has(id); const cx = col * cellSize; const cy = row * cellSize; // Slot Bg const slot = this.add.rectangle(cx, cy, 50, 50, isUnlocked ? 0xccb08e : 0xaaaaaa); slot.setStrokeStyle(1, 0x8d6e63); this.collectionGrid.add(slot); if (isUnlocked) { // Icon const key = `item_${id}`; const tex = this.textures.exists(key) ? key : null; if (tex) { const sprite = this.add.sprite(cx, cy, tex).setScale(1.2); this.collectionGrid.add(sprite); } else { this.collectionGrid.add(this.add.text(cx, cy, id.substr(0, 2), { fontSize: '12px', color: '#000' }).setOrigin(0.5)); } // Tooltip logic could go here (hover) slot.setInteractive(); slot.on('pointerover', () => { this.showCollectionTooltip(cx, cy, data); }); slot.on('pointerout', () => { this.hideCollectionTooltip(); }); } else { // Locked this.collectionGrid.add(this.add.text(cx, cy, '?', { fontSize: '24px', color: '#555555' }).setOrigin(0.5)); } col++; if (col >= visibleCols) { col = 0; row++; } }); } showCollectionTooltip(x, y, data) { if (this.collectionTooltip) this.collectionTooltip.destroy(); this.collectionTooltip = this.add.container(this.collectionGrid.x + x + 30, this.collectionGrid.y + y); this.collectionContainer.add(this.collectionTooltip); const bg = this.add.rectangle(0, 0, 150, 60, 0x000000, 0.8); const name = this.add.text(0, -15, data.name, { fontSize: '14px', fontStyle: 'bold', color: '#fff' }).setOrigin(0.5); const desc = this.add.text(0, 10, data.category, { fontSize: '12px', color: '#aaa' }).setOrigin(0.5); this.collectionTooltip.add([bg, name, desc]); } hideCollectionTooltip() { if (this.collectionTooltip) { this.collectionTooltip.destroy(); this.collectionTooltip = null; } } // --- WORKER MENU --- showWorkerMenu(zombie) { if (this.workerMenuContainer) this.workerMenuContainer.destroy(); const x = this.scale.width / 2; const y = this.scale.height / 2; const w = 300; const h = 350; this.workerMenuContainer = this.add.container(x, y); this.workerMenuContainer.setDepth(2300); // Background const bg = this.add.graphics(); bg.fillStyle(0x333333, 0.95); bg.fillRoundedRect(-w / 2, -h / 2, w, h, 10); bg.lineStyle(2, 0x00FF00, 1); bg.strokeRoundedRect(-w / 2, -h / 2, w, h, 10); this.workerMenuContainer.add(bg); // Title const title = this.add.text(0, -h / 2 + 25, 'ZOMBIE WORKER CONTROL', { fontSize: '20px', fontStyle: 'bold', color: '#00FF00' }).setOrigin(0.5); this.workerMenuContainer.add(title); // Status const currentTask = zombie.workerData ? zombie.workerData.type : 'IDLE'; const statusText = this.add.text(0, -h / 2 + 55, `Current Task: ${currentTask}`, { fontSize: '16px', color: '#FFFFFF' }).setOrigin(0.5); this.workerMenuContainer.add(statusText); // Buttons const buttons = [ { label: 'FARM (Seeds/Harvest)', action: 'FARM', color: 0x8B4513 }, { label: 'MINE (Rocks)', action: 'MINE', color: 0x555555 }, { label: 'CLEAR ZONE (Trees/Debris)', action: 'CLEAR', color: 0xAA0000 }, { label: 'STOP / UNASSIGN', action: 'STOP', color: 0xFF5555 } ]; let btnY = -h / 2 + 100; buttons.forEach(btn => { const btnBg = this.add.rectangle(0, btnY, 250, 40, btn.color); btnBg.setInteractive({ useHandCursor: true }); btnBg.on('pointerdown', () => { this.assignZombieTask(zombie, btn.action); this.workerMenuContainer.destroy(); this.workerMenuContainer = null; }); this.workerMenuContainer.add(btnBg); const txt = this.add.text(0, btnY, btn.label, { fontSize: '16px', fontStyle: 'bold' }).setOrigin(0.5); this.workerMenuContainer.add(txt); btnY += 50; }); // Close const closeBtn = this.add.text(w / 2 - 20, -h / 2 + 20, 'X', { fontSize: '20px', color: '#FF0000', fontStyle: 'bold' }).setOrigin(0.5); closeBtn.setInteractive({ useHandCursor: true }).on('pointerdown', () => { this.workerMenuContainer.destroy(); this.workerMenuContainer = null; }); this.workerMenuContainer.add(closeBtn); } assignZombieTask(zombie, task) { if (!this.gameScene || !this.gameScene.zombieWorkerSystem) return; if (task === 'STOP') { this.gameScene.zombieWorkerSystem.removeWorker(zombie); this.gameScene.events.emit('show-floating-text', { x: zombie.sprite.x, y: zombie.sprite.y - 50, text: 'Idling...', color: '#FFFFFF' }); } else { // Assign with radius 6 this.gameScene.zombieWorkerSystem.assignWork(zombie, task, 8); this.gameScene.events.emit('show-floating-text', { x: zombie.sprite.x, y: zombie.sprite.y - 50, text: `Task: ${task}`, color: '#00FF00' }); } } // --- OXYGEN BAR --- createOxygenBar() { this.oxygenContainer = this.add.container(this.scale.width / 2, this.scale.height - 100); this.oxygenContainer.setDepth(2000); // Bg const bg = this.add.rectangle(0, 0, 204, 24, 0x000000); bg.setStrokeStyle(2, 0xffffff); this.oxygenContainer.add(bg); // Bar this.oxygenBar = this.add.rectangle(-100, 0, 200, 20, 0x00FFFF); this.oxygenBar.setOrigin(0, 0.5); this.oxygenContainer.add(this.oxygenBar); // Text const text = this.add.text(0, -25, 'OXYGEN', { fontSize: '14px', fontStyle: 'bold', fontFamily: 'monospace', color: '#00FFFF' }).setOrigin(0.5); this.oxygenContainer.add(text); this.oxygenContainer.setVisible(false); } updateOxygen(data) { if (!this.oxygenContainer) return; this.oxygenContainer.setVisible(true); const percent = Math.max(0, data.current / data.max); this.oxygenBar.width = 200 * percent; // Color warning if (percent < 0.3) this.oxygenBar.fillColor = 0xFF0000; else this.oxygenBar.fillColor = 0x00FFFF; } hideOxygen() { if (this.oxygenContainer) this.oxygenContainer.setVisible(false); } showFloatingText(data) { const text = this.add.text(data.x, data.y, data.text, { fontSize: '16px', color: data.color || '#FFFFFF', fontStyle: 'bold', stroke: '#000000', strokeThickness: 3 }); text.setOrigin(0.5); text.setDepth(10000); text.setScrollFactor(1); // Animate upward and fade this.tweens.add({ targets: text, y: text.y - 50, alpha: 0, duration: 1500, ease: 'Power2', onComplete: () => text.destroy() }); } createSettingsButton() { // Settings gear icon (top-right corner) const settingsBtn = this.add.text(this.cameras.main.width - 60, 20, '⚙️', { fontSize: '32px', color: '#ffffff' }); settingsBtn.setScrollFactor(0); settingsBtn.setDepth(9999); settingsBtn.setInteractive({ useHandCursor: true }); settingsBtn.on('pointerover', () => { settingsBtn.setScale(1.2); }); settingsBtn.on('pointerout', () => { settingsBtn.setScale(1.0); }); settingsBtn.on('pointerdown', () => { this.toggleSettingsMenu(); }); } toggleSettingsMenu() { if (this.settingsContainer) { // Close existing menu this.settingsContainer.destroy(); this.settingsContainer = null; // Resume game if (this.gameScene) { this.gameScene.physics.resume(); } return; } // Pause game if (this.gameScene) { this.gameScene.physics.pause(); } // Create settings menu const width = this.cameras.main.width; const height = this.cameras.main.height; this.settingsContainer = this.add.container(0, 0); this.settingsContainer.setScrollFactor(0); this.settingsContainer.setDepth(10000); // Semi-transparent background const bg = this.add.rectangle(0, 0, width, height, 0x000000, 0.8); bg.setOrigin(0); this.settingsContainer.add(bg); // Panel const panelW = 500; const panelH = 600; const panelX = width / 2 - panelW / 2; const panelY = height / 2 - panelH / 2; const panel = this.add.rectangle(panelX, panelY, panelW, panelH, 0x1a1a2e, 1); panel.setOrigin(0); panel.setStrokeStyle(4, 0x00ff41); this.settingsContainer.add(panel); // Title const title = this.add.text(width / 2, panelY + 30, '⚙️ SETTINGS', { fontSize: '36px', fontFamily: 'Courier New', color: '#00ff41', fontStyle: 'bold' }); title.setOrigin(0.5, 0); this.settingsContainer.add(title); // Language Section this.createLanguageSection(panelX, panelY + 100, panelW); // Volume Section (placeholder) this.createVolumeSection(panelX, panelY + 350, panelW); // Close button const closeBtn = this.add.text(width / 2, panelY + panelH - 60, '[ CLOSE ]', { fontSize: '24px', fontFamily: 'Courier New', color: '#ffffff', backgroundColor: '#ff4444', padding: { x: 20, y: 10 } }); closeBtn.setOrigin(0.5); closeBtn.setInteractive({ useHandCursor: true }); closeBtn.on('pointerover', () => closeBtn.setScale(1.1)); closeBtn.on('pointerout', () => closeBtn.setScale(1.0)); closeBtn.on('pointerdown', () => this.toggleSettingsMenu()); this.settingsContainer.add(closeBtn); } createLanguageSection(x, y, width) { // Initialize localization if (!window.i18n) { window.i18n = new LocalizationSystem(); } const sectionTitle = this.add.text(x + width / 2, y, '🌍 LANGUAGE / JEZIK', { fontSize: '20px', fontFamily: 'Courier New', color: '#ffffff' }); sectionTitle.setOrigin(0.5, 0); this.settingsContainer.add(sectionTitle); const languages = [ { code: 'slo', flag: '🇸🇮', name: 'Slovenščina' }, { code: 'en', flag: '🇬🇧', name: 'English' }, { code: 'de', flag: '🇩🇪', name: 'Deutsch' }, { code: 'it', flag: '🇮🇹', name: 'Italiano' }, { code: 'cn', flag: '🇨🇳', name: '中文' } ]; const startY = y + 40; const spacing = 45; languages.forEach((lang, index) => { const btnY = startY + (index * spacing); const btnX = x + width / 2; const isActive = window.i18n.getCurrentLanguage() === lang.code; const btn = this.add.text(btnX, btnY, `${lang.flag} ${lang.name}`, { fontSize: '18px', fontFamily: 'Courier New', color: isActive ? '#00ff41' : '#ffffff', backgroundColor: isActive ? '#333333' : '#222222', padding: { x: 15, y: 8 } }); btn.setOrigin(0.5); btn.setInteractive({ useHandCursor: true }); btn.on('pointerover', () => { btn.setScale(1.05); if (!isActive) btn.setBackgroundColor('#444444'); }); btn.on('pointerout', () => { btn.setScale(1.0); if (!isActive) btn.setBackgroundColor('#222222'); }); btn.on('pointerdown', () => { window.i18n.setLanguage(lang.code); // Refresh settings menu to update colors this.toggleSettingsMenu(); this.toggleSettingsMenu(); }); this.settingsContainer.add(btn); }); } createVolumeSection(x, y, width) { const sectionTitle = this.add.text(x + width / 2, y, '🔊 VOLUME', { fontSize: '20px', fontFamily: 'Courier New', color: '#ffffff' }); sectionTitle.setOrigin(0.5, 0); this.settingsContainer.add(sectionTitle); // Placeholder text const placeholder = this.add.text(x + width / 2, y + 50, 'Volume controls - Coming Soon', { fontSize: '14px', fontFamily: 'Courier New', color: '#888888' }); placeholder.setOrigin(0.5, 0); this.settingsContainer.add(placeholder); } togglePauseMenu() { if (this.pauseMenuActive) { // Close pause menu if (this.pauseMenuContainer) { this.pauseMenuContainer.destroy(); this.pauseMenuContainer = null; } this.pauseMenuActive = false; // Resume game if (this.gameScene) { this.gameScene.physics.resume(); } } else { // Open pause menu this.createPauseMenu(); this.pauseMenuActive = true; // Pause game if (this.gameScene) { this.gameScene.physics.pause(); } } } createPauseMenu() { const width = this.cameras.main.width; const height = this.cameras.main.height; // Initialize i18n if not available if (!window.i18n) { window.i18n = new LocalizationSystem(); } this.pauseMenuContainer = this.add.container(0, 0); this.pauseMenuContainer.setScrollFactor(0); this.pauseMenuContainer.setDepth(15000); // Semi-transparent background const bg = this.add.rectangle(0, 0, width, height, 0x000000, 0.85); bg.setOrigin(0); this.pauseMenuContainer.add(bg); // Panel const panelW = 500; const panelH = 550; const panelX = width / 2 - panelW / 2; const panelY = height / 2 - panelH / 2; const panel = this.add.rectangle(panelX, panelY, panelW, panelH, 0x1a1a2e, 1); panel.setOrigin(0); panel.setStrokeStyle(4, 0x00ff41); this.pauseMenuContainer.add(panel); // Title const title = this.add.text(width / 2, panelY + 40, '⏸️ ' + window.i18n.t('pause.title', 'PAUSED'), { fontSize: '48px', fontFamily: 'Courier New', color: '#00ff41', fontStyle: 'bold' }); title.setOrigin(0.5); this.pauseMenuContainer.add(title); // Menu buttons const buttons = [ { label: window.i18n.t('pause.resume', '▶ Resume'), color: '#00ff41', action: () => this.togglePauseMenu() }, { label: window.i18n.t('pause.save', '💾 Save Game'), color: '#4477ff', action: () => this.saveGame() }, { label: window.i18n.t('pause.settings', '⚙️ Settings'), color: '#ffaa00', action: () => { this.togglePauseMenu(); this.toggleSettingsMenu(); } }, { label: window.i18n.t('pause.quit', '🚪 Quit to Menu'), color: '#ff4444', action: () => this.quitToMenu() } ]; const startY = panelY + 150; const spacing = 90; buttons.forEach((btn, index) => { const y = startY + (index * spacing); // Button background const btnBg = this.add.rectangle(width / 2, y, 400, 70, 0x2a2a4e, 1); btnBg.setStrokeStyle(3, btn.color); // Button text const btnText = this.add.text(width / 2, y, btn.label, { fontSize: '28px', fontFamily: 'Courier New', color: btn.color, fontStyle: 'bold' }); btnText.setOrigin(0.5); // Make interactive btnBg.setInteractive({ useHandCursor: true }); btnBg.on('pointerover', () => { btnBg.setFillStyle(0x3a3a6e); btnText.setScale(1.05); }); btnBg.on('pointerout', () => { btnBg.setFillStyle(0x2a2a4e); btnText.setScale(1.0); }); btnBg.on('pointerdown', () => { this.tweens.add({ targets: btnBg, alpha: 0.5, yoyo: true, duration: 100, onComplete: btn.action }); }); this.pauseMenuContainer.add(btnBg); this.pauseMenuContainer.add(btnText); }); // Hint text const hint = this.add.text(width / 2, panelY + panelH - 30, window.i18n.t('pause.hint', 'Press ESC to resume'), { fontSize: '16px', fontFamily: 'Courier New', color: '#888888' }); hint.setOrigin(0.5); this.pauseMenuContainer.add(hint); } saveGame() { console.log('💾 Saving game...'); // TODO: Implement save system // For now, show notification if (this.gameScene && this.gameScene.events) { this.gameScene.events.emit('show-floating-text', { x: this.cameras.main.width / 2, y: 100, text: window.i18n.t('save.success', '✓ Game Saved!'), color: '#00ff41' }); } // Close pause menu after saving setTimeout(() => { this.togglePauseMenu(); }, 1000); } quitToMenu() { console.log('🚪 Quitting to main menu...'); // Confirm dialog const confirm = window.confirm(window.i18n.t('quit.confirm', 'Quit to main menu? Unsaved progress will be lost.')); if (confirm) { // Stop all scenes this.scene.stop('GameScene'); this.scene.stop('UIScene'); // Start menu this.scene.start('StoryScene'); } } // ========== NEW: ZOMBIE & FARM STATS PANELS ========== createZombieStatsPanel() { const panelWidth = 220; const panelHeight = 140; const x = 20; const y = 120; // Below player stats // Container this.zombieStatsContainer = this.add.container(x, y); this.zombieStatsContainer.setDepth(1000); // Background const bg = this.add.graphics(); bg.fillStyle(0x1a1a2e, 0.9); bg.fillRect(0, 0, panelWidth, panelHeight); bg.lineStyle(2, 0x8a2be2, 0.8); // Purple border bg.strokeRect(0, 0, panelWidth, panelHeight); this.zombieStatsContainer.add(bg); // Title const title = this.add.text(panelWidth / 2, 15, '🧟 ZOMBIE WORKER', { fontSize: '14px', fontFamily: 'Courier New', fill: '#8a2be2', fontStyle: 'bold' }).setOrigin(0.5); this.zombieStatsContainer.add(title); // Stats Text this.zombieNameText = this.add.text(10, 35, 'Name: Worker #1', { fontSize: '12px', fill: '#ffffff' }); this.zombieStatsContainer.add(this.zombieNameText); this.zombieTaskText = this.add.text(10, 55, 'Task: IDLE', { fontSize: '12px', fill: '#ffff00' }); this.zombieStatsContainer.add(this.zombieTaskText); this.zombieLevelText = this.add.text(10, 75, 'Level: 1 (0/100 XP)', { fontSize: '12px', fill: '#00ff00' }); this.zombieStatsContainer.add(this.zombieLevelText); // Energy Bar const energyLabel = this.add.text(10, 95, 'ENERGY:', { fontSize: '11px', fill: '#aaaaaa' }); this.zombieStatsContainer.add(energyLabel); this.zombieEnergyBar = this.drawMiniBar(10, 110, panelWidth - 20, 15, 0x00aaff, 100); this.zombieStatsContainer.add(this.zombieEnergyBar.bg); this.zombieStatsContainer.add(this.zombieEnergyBar.fill); // Initially visible this.zombieStatsContainer.setVisible(true); } createFarmStatsPanel() { const panelWidth = 220; const panelHeight = 120; const x = 20; const y = 280; // Below zombie stats // Container this.farmStatsContainer = this.add.container(x, y); this.farmStatsContainer.setDepth(1000); // Background const bg = this.add.graphics(); bg.fillStyle(0x1a2e1a, 0.9); bg.fillRect(0, 0, panelWidth, panelHeight); bg.lineStyle(2, 0x00ff00, 0.8); // Green border bg.strokeRect(0, 0, panelWidth, panelHeight); this.farmStatsContainer.add(bg); // Title const title = this.add.text(panelWidth / 2, 15, '🌾 FARM STATS', { fontSize: '14px', fontFamily: 'Courier New', fill: '#00ff00', fontStyle: 'bold' }).setOrigin(0.5); this.farmStatsContainer.add(title); // Stats this.farmCropsPlantedText = this.add.text(10, 40, 'Crops Planted: 0', { fontSize: '12px', fill: '#ffffff' }); this.farmStatsContainer.add(this.farmCropsPlantedText); this.farmCropsHarvestedText = this.add.text(10, 60, 'Total Harvested: 0', { fontSize: '12px', fill: '#ffff00' }); this.farmStatsContainer.add(this.farmCropsHarvestedText); this.farmGoldEarnedText = this.add.text(10, 80, 'Gold Earned: 0g', { fontSize: '12px', fill: '#ffd700' }); this.farmStatsContainer.add(this.farmGoldEarnedText); this.farmDaysText = this.add.text(10, 100, 'Days Farmed: 0', { fontSize: '12px', fill: '#aaaaaa' }); this.farmStatsContainer.add(this.farmDaysText); } drawMiniBar(x, y, width, height, color, initialPercent = 100) { // Background const bg = this.add.graphics(); bg.fillStyle(0x000000, 0.5); bg.fillRect(x, y, width, height); bg.lineStyle(1, 0xffffff, 0.3); bg.strokeRect(x, y, width, height); // Fill const fill = this.add.graphics(); fill.fillStyle(color, 1); const maxWidth = width - 4; const currentWidth = (maxWidth * initialPercent) / 100; fill.fillRect(x + 2, y + 2, currentWidth, height - 4); return { bg, fill, x, y, width, height, color }; } setMiniBarValue(bar, percent) { percent = Phaser.Math.Clamp(percent, 0, 100); bar.fill.clear(); bar.fill.fillStyle(bar.color, 1); const maxWidth = bar.width - 4; const currentWidth = (maxWidth * percent) / 100; bar.fill.fillRect(bar.x + 2, bar.y + 2, currentWidth, bar.height - 4); } // Update methods (call from update()) updateZombieStats(zombie) { if (!zombie || !this.zombieStatsContainer) return; this.zombieStatsContainer.setVisible(true); if (this.zombieNameText) this.zombieNameText.setText(`Name: ${zombie.name || 'Worker #1'}`); if (this.zombieTaskText) this.zombieTaskText.setText(`Task: ${zombie.task || 'IDLE'}`); if (this.zombieLevelText) this.zombieLevelText.setText(`Level: ${zombie.level || 1} (${zombie.xp || 0}/100 XP)`); const energy = zombie.energy !== undefined ? zombie.energy : 100; if (this.zombieEnergyBar) this.setMiniBarValue(this.zombieEnergyBar, energy); } updateFarmStats(stats) { if (!stats || !this.farmStatsContainer) return; if (this.farmCropsPlantedText) this.farmCropsPlantedText.setText(`Crops Planted: ${stats.cropsPlanted || 0}`); if (this.farmCropsHarvestedText) this.farmCropsHarvestedText.setText(`Total Harvested: ${stats.totalHarvested || 0}`); if (this.farmGoldEarnedText) this.farmGoldEarnedText.setText(`Gold Earned: ${stats.goldEarned || 0}g`); if (this.farmDaysText) this.farmDaysText.setText(`Days Farmed: ${stats.daysFarmed || 0}`); } createMinimap() { const size = 150; // Minimap size const x = 20; const y = this.scale.height - size - 80; // Above inventory bar // Container this.minimapContainer = this.add.container(x, y); this.minimapContainer.setDepth(1000); // Background const bg = this.add.graphics(); bg.fillStyle(0x000000, 0.7); bg.fillRect(0, 0, size, size); bg.lineStyle(2, 0x00ff41, 0.8); bg.strokeRect(0, 0, size, size); this.minimapContainer.add(bg); // Title const title = this.add.text(size / 2, -15, 'MINIMAP', { fontSize: '12px', fontFamily: 'Courier New', fill: '#00ff41', fontStyle: 'bold' }).setOrigin(0.5); this.minimapContainer.add(title); // Minimap graphics (will be updated in update()) this.minimapGraphics = this.add.graphics(); this.minimapGraphics.setPosition(x, y); this.minimapGraphics.setDepth(1001); // Player dot this.minimapPlayerDot = this.add.circle(size / 2, size / 2, 3, 0xffff00); this.minimapContainer.add(this.minimapPlayerDot); console.log('🗺️ Minimap created!'); } updateMinimap() { if (!this.minimapGraphics || !this.gameScene || !this.gameScene.player) return; const size = 150; const worldSize = 100; // Assuming 100x100 world const scale = size / worldSize; this.minimapGraphics.clear(); // Get player position const player = this.gameScene.player; const playerGridX = Math.floor(player.gridX || 0); const playerGridY = Math.floor(player.gridY || 0); // Draw terrain (simplified) if (this.gameScene.terrainSystem && this.gameScene.terrainSystem.tiles) { const viewRange = 20; // Show 20x20 tiles around player const startX = Math.max(0, playerGridX - viewRange); const endX = Math.min(worldSize, playerGridX + viewRange); const startY = Math.max(0, playerGridY - viewRange); const endY = Math.min(worldSize, playerGridY + viewRange); for (let gx = startX; gx < endX; gx++) { for (let gy = startY; gy < endY; gy++) { const tile = this.gameScene.terrainSystem.getTile(gx, gy); if (!tile) continue; let color = 0x44aa44; // Default green (grass) if (tile.type === 'water') color = 0x0088ff; else if (tile.type === 'sand') color = 0xffdd88; else if (tile.type === 'stone') color = 0x888888; else if (tile.type === 'farm') color = 0x8B4513; // Brown for farm const mx = (gx - playerGridX + viewRange) * (size / (viewRange * 2)); const my = (gy - playerGridY + viewRange) * (size / (viewRange * 2)); this.minimapGraphics.fillStyle(color, 0.8); this.minimapGraphics.fillRect( this.minimapContainer.x + mx, this.minimapContainer.y + my, size / (viewRange * 2), size / (viewRange * 2) ); } } } // Draw NPCs (red dots) if (this.gameScene.npcs) { this.gameScene.npcs.forEach(npc => { const dx = npc.gridX - playerGridX; const dy = npc.gridY - playerGridY; const viewRange = 20; if (Math.abs(dx) < viewRange && Math.abs(dy) < viewRange) { const mx = (dx + viewRange) * (size / (viewRange * 2)); const my = (dy + viewRange) * (size / (viewRange * 2)); const color = npc.isTamed ? 0x00ff00 : 0xff0000; // Green if tamed, red if hostile this.minimapGraphics.fillStyle(color, 1); this.minimapGraphics.fillCircle( this.minimapContainer.x + mx, this.minimapContainer.y + my, 2 ); } }); } } }