/** * 👥 NPC POPULATION SYSTEM * Spawns and manages NPCs in structures across the world * - Biome-specific NPCs * - Dialog system * - Trading functionality * - Quest giving */ class NPCPopulationSystem { constructor(scene) { this.scene = scene; // All NPCs in the world this.npcs = []; // NPC types per biome this.npcTypes = { 'Grassland': ['farmer', 'blacksmith', 'merchant', 'guard'], 'Forest': ['hunter', 'herbalist', 'ranger', 'druid'], 'Desert': ['nomad', 'treasure_hunter', 'merchant', 'archaeologist'], 'Mountain': ['miner', 'dwarf', 'geologist', 'mountaineer'], 'Swamp': ['witch', 'alchemist', 'hermit', 'shaman'] }; // Dialog templates this.dialogs = { 'farmer': [ "Welcome to my farm! Need any seeds?", "The harvest this year is bountiful!", "I sell the best wheat in the land!" ], 'merchant': [ "Looking to buy or sell? I've got the best deals!", "Welcome, traveler! Check out my wares!", "Gold for goods, goods for gold!" ], 'hunter': [ "The forest is full of game. Happy hunting!", "Watch out for the wolves at night.", "I can sell you some arrows if you need them." ], 'nomad': [ "The desert holds many secrets...", "Water is more valuable than gold here.", "I've traveled far and wide, seen many things." ], 'miner': [ "These mountains are rich with ore!", "I can sell you some iron if you need it.", "Watch your step in those caves!" ], 'witch': [ "Potions and hexes, what do you need?", "The swamp holds ancient magic...", "I can brew you something special." ] }; // Trade goods per NPC type this.tradeGoods = { 'farmer': [ { item: 'wheat_seeds', price: 10, stock: 50 }, { item: 'wheat', price: 5, stock: 100 }, { item: 'bread', price: 15, stock: 20 } ], 'merchant': [ { item: 'wood', price: 8, stock: 200 }, { item: 'stone', price: 6, stock: 150 }, { item: 'iron_ore', price: 20, stock: 50 } ], 'blacksmith': [ { item: 'iron_sword', price: 150, stock: 5 }, { item: 'iron_pickaxe', price: 100, stock: 10 }, { item: 'iron_axe', price: 120, stock: 8 } ], 'hunter': [ { item: 'arrow', price: 5, stock: 100 }, { item: 'bow', price: 80, stock: 3 }, { item: 'meat', price: 20, stock: 30 } ] }; console.log('👥 NPCPopulationSystem initialized'); } // Populate structures with NPCs populateStructures(structureSystem) { if (!structureSystem) return; let npcsSpawned = 0; // Spawn NPCs in structures (30% chance) for (const structure of structureSystem.structures) { if (Math.random() < 0.3) { const npcType = this.selectNPCType(structure.biome); this.spawnNPC(structure.x, structure.y, npcType, structure.biome); npcsSpawned++; } } // Always spawn special NPCs at landmarks for (const landmark of structureSystem.landmarks) { this.spawnNPC(landmark.x, landmark.y, 'quest_giver', landmark.type, true); npcsSpawned++; } console.log(`✅ Spawned ${npcsSpawned} NPCs in structures`); } // Select NPC type for biome selectNPCType(biome) { const types = this.npcTypes[biome] || this.npcTypes['Grassland']; return types[Math.floor(Math.random() * types.length)]; } // Spawn single NPC spawnNPC(x, y, type, biome, isQuestGiver = false) { const npc = { x, y, type, biome, isQuestGiver, dialogIndex: 0, hasQuest: isQuestGiver, questCompleted: false, sprite: null }; this.npcs.push(npc); return npc; } // Create NPC sprite (called when chunk loads) createNPCSprite(npc, chunk) { if (npc.sprite) return; // Already has sprite const worldX = npc.x * 48 + 24; const worldY = npc.y * 48 + 24; // Create simple circle sprite for NPC const color = this.getNPCColor(npc.type); const sprite = this.scene.add.circle(worldX, worldY, 12, color); sprite.setDepth(10 + worldY); // Add name label const label = this.scene.add.text(worldX, worldY - 20, npc.type, { fontSize: '12px', color: '#ffffff', backgroundColor: '#000000', padding: { x: 4, y: 2 } }); label.setOrigin(0.5); label.setDepth(10 + worldY); // Quest marker for quest givers if (npc.isQuestGiver && !npc.questCompleted) { const questMarker = this.scene.add.text(worldX, worldY - 35, '!', { fontSize: '24px', color: '#FFD700', fontStyle: 'bold' }); questMarker.setOrigin(0.5); questMarker.setDepth(10 + worldY); npc.questMarker = questMarker; if (chunk) chunk.sprites.push(questMarker); } npc.sprite = sprite; npc.label = label; if (chunk) { chunk.sprites.push(sprite); chunk.sprites.push(label); } } // Get NPC color getNPCColor(type) { const colors = { 'farmer': 0x8B4513, 'merchant': 0xDAA520, 'blacksmith': 0x696969, 'hunter': 0x228B22, 'nomad': 0xD2691E, 'miner': 0x708090, 'witch': 0x9370DB, 'quest_giver': 0xFFD700 }; return colors[type] || 0x808080; } // Check for nearby NPCs update(playerX, playerY) { let nearestNPC = null; let minDist = 3; for (const npc of this.npcs) { const dist = Math.sqrt((npc.x - playerX) ** 2 + (npc.y - playerY) ** 2); if (dist < minDist) { minDist = dist; nearestNPC = npc; } } if (nearestNPC && !this.currentNPC) { this.showTalkPrompt(nearestNPC); this.currentNPC = nearestNPC; } else if (!nearestNPC && this.currentNPC) { this.hideTalkPrompt(); this.currentNPC = null; } } // Show prompt to talk showTalkPrompt(npc) { if (this.talkPrompt) return; const promptText = npc.isQuestGiver ? '💬 Press T to talk (QUEST AVAILABLE)' : `💬 Press T to talk to ${npc.type}`; this.talkPrompt = this.scene.add.text( this.scene.cameras.main.centerX, this.scene.cameras.main.height - 100, promptText, { fontSize: '24px', color: npc.isQuestGiver ? '#FFD700' : '#ffffff', backgroundColor: '#000000', padding: { x: 20, y: 10 } } ); this.talkPrompt.setOrigin(0.5); this.talkPrompt.setScrollFactor(0); this.talkPrompt.setDepth(10000); } // Hide talk prompt hideTalkPrompt() { if (this.talkPrompt) { this.talkPrompt.destroy(); this.talkPrompt = null; } } // Talk to NPC (T key) talkToNPC() { if (!this.currentNPC) return; const npc = this.currentNPC; // Get dialog const dialogs = this.dialogs[npc.type] || ["Hello, traveler!"]; const dialog = dialogs[npc.dialogIndex % dialogs.length]; npc.dialogIndex++; // Show dialog box this.showDialog(npc, dialog); } // Show dialog UI showDialog(npc, text) { // Close existing dialog if (this.dialogBox) { this.dialogBox.destroy(); this.dialogText.destroy(); this.dialogBox = null; } // Create dialog box const centerX = this.scene.cameras.main.centerX; const centerY = this.scene.cameras.main.height - 150; this.dialogBox = this.scene.add.rectangle( centerX, centerY, 600, 120, 0x000000, 0.8 ); this.dialogBox.setStrokeStyle(3, 0xFFFFFF); this.dialogBox.setScrollFactor(0); this.dialogBox.setDepth(10001); this.dialogText = this.scene.add.text( centerX, centerY - 30, `${npc.type}:\n${text}`, { fontSize: '20px', color: '#ffffff', align: 'center', wordWrap: { width: 550 } } ); this.dialogText.setOrigin(0.5, 0); this.dialogText.setScrollFactor(0); this.dialogText.setDepth(10002); // Auto-close after 4 seconds this.scene.time.delayedCall(4000, () => { if (this.dialogBox) { this.dialogBox.destroy(); this.dialogText.destroy(); this.dialogBox = null; } }); } // Get statistics getStats() { const byBiome = {}; for (const npc of this.npcs) { byBiome[npc.biome] = (byBiome[npc.biome] || 0) + 1; } return { totalNPCs: this.npcs.length, questGivers: this.npcs.filter(n => n.isQuestGiver).length, byBiome }; } destroy() { this.hideTalkPrompt(); if (this.dialogBox) { this.dialogBox.destroy(); this.dialogText.destroy(); } this.npcs.forEach(npc => { if (npc.sprite) npc.sprite.destroy(); if (npc.label) npc.label.destroy(); if (npc.questMarker) npc.questMarker.destroy(); }); this.npcs = []; } }