- NEW: Flat2DTerrainSystem.js (375 lines) - NEW: map2d_data.js procedural map (221 lines) - MODIFIED: GameScene async create, 2D terrain integration - MODIFIED: Player.js flat 2D positioning - MODIFIED: game.js disabled pixelArt for smooth rendering - FIXED: 15+ bugs (updateCulling, isometric conversions, grid lines) - ADDED: Phase 28 to TASKS.md - DOCS: DNEVNIK.md session summary Result: Working flat 2D game with Stardew Valley style! Time: 5.5 hours
381 lines
12 KiB
JavaScript
381 lines
12 KiB
JavaScript
// Crafting UI - Visual panel for crafting interface
|
|
class CraftingUI {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
this.craftingSystem = scene.craftingSystem;
|
|
|
|
this.isOpen = false;
|
|
this.currentCategory = 'all';
|
|
this.selectedRecipe = null;
|
|
|
|
// UI elements
|
|
this.container = null;
|
|
this.panel = null;
|
|
this.categoryButtons = [];
|
|
this.recipeList = [];
|
|
this.detailsPanel = null;
|
|
|
|
this.createUI();
|
|
this.hide();
|
|
|
|
// Listen for crafting events
|
|
this.setupEventListeners();
|
|
|
|
console.log('🎨 CraftingUI initialized');
|
|
}
|
|
|
|
createUI() {
|
|
const width = this.scene.cameras.main.width;
|
|
const height = this.scene.cameras.main.height;
|
|
|
|
// Main container
|
|
this.container = this.scene.add.container(0, 0);
|
|
this.container.setDepth(10000);
|
|
this.container.setScrollFactor(0);
|
|
|
|
// Semi-transparent background overlay
|
|
const overlay = this.scene.add.rectangle(0, 0, width, height, 0x000000, 0.7);
|
|
overlay.setOrigin(0);
|
|
overlay.setInteractive();
|
|
this.container.add(overlay);
|
|
|
|
// Main panel (centered)
|
|
const panelWidth = 700;
|
|
const panelHeight = 500;
|
|
const panelX = width / 2 - panelWidth / 2;
|
|
const panelY = height / 2 - panelHeight / 2;
|
|
|
|
this.panel = this.scene.add.rectangle(panelX, panelY, panelWidth, panelHeight, 0x2a1810);
|
|
this.panel.setOrigin(0);
|
|
this.panel.setStrokeStyle(3, 0x4a3820);
|
|
this.container.add(this.panel);
|
|
|
|
// Title
|
|
const title = this.scene.add.text(width / 2, panelY + 20, '🛠️ CRAFTING', {
|
|
fontSize: '32px',
|
|
fontFamily: 'Georgia, serif',
|
|
fill: '#f4e4c1',
|
|
stroke: '#2d1b00',
|
|
strokeThickness: 4
|
|
}).setOrigin(0.5);
|
|
this.container.add(title);
|
|
|
|
// Close button
|
|
const closeBtn = this.scene.add.text(panelX + panelWidth - 40, panelY + 20, '✖', {
|
|
fontSize: '24px',
|
|
fill: '#ff6666'
|
|
}).setOrigin(0.5);
|
|
closeBtn.setInteractive({ useHandCursor: true });
|
|
closeBtn.on('pointerdown', () => this.hide());
|
|
closeBtn.on('pointerover', () => closeBtn.setScale(1.2));
|
|
closeBtn.on('pointerout', () => closeBtn.setScale(1.0));
|
|
this.container.add(closeBtn);
|
|
|
|
// Category buttons (top)
|
|
this.createCategoryButtons(panelX, panelY + 60, panelWidth);
|
|
|
|
// Recipe list (left side)
|
|
this.createRecipeListPanel(panelX + 10, panelY + 120, 300, 350);
|
|
|
|
// Details panel (right side)
|
|
this.createDetailsPanel(panelX + 320, panelY + 120, 370, 350);
|
|
}
|
|
|
|
createCategoryButtons(x, y, width) {
|
|
const categories = this.craftingSystem.categories;
|
|
const buttonWidth = (width - 40) / categories.length;
|
|
|
|
categories.forEach((category, index) => {
|
|
const btnX = x + 20 + (index * buttonWidth);
|
|
|
|
const btn = this.scene.add.rectangle(btnX, y, buttonWidth - 10, 40, 0x4a3820);
|
|
btn.setOrigin(0, 0.5);
|
|
btn.setStrokeStyle(2, 0x6a5840);
|
|
btn.setInteractive({ useHandCursor: true });
|
|
|
|
const text = this.scene.add.text(btnX + buttonWidth / 2 - 5, y, `${category.icon} ${category.name}`, {
|
|
fontSize: '14px',
|
|
fontFamily: 'Arial',
|
|
fill: '#d4c4a1'
|
|
}).setOrigin(0.5);
|
|
|
|
btn.on('pointerdown', () => this.selectCategory(category.id));
|
|
btn.on('pointerover', () => {
|
|
btn.setFillStyle(0x6a5840);
|
|
text.setScale(1.05);
|
|
});
|
|
btn.on('pointerout', () => {
|
|
btn.setFillStyle(0x4a3820);
|
|
text.setScale(1.0);
|
|
});
|
|
|
|
this.container.add(btn);
|
|
this.container.add(text);
|
|
|
|
this.categoryButtons.push({ category: category.id, btn, text });
|
|
});
|
|
}
|
|
|
|
createRecipeListPanel(x, y, width, height) {
|
|
// Background
|
|
const bg = this.scene.add.rectangle(x, y, width, height, 0x1a1410);
|
|
bg.setOrigin(0);
|
|
bg.setStrokeStyle(2, 0x4a3820);
|
|
this.container.add(bg);
|
|
|
|
// Title
|
|
const title = this.scene.add.text(x + width / 2, y + 10, 'Recipes', {
|
|
fontSize: '18px',
|
|
fontFamily: 'Georgia, serif',
|
|
fill: '#f4e4c1'
|
|
}).setOrigin(0.5, 0);
|
|
this.container.add(title);
|
|
|
|
// Store panel bounds for recipe items
|
|
this.recipePanelBounds = { x, y: y + 40, width, height: height - 40 };
|
|
}
|
|
|
|
createDetailsPanel(x, y, width, height) {
|
|
// Background
|
|
const bg = this.scene.add.rectangle(x, y, width, height, 0x1a1410);
|
|
bg.setOrigin(0);
|
|
bg.setStrokeStyle(2, 0x4a3820);
|
|
this.container.add(bg);
|
|
|
|
this.detailsPanelBounds = { x, y, width, height };
|
|
}
|
|
|
|
selectCategory(categoryId) {
|
|
this.currentCategory = categoryId;
|
|
this.refreshRecipeList();
|
|
|
|
// Update button styles
|
|
this.categoryButtons.forEach(({ category, btn, text }) => {
|
|
if (category === categoryId) {
|
|
btn.setFillStyle(0x6a5840);
|
|
text.setStyle({ fill: '#ffffff' });
|
|
} else {
|
|
btn.setFillStyle(0x4a3820);
|
|
text.setStyle({ fill: '#d4c4a1' });
|
|
}
|
|
});
|
|
}
|
|
|
|
refreshRecipeList() {
|
|
// Clear existing recipe items
|
|
this.recipeList.forEach(item => item.destroy());
|
|
this.recipeList = [];
|
|
|
|
// Get recipes for current category
|
|
const recipes = this.craftingSystem.getUnlockedRecipes(this.currentCategory);
|
|
|
|
const { x, y, width } = this.recipePanelBounds;
|
|
const itemHeight = 50;
|
|
|
|
recipes.forEach((recipe, index) => {
|
|
const itemY = y + (index * itemHeight);
|
|
|
|
// Check if can craft
|
|
const canCraft = this.craftingSystem.canCraft(recipe.id);
|
|
|
|
// Background
|
|
const bg = this.scene.add.rectangle(x + 5, itemY, width - 10, itemHeight - 5, 0x2a1810);
|
|
bg.setOrigin(0);
|
|
bg.setStrokeStyle(1, canCraft.canCraft ? 0x4a9d5f : 0x6a5840);
|
|
bg.setInteractive({ useHandCursor: true });
|
|
|
|
// Recipe name
|
|
const name = this.scene.add.text(x + 15, itemY + itemHeight / 2, recipe.name, {
|
|
fontSize: '16px',
|
|
fontFamily: 'Arial',
|
|
fill: canCraft.canCraft ? '#ffffff' : '#888888'
|
|
}).setOrigin(0, 0.5);
|
|
|
|
// Hover effect
|
|
bg.on('pointerover', () => {
|
|
bg.setFillStyle(0x3a2820);
|
|
name.setScale(1.05);
|
|
});
|
|
bg.on('pointerout', () => {
|
|
bg.setFillStyle(0x2a1810);
|
|
name.setScale(1.0);
|
|
});
|
|
|
|
// Click to select
|
|
bg.on('pointerdown', () => this.selectRecipe(recipe));
|
|
|
|
this.container.add(bg);
|
|
this.container.add(name);
|
|
|
|
this.recipeList.push(bg, name);
|
|
});
|
|
}
|
|
|
|
selectRecipe(recipe) {
|
|
this.selectedRecipe = recipe;
|
|
this.showRecipeDetails();
|
|
}
|
|
|
|
showRecipeDetails() {
|
|
if (!this.selectedRecipe) return;
|
|
|
|
// Clear existing details
|
|
if (this.detailsContent) {
|
|
this.detailsContent.forEach(item => item.destroy());
|
|
}
|
|
this.detailsContent = [];
|
|
|
|
const { x, y, width } = this.detailsPanelBounds;
|
|
const recipe = this.selectedRecipe;
|
|
|
|
// Recipe name
|
|
const name = this.scene.add.text(x + width / 2, y + 20, recipe.name, {
|
|
fontSize: '24px',
|
|
fontFamily: 'Georgia, serif',
|
|
fill: '#f4e4c1',
|
|
stroke: '#2d1b00',
|
|
strokeThickness: 2
|
|
}).setOrigin(0.5, 0);
|
|
this.detailsContent.push(name);
|
|
|
|
// Description
|
|
const desc = this.scene.add.text(x + 20, y + 60, recipe.description, {
|
|
fontSize: '14px',
|
|
fontFamily: 'Arial',
|
|
fill: '#d4c4a1',
|
|
wordWrap: { width: width - 40 }
|
|
});
|
|
this.detailsContent.push(desc);
|
|
|
|
// Ingredients title
|
|
const ingredTitle = this.scene.add.text(x + 20, y + 120, 'Required Ingredients:', {
|
|
fontSize: '16px',
|
|
fontFamily: 'Arial',
|
|
fill: '#f4e4c1',
|
|
fontStyle: 'bold'
|
|
});
|
|
this.detailsContent.push(ingredTitle);
|
|
|
|
// Ingredients list
|
|
const inventory = this.scene.inventorySystem;
|
|
let ingredY = y + 150;
|
|
|
|
for (const [itemId, required] of Object.entries(recipe.ingredients)) {
|
|
const has = inventory ? inventory.getItemCount(itemId) : 0;
|
|
const hasEnough = has >= required;
|
|
|
|
const text = this.scene.add.text(x + 30, ingredY, `• ${itemId}: ${has}/${required}`, {
|
|
fontSize: '14px',
|
|
fontFamily: 'Arial',
|
|
fill: hasEnough ? '#4a9d5f' : '#ff6666'
|
|
});
|
|
this.detailsContent.push(text);
|
|
ingredY += 25;
|
|
}
|
|
|
|
// Result
|
|
const resultText = this.scene.add.text(x + 20, ingredY + 20, `Produces: ${recipe.result.quantity}x ${recipe.result.item}`, {
|
|
fontSize: '16px',
|
|
fontFamily: 'Arial',
|
|
fill: '#4aa3d0',
|
|
fontStyle: 'bold'
|
|
});
|
|
this.detailsContent.push(resultText);
|
|
|
|
// Craft button
|
|
const canCraft = this.craftingSystem.canCraft(recipe.id);
|
|
const btnY = y + 320;
|
|
|
|
const craftBtn = this.scene.add.rectangle(x + width / 2, btnY, 200, 40, canCraft.canCraft ? 0x4a9d5f : 0x666666);
|
|
craftBtn.setStrokeStyle(2, 0x000000);
|
|
|
|
const btnText = this.scene.add.text(x + width / 2, btnY, canCraft.canCraft ? '🔨 CRAFT' : '❌ Cannot Craft', {
|
|
fontSize: '18px',
|
|
fontFamily: 'Arial',
|
|
fill: '#ffffff',
|
|
fontStyle: 'bold'
|
|
}).setOrigin(0.5);
|
|
|
|
if (canCraft.canCraft) {
|
|
craftBtn.setInteractive({ useHandCursor: true });
|
|
craftBtn.on('pointerover', () => {
|
|
craftBtn.setFillStyle(0x5abd6f);
|
|
btnText.setScale(1.1);
|
|
});
|
|
craftBtn.on('pointerout', () => {
|
|
craftBtn.setFillStyle(0x4a9d5f);
|
|
btnText.setScale(1.0);
|
|
});
|
|
craftBtn.on('pointerdown', () => this.craftSelectedRecipe());
|
|
}
|
|
|
|
this.detailsContent.push(craftBtn, btnText);
|
|
|
|
// Add all to container
|
|
this.detailsContent.forEach(item => this.container.add(item));
|
|
}
|
|
|
|
craftSelectedRecipe() {
|
|
if (!this.selectedRecipe) return;
|
|
|
|
const success = this.craftingSystem.craftItem(this.selectedRecipe.id);
|
|
|
|
if (success) {
|
|
// Refresh UI
|
|
this.refreshRecipeList();
|
|
this.showRecipeDetails();
|
|
|
|
console.log(`✅ Crafting started: ${this.selectedRecipe.name}`);
|
|
}
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Listen for inventory changes to update recipe availability
|
|
this.scene.events.on('inventory-changed', () => {
|
|
if (this.isOpen) {
|
|
this.refreshRecipeList();
|
|
if (this.selectedRecipe) {
|
|
this.showRecipeDetails();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Listen for craft completion
|
|
this.scene.events.on('craft-complete', (data) => {
|
|
if (this.isOpen) {
|
|
this.refreshRecipeList();
|
|
if (this.selectedRecipe) {
|
|
this.showRecipeDetails();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
show() {
|
|
this.isOpen = true;
|
|
this.container.setVisible(true);
|
|
this.selectCategory(this.currentCategory);
|
|
console.log('🛠️ Crafting UI opened');
|
|
}
|
|
|
|
hide() {
|
|
this.isOpen = false;
|
|
this.container.setVisible(false);
|
|
console.log('🛠️ Crafting UI closed');
|
|
}
|
|
|
|
toggle() {
|
|
if (this.isOpen) {
|
|
this.hide();
|
|
} else {
|
|
this.show();
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
if (this.container) {
|
|
this.container.destroy();
|
|
}
|
|
}
|
|
}
|