/** * NPCShopSystem.js * ================ * KRVAVA Ε½ETEV - NPC Trading & Shop System (Phase 38) * * Features: * - 4 NPC shop types (Blacksmith, Baker, Trader, Healer) * - Shop UI with buy/sell * - Dynamic pricing * - Stock management * - Relationship discounts * * @author NovaFarma Team * @date 2025-12-23 */ class NPCShopSystem { constructor(scene) { this.scene = scene; // Shop registry this.shops = new Map(); this.currentShop = null; // Shop UI this.shopContainer = null; this.isShopOpen = false; // Player inventory reference this.playerInventory = null; this.playerZlatniki = 0; console.log('πŸ›’ NPCShopSystem initialized'); // Register all shops this.registerShops(); // Create shop UI this.createShopUI(); } /** * Register all NPC shops */ registerShops() { const shops = [ { id: 'blacksmith', name: 'Kovač (Blacksmith)', npc: 'Ivan the Blacksmith', icon: 'βš’οΈ', location: { x: 200, y: 200 }, inventory: [ // Tools { id: 'iron_axe', name: 'Iron Axe', price: 200, stock: 5, category: 'tools', locked: true }, { id: 'iron_pickaxe', name: 'Iron Pickaxe', price: 200, stock: 5, category: 'tools', locked: true }, { id: 'iron_hoe', name: 'Iron Hoe', price: 150, stock: 5, category: 'tools', locked: true }, { id: 'watering_can', name: 'Watering Can', price: 100, stock: 10, category: 'tools' }, // Weapons { id: 'iron_sword', name: 'Iron Sword', price: 500, stock: 3, category: 'weapons', locked: true }, { id: 'steel_sword', name: 'Steel Sword', price: 1000, stock: 2, category: 'weapons', locked: true }, { id: 'crossbow', name: 'Crossbow', price: 800, stock: 2, category: 'weapons', locked: true }, // Armor { id: 'leather_armor', name: 'Leather Armor', price: 300, stock: 5, category: 'armor' }, { id: 'iron_armor', name: 'Iron Armor', price: 800, stock: 3, category: 'armor' } ], buyback: ['iron_ore', 'steel_bar', 'scrap_metal'] }, { id: 'baker', name: 'Pekarica (Baker)', npc: 'Maria the Baker', icon: '🍞', location: { x: 250, y: 200 }, inventory: [ // Food { id: 'bread', name: 'Bread', price: 10, stock: 50, category: 'food' }, { id: 'cheese', name: 'Cheese', price: 20, stock: 30, category: 'food' }, { id: 'apple_pie', name: 'Apple Pie', price: 50, stock: 20, category: 'food' }, { id: 'cake', name: 'Cake', price: 100, stock: 10, category: 'food' }, // Recipes { id: 'recipe_cookies', name: 'Cookie Recipe', price: 200, stock: 1, category: 'recipes' }, { id: 'recipe_pizza', name: 'Pizza Recipe', price: 300, stock: 1, category: 'recipes' }, // Ingredients { id: 'flour', name: 'Flour', price: 15, stock: 100, category: 'ingredients' }, { id: 'sugar', name: 'Sugar', price: 20, stock: 80, category: 'ingredients' }, { id: 'yeast', name: 'Yeast', price: 10, stock: 50, category: 'ingredients' } ], buyback: ['wheat', 'milk', 'eggs', 'berries'] }, { id: 'trader', name: 'Trgovec (General Trader)', npc: 'Gregor the Trader', icon: 'πŸ’°', location: { x: 300, y: 200 }, inventory: [ // Seeds { id: 'wheat_seeds', name: 'Wheat Seeds', price: 5, stock: 200, category: 'seeds' }, { id: 'corn_seeds', name: 'Corn Seeds', price: 8, stock: 150, category: 'seeds' }, { id: 'tomato_seeds', name: 'Tomato Seeds', price: 10, stock: 100, category: 'seeds' }, { id: 'strawberry_seeds', name: 'Strawberry Seeds', price: 15, stock: 80, category: 'seeds' }, { id: 'cannabis_seeds', name: 'Cannabis Seeds', price: 20, stock: 50, category: 'seeds' }, // Materials { id: 'wood', name: 'Wood', price: 10, stock: 500, category: 'materials' }, { id: 'stone', name: 'Stone', price: 15, stock: 300, category: 'materials' }, { id: 'clay', name: 'Clay', price: 20, stock: 200, category: 'materials' }, // Special { id: 'saddle', name: 'Saddle', price: 500, stock: 2, category: 'special' }, { id: 'bouquet', name: 'Bouquet', price: 100, stock: 10, category: 'special' }, { id: 'mermaid_pendant', name: 'Mermaid Pendant', price: 5000, stock: 1, category: 'special' } ], buyback: ['crops', 'foraged_items', 'fish'] }, { id: 'healer', name: 'Zdravnik (Healer)', npc: 'Dr. Ana Kovač', icon: 'βš•οΈ', location: { x: 350, y: 200 }, inventory: [ // Potions { id: 'health_potion', name: 'Health Potion', price: 50, stock: 50, category: 'potions' }, { id: 'stamina_potion', name: 'Stamina Potion', price: 40, stock: 50, category: 'potions' }, { id: 'antidote', name: 'Antidote', price: 30, stock: 30, category: 'potions' }, { id: 'cure_infection', name: 'Cure Infection', price: 200, stock: 10, category: 'potions' }, // Research { id: 'cure_research_1', name: 'Cure Research Notes I', price: 1000, stock: 1, category: 'research' }, { id: 'cure_research_2', name: 'Cure Research Notes II', price: 2000, stock: 1, category: 'research' }, // Medical supplies { id: 'bandage', name: 'Bandage', price: 15, stock: 100, category: 'medical' }, { id: 'medicine', name: 'Medicine', price: 80, stock: 30, category: 'medical' } ], buyback: ['herbs', 'mushrooms', 'zombie_samples', 'cannabis', 'cannabis_buds'] } ]; shops.forEach(shop => this.shops.set(shop.id, shop)); console.log(`βœ… Registered ${this.shops.size} NPC shops`); } /** * Create shop UI */ createShopUI() { const width = this.scene.cameras.main.width; const height = this.scene.cameras.main.height; // Main container this.shopContainer = this.scene.add.container(width / 2, height / 2); this.shopContainer.setScrollFactor(0); this.shopContainer.setDepth(10000); this.shopContainer.setVisible(false); // Background const bg = this.scene.add.rectangle(0, 0, 900, 600, 0x1a1a1a, 0.95); bg.setStrokeStyle(3, 0xFFD700); this.shopContainer.add(bg); // Title (will be updated) this.shopTitle = this.scene.add.text(0, -280, 'πŸ›’ SHOP', { fontSize: '32px', fontFamily: 'Arial', color: '#FFD700', fontStyle: 'bold' }); this.shopTitle.setOrigin(0.5); this.shopContainer.add(this.shopTitle); // Close button const closeBtn = this.scene.add.text(430, -280, '❌', { fontSize: '24px', cursor: 'pointer' }); closeBtn.setInteractive(); closeBtn.on('pointerdown', () => this.closeShop()); this.shopContainer.add(closeBtn); // Player money display this.moneyText = this.scene.add.text(-430, -250, 'πŸ’° 0 Zlatniki', { fontSize: '18px', fontFamily: 'Arial', color: '#FFD700' }); this.shopContainer.add(this.moneyText); // Category tabs this.createCategoryTabs(); // Item list container this.itemListContainer = this.scene.add.container(0, 0); this.shopContainer.add(this.itemListContainer); console.log('βœ… Shop UI created'); } /** * Create category tabs */ createCategoryTabs() { const categories = ['all', 'tools', 'weapons', 'food', 'seeds', 'potions']; const tabWidth = 120; const startX = -400; const y = -200; categories.forEach((category, index) => { const tab = this.scene.add.rectangle( startX + (index * tabWidth), y, 110, 40, 0x2d2d2d ); tab.setStrokeStyle(2, 0x666666); tab.setInteractive(); tab.on('pointerdown', () => this.filterByCategory(category)); const label = this.scene.add.text( startX + (index * tabWidth), y, category.toUpperCase(), { fontSize: '14px', fontFamily: 'Arial', color: '#ffffff' } ); label.setOrigin(0.5); this.shopContainer.add(tab); this.shopContainer.add(label); }); } /** * Open shop */ openShop(shopId) { const shop = this.shops.get(shopId); if (!shop) { console.error(`Shop ${shopId} not found!`); return false; } this.currentShop = shop; this.isShopOpen = true; // Update title this.shopTitle.setText(`${shop.icon} ${shop.name}`); // Update money this.updateMoneyDisplay(); // Display items this.displayShopItems(shop.inventory); // Show container this.shopContainer.setVisible(true); console.log(`πŸ›’ Opened ${shop.name}`); return true; } /** * Close shop */ closeShop() { this.isShopOpen = false; this.currentShop = null; this.shopContainer.setVisible(false); console.log('πŸ›’ Shop closed'); } /** * Display shop items */ displayShopItems(items, filter = 'all') { // Clear previous items this.itemListContainer.removeAll(true); // Filter items let filteredItems = items; if (filter !== 'all') { filteredItems = items.filter(item => item.category === filter); } // Display items (max 10 visible, scrollable) const itemHeight = 50; const startY = -150; filteredItems.slice(0, 10).forEach((item, index) => { const y = startY + (index * itemHeight); // Item background const itemBg = this.scene.add.rectangle(-400, y, 850, 45, 0x2d2d2d, 0.8); itemBg.setStrokeStyle(1, 0x444444); this.itemListContainer.add(itemBg); // Item name const nameText = this.scene.add.text(-380, y - 10, item.name, { fontSize: '16px', fontFamily: 'Arial', color: '#ffffff' }); this.itemListContainer.add(nameText); // Stock const stockText = this.scene.add.text(-380, y + 10, `Stock: ${item.stock}`, { fontSize: '12px', fontFamily: 'Arial', color: '#888888' }); this.itemListContainer.add(stockText); // Price const priceText = this.scene.add.text(200, y, `${item.price} Ε½`, { fontSize: '18px', fontFamily: 'Arial', color: '#FFD700', fontStyle: 'bold' }); priceText.setOrigin(0.5); this.itemListContainer.add(priceText); // Buy button const isLocked = item.locked && !this.isShopRestored(this.currentShop.id); const buyColor = isLocked ? 0x666666 : 0x228B22; const buyBtn = this.scene.add.rectangle(350, y, 100, 35, buyColor); buyBtn.setStrokeStyle(2, isLocked ? 0x999999 : 0x32CD32); buyBtn.setInteractive(); buyBtn.on('pointerdown', () => { if (isLocked) { this.showNotification({ title: 'Shop Ruined', text: `Restore the shop to unlock this item!`, icon: '🏚️' }); } else { this.buyItem(item); } }); this.itemListContainer.add(buyBtn); const buyText = this.scene.add.text(350, y, isLocked ? 'LOCKED' : 'BUY', { fontSize: '14px', fontFamily: 'Arial', color: '#ffffff', fontStyle: 'bold' }); buyText.setOrigin(0.5); this.itemListContainer.add(buyText); }); } isShopRestored(shopId) { if (!this.scene.townRestorationSystem) return true; // Fallback if system missing // Map shopId to buildingId const mapping = { 'blacksmith': 'jakob_shop', // or whichever ID correctly matches TownRestorationSystem 'baker': 'lena_bakery', 'healer': 'dr_chen_clinic' }; const buildingId = mapping[shopId]; if (!buildingId) return true; // General trader is always open const building = this.scene.townRestorationSystem.buildings.get(buildingId); return building ? building.isRestored : false; } /** * Filter by category */ filterByCategory(category) { if (!this.currentShop) return; this.displayShopItems(this.currentShop.inventory, category); } /** * Buy item */ buyItem(item) { // Check stock if (item.stock <= 0) { this.showNotification({ title: 'Out of Stock', text: `${item.name} is out of stock!`, icon: 'πŸ“¦' }); return false; } // Calculate price with relationship discount const finalPrice = this.calculatePrice(item.price); // Check if player can afford if (this.playerZlatniki < finalPrice) { this.showNotification({ title: 'Not Enough Money', text: `Need ${finalPrice}Ε½ to buy ${item.name}!`, icon: 'πŸ’°' }); return false; } // Purchase! this.playerZlatniki -= finalPrice; item.stock--; // TODO: Add item to player inventory console.log(`βœ… Purchased: ${item.name} for ${finalPrice}Ε½`); // Update UI this.updateMoneyDisplay(); this.displayShopItems(this.currentShop.inventory); this.showNotification({ title: 'Purchase Complete!', text: `Bought ${item.name} for ${finalPrice}Ε½!`, icon: 'βœ…' }); return true; } /** * Calculate price with discounts */ calculatePrice(basePrice) { // TODO: Apply relationship discounts // For now, return base price return basePrice; } /** * Update money display */ updateMoneyDisplay() { this.moneyText.setText(`πŸ’° ${this.playerZlatniki} Zlatniki`); } /** * Set player money */ setPlayerMoney(amount) { this.playerZlatniki = amount; this.updateMoneyDisplay(); } /** * Get shop info */ getShopInfo(shopId) { return this.shops.get(shopId); } /** * Get all shops */ getAllShops() { return Array.from(this.shops.values()); } /** * Restock shop */ restockShop(shopId) { const shop = this.shops.get(shopId); if (!shop) return false; shop.inventory.forEach(item => { item.stock = Math.min(item.stock + 5, 100); // Restock +5, max 100 }); console.log(`πŸ“¦ ${shop.name} restocked!`); return true; } /** * Helper: Show notification */ showNotification(notification) { console.log(`πŸ“’ ${notification.icon} ${notification.title}: ${notification.text}`); const ui = this.scene.scene.get('UIScene'); if (ui && ui.showNotification) { ui.showNotification(notification); } } }