Files
novafarma/EMERGENCY_SYSTEMS_RECOVERY/NPCShopSystem.js
2026-01-16 02:43:46 +01:00

504 lines
16 KiB
JavaScript

/**
* 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);
}
}
}