phase 11 koncano

This commit is contained in:
2025-12-08 14:01:41 +01:00
parent 07f0752d81
commit f3d476e843
21 changed files with 1503 additions and 200 deletions

View File

@@ -43,6 +43,29 @@ class UIScene extends Phaser.Scene {
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: '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.' }
];
}
// ... (rest of class) ...
@@ -64,90 +87,201 @@ class UIScene extends Phaser.Scene {
}
createCraftingMenu() {
const w = 300;
const h = 250;
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
// Bg
// 1. Background (Main Window)
const bg = this.add.graphics();
bg.fillStyle(0x222222, 0.95);
bg.fillStyle(0x1a1a2e, 0.98); // Dark Blue theme
bg.fillRect(-w / 2, -h / 2, w, h);
bg.lineStyle(2, 0x888888, 1);
bg.lineStyle(2, 0x4e4e6e, 1);
bg.strokeRect(-w / 2, -h / 2, w, h);
this.craftingContainer.add(bg);
// Title
const title = this.add.text(0, -h / 2 + 20, 'CRAFTING', { fontSize: '24px', fontStyle: 'bold', color: '#ffffff' }).setOrigin(0.5);
// 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);
// Recipes
const recipes = [
{ name: 'Axe', code: '1', req: '5 Wood', type: 'axe', cost: { wood: 5 } },
{ name: 'Pickaxe', code: '2', req: '5 Wood, 2 Stone', type: 'pickaxe', cost: { wood: 5, stone: 2 } },
{ name: 'Hoe', code: '3', req: '3 Wood, 2 Stone', type: 'hoe', cost: { wood: 3, stone: 2 } },
{ name: 'Sword', code: '4', req: '10 Wood, 5 Stone', type: 'sword', cost: { wood: 10, stone: 5 } }
];
// 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);
recipes.forEach((r, i) => {
const rowY = -h / 2 + 70 + (i * 40);
// 2. Layout Containers
// Left Panel (List)
this.recipeListContainer = this.add.container(-w / 2 + 20, -h / 2 + 70);
this.craftingContainer.add(this.recipeListContainer);
// Text
const txt = this.add.text(-w / 2 + 20, rowY, `[${r.code}] ${r.name} (${r.req})`, {
fontSize: '16px', color: '#eeeeee'
});
this.craftingContainer.add(txt);
// Right Panel (Details)
this.detailsContainer = this.add.container(20, -h / 2 + 70);
this.craftingContainer.add(this.detailsContainer);
// Button Logic (Keyboard 1-4 works too via GameScene? No, let's localize input)
});
// Instruction
const instr = this.add.text(0, h / 2 - 20, 'Press number keys to craft', { fontSize: '12px', color: '#aaaaaa' }).setOrigin(0.5);
this.craftingContainer.add(instr);
// Input listener for crafting
this.input.keyboard.on('keydown', (e) => {
if (!this.craftingContainer.visible) return;
const key = e.key;
const recipe = recipes.find(r => r.code === key);
if (recipe) {
this.tryCraft(recipe);
}
});
// 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;
// Check cost
if (recipe.cost.wood && !inv.hasItem('wood', recipe.cost.wood)) {
console.log('Craft fail: Wood');
return; // Add UI feedback "Need Wood"
}
if (recipe.cost.stone && !inv.hasItem('stone', recipe.cost.stone)) {
console.log('Craft fail: Stone');
return;
// 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
if (recipe.cost.wood) inv.removeItem('wood', recipe.cost.wood);
if (recipe.cost.stone) inv.removeItem('stone', recipe.cost.stone);
for (const [item, count] of Object.entries(recipe.req)) {
inv.removeItem(item, count);
}
// Add Item
inv.addItem(recipe.type, 1);
inv.addItem(recipe.id, recipe.output);
console.log(`Crafted ${recipe.name}!`);
// Flash effect
this.cameras.main.flash(200, 0, 255, 0); // Green flash
this.craftingContainer.setVisible(false);
// Sound & Visuals
if (this.gameScene.soundManager) this.gameScene.soundManager.playSuccess();
this.cameras.main.flash(200, 255, 255, 255);
// Refresh UI
this.refreshRecipeList();
this.showRecipeDetails(recipe);
}
resize(gameSize) {
@@ -975,4 +1109,340 @@ class UIScene extends Phaser.Scene {
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'
});
}
}
}