1545 lines
56 KiB
JavaScript
1545 lines
56 KiB
JavaScript
class UIScene extends Phaser.Scene {
|
|
constructor() {
|
|
super({ key: 'UIScene' });
|
|
}
|
|
|
|
create() {
|
|
console.log('🖥️ UIScene: Initialized!');
|
|
|
|
// Pridobi reference na GameScene podatke (ko bodo na voljo)
|
|
this.gameScene = this.scene.get('GameScene');
|
|
|
|
// Setup UI Container
|
|
// Shared Overlay (DayNight + Weather)
|
|
// Rendered here to be screen-space (HUD) and ignore zoom
|
|
this.overlayGraphics = this.add.graphics();
|
|
this.overlayGraphics.setDepth(-1000); // Behind UI elements
|
|
|
|
this.drawUI();
|
|
|
|
this.createInventoryBar();
|
|
this.createGoldDisplay();
|
|
this.createVirtualJoystick();
|
|
this.createClock();
|
|
// this.createDebugInfo();
|
|
this.createSettingsButton();
|
|
|
|
// Listeners for Age
|
|
if (this.gameScene) {
|
|
this.gameScene.events.on('update-age-ui', (data) => this.updateAge(data.gen, data.age));
|
|
}
|
|
|
|
// Resize event
|
|
this.scale.on('resize', this.resize, this);
|
|
|
|
// Crafting Menu (C)
|
|
this.input.keyboard.on('keydown-C', () => {
|
|
this.toggleCraftingMenu();
|
|
});
|
|
|
|
// Save (F5)
|
|
this.input.keyboard.on('keydown-F5', () => {
|
|
if (this.gameScene) this.gameScene.saveGame();
|
|
});
|
|
|
|
// Factory Reset (F8) - Fix za "zginla drevesa"
|
|
this.input.keyboard.on('keydown-F8', () => {
|
|
console.log('🔥 FACTORY RESET - Clearing Save & Reloading...');
|
|
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: 'bucket', name: 'Iron Bucket', req: { 'iron_bar': 2 }, output: 1, type: 'tool', desc: 'Used for milking cows.' },
|
|
{ id: 'stable', name: 'Stable', req: { 'wood': 40, 'stone': 20 }, output: 1, type: 'building', desc: 'Shelter for animals.', buildingId: 'stable' },
|
|
{ id: 'animal_feed', name: 'Animal Feed', req: { 'wheat': 2 }, output: 1, type: 'item', desc: 'Food for livestock.' },
|
|
{ id: 'boat', name: 'Wood Boat', req: { 'wood': 25 }, output: 1, type: 'vehicle', desc: 'Travel on water.' },
|
|
{ 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.' }
|
|
];
|
|
|
|
// OXYGEN EVENTS
|
|
if (this.gameScene) {
|
|
this.gameScene.events.on('update-inventory', () => this.updateInventoryUI());
|
|
this.gameScene.events.on('show-floating-text', (data) => this.showFloatingText(data));
|
|
this.gameScene.events.on('update-oxygen', (data) => this.updateOxygen(data));
|
|
this.gameScene.events.on('hide-oxygen', () => this.hideOxygen());
|
|
}
|
|
|
|
this.createOxygenBar();
|
|
}
|
|
|
|
// ... (rest of class) ...
|
|
|
|
toggleCraftingMenu() {
|
|
if (!this.craftingContainer) this.createCraftingMenu();
|
|
this.craftingContainer.setVisible(!this.craftingContainer.visible);
|
|
// Pause/Resume game?
|
|
// if (this.gameScene) this.gameScene.physics.world.isPaused = this.craftingContainer.visible;
|
|
}
|
|
|
|
toggleCrafting() {
|
|
this.toggleCraftingMenu();
|
|
}
|
|
|
|
toggleInventory() {
|
|
// Za zdaj odpre crafting meni, ker nimamo ločenega "Big Inventory"
|
|
this.toggleCraftingMenu();
|
|
}
|
|
|
|
createCraftingMenu() {
|
|
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
|
|
|
|
// 1. Background (Main Window)
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x1a1a2e, 0.98); // Dark Blue theme
|
|
bg.fillRect(-w / 2, -h / 2, w, h);
|
|
bg.lineStyle(2, 0x4e4e6e, 1);
|
|
bg.strokeRect(-w / 2, -h / 2, w, h);
|
|
this.craftingContainer.add(bg);
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
|
|
// 2. Layout Containers
|
|
// Left Panel (List)
|
|
this.recipeListContainer = this.add.container(-w / 2 + 20, -h / 2 + 70);
|
|
this.craftingContainer.add(this.recipeListContainer);
|
|
|
|
// Right Panel (Details)
|
|
this.detailsContainer = this.add.container(20, -h / 2 + 70);
|
|
this.craftingContainer.add(this.detailsContainer);
|
|
|
|
// 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;
|
|
|
|
// 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
|
|
for (const [item, count] of Object.entries(recipe.req)) {
|
|
inv.removeItem(item, count);
|
|
}
|
|
|
|
// Add Item
|
|
inv.addItem(recipe.id, recipe.output);
|
|
|
|
console.log(`Crafted ${recipe.name}!`);
|
|
|
|
// 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) {
|
|
this.width = gameSize.width;
|
|
this.height = gameSize.height;
|
|
|
|
this.cameras.main.setViewport(0, 0, this.width, this.height);
|
|
|
|
// Re-create UI elements at new positions
|
|
this.createClock();
|
|
this.createGoldDisplay();
|
|
this.createInventoryBar();
|
|
this.createInventoryBar();
|
|
this.createDebugInfo();
|
|
this.createSettingsButton();
|
|
|
|
// Refresh data
|
|
if (this.gameScene) {
|
|
if (this.gameScene.inventorySystem) {
|
|
this.updateInventory(this.gameScene.inventorySystem.slots);
|
|
}
|
|
// Clock/Gold update automatically in next frame
|
|
// Overlay fixes itself via Systems
|
|
if (this.overlayGraphics) {
|
|
this.overlayGraphics.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
drawUI() {
|
|
const x = 20;
|
|
const y = 20;
|
|
const width = 150; // Zmanjšana širina (300 -> 150)
|
|
const height = 15; // Zmanjšana višina (30 -> 15)
|
|
const padding = 10;
|
|
|
|
// 1. Health Bar
|
|
this.healthBar = this.drawBar(x, y, width, height, 0xff0000, 100, 'HP');
|
|
|
|
// 2. Hunger Bar
|
|
this.hungerBar = this.drawBar(x, y + height + padding, width, height, 0xff8800, 80, 'HUN');
|
|
|
|
// 3. Thirst Bar
|
|
this.thirstBar = this.drawBar(x, y + (height + padding) * 2, width, height, 0x0088ff, 90, 'H2O');
|
|
|
|
// 4. XP Bar
|
|
this.XPBar = this.drawBar(x, y + (height + padding) * 3, width, height, 0xFFD700, 0, 'XP');
|
|
|
|
// 5. Level Display
|
|
this.LevelDisplay = this.add.text(x, y + (height + padding) * 4, 'LV: 1', {
|
|
fontSize: '18px', fontFamily: 'Courier New', fill: '#FFD700', fontStyle: 'bold'
|
|
});
|
|
}
|
|
|
|
drawBar(x, y, width, height, color, initialPercent = 100, label = '') {
|
|
// Label
|
|
if (label) {
|
|
this.add.text(x, y - 12, label, { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff' });
|
|
}
|
|
|
|
// Background
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x000000, 0.5);
|
|
bg.fillRect(x, y, width, height);
|
|
bg.lineStyle(2, 0xffffff, 0.2);
|
|
bg.strokeRect(x, y, width, height);
|
|
|
|
// Fill
|
|
const fill = this.add.graphics();
|
|
// Initial draw
|
|
fill.fillStyle(color, 1);
|
|
const maxWidth = width - 4;
|
|
const currentWidth = (maxWidth * initialPercent) / 100;
|
|
fill.fillRect(x + 2, y + 2, currentWidth, height - 4);
|
|
|
|
return { bg, fill, x, y, width, height, color };
|
|
}
|
|
|
|
setBarValue(bar, percent) {
|
|
// Clamp 0-100
|
|
percent = Phaser.Math.Clamp(percent, 0, 100);
|
|
|
|
bar.fill.clear();
|
|
bar.fill.fillStyle(bar.color, 1);
|
|
|
|
const maxWidth = bar.width - 4;
|
|
const currentWidth = (maxWidth * percent) / 100;
|
|
|
|
bar.fill.fillRect(bar.x + 2, bar.y + 2, currentWidth, bar.height - 4);
|
|
}
|
|
|
|
createInventoryBar() {
|
|
// Clean up
|
|
if (this.inventorySlots) {
|
|
this.inventorySlots.forEach(slot => {
|
|
if (slot.itemSprite) slot.itemSprite.destroy();
|
|
if (slot.itemText) slot.itemText.destroy();
|
|
if (slot.countText) slot.countText.destroy();
|
|
slot.destroy();
|
|
});
|
|
}
|
|
|
|
const slotCount = 9;
|
|
const slotSize = 48; // 48x48 sloti
|
|
const padding = 5;
|
|
|
|
const totalWidth = (slotCount * slotSize) + ((slotCount - 1) * padding);
|
|
const startX = (this.scale.width - totalWidth) / 2;
|
|
const startY = this.scale.height - slotSize - 20;
|
|
|
|
this.inventorySlots = [];
|
|
if (this.selectedSlot === undefined) this.selectedSlot = 0;
|
|
|
|
for (let i = 0; i < slotCount; i++) {
|
|
const x = startX + i * (slotSize + padding);
|
|
|
|
// Slot Background
|
|
const slot = this.add.graphics();
|
|
|
|
// Draw function to update style based on selection
|
|
slot.userData = { x, y: startY, size: slotSize, index: i };
|
|
this.drawSlot(slot, false);
|
|
|
|
// Add number text
|
|
this.add.text(x + 2, startY + 2, (i + 1).toString(), {
|
|
fontSize: '10px',
|
|
fontFamily: 'monospace',
|
|
fill: '#ffffff'
|
|
});
|
|
|
|
this.inventorySlots.push(slot);
|
|
}
|
|
|
|
// Select first one initially
|
|
this.selectSlot(0);
|
|
|
|
// Keyboard inputs 1-9
|
|
this.input.keyboard.on('keydown', (event) => {
|
|
const num = parseInt(event.key);
|
|
if (!isNaN(num) && num >= 1 && num <= 9) {
|
|
this.selectSlot(num - 1);
|
|
}
|
|
});
|
|
|
|
// Mouse scroll for inventory (optional)
|
|
this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
|
|
if (deltaY > 0) {
|
|
this.selectSlot((this.selectedSlot + 1) % slotCount);
|
|
} else if (deltaY < 0) {
|
|
this.selectSlot((this.selectedSlot - 1 + slotCount) % slotCount);
|
|
}
|
|
});
|
|
}
|
|
|
|
drawSlot(graphics, isSelected) {
|
|
const { x, y, size } = graphics.userData;
|
|
|
|
graphics.clear();
|
|
|
|
// Background
|
|
graphics.fillStyle(0x000000, 0.6);
|
|
graphics.fillRect(x, y, size, size);
|
|
|
|
// Border
|
|
if (isSelected) {
|
|
graphics.lineStyle(3, 0xffff00, 1); // Yellow thick border for selection
|
|
} else {
|
|
graphics.lineStyle(2, 0x888888, 0.5); // Grey thin border
|
|
}
|
|
graphics.strokeRect(x, y, size, size);
|
|
}
|
|
|
|
selectSlot(index) {
|
|
// Deselect ALL slots first
|
|
for (let i = 0; i < this.inventorySlots.length; i++) {
|
|
if (this.inventorySlots[i]) {
|
|
this.drawSlot(this.inventorySlots[i], false);
|
|
}
|
|
}
|
|
|
|
this.selectedSlot = index;
|
|
|
|
// Select new
|
|
if (this.inventorySlots[this.selectedSlot]) {
|
|
this.drawSlot(this.inventorySlots[this.selectedSlot], true);
|
|
}
|
|
}
|
|
|
|
updateInventory(slots) {
|
|
if (!this.inventorySlots) return;
|
|
|
|
for (let i = 0; i < this.inventorySlots.length; i++) {
|
|
const slotGraphics = this.inventorySlots[i];
|
|
|
|
// Clean up old visual elements
|
|
if (slotGraphics.itemSprite) {
|
|
slotGraphics.itemSprite.destroy();
|
|
slotGraphics.itemSprite = null;
|
|
}
|
|
if (slotGraphics.itemText) {
|
|
slotGraphics.itemText.destroy();
|
|
slotGraphics.itemText = null;
|
|
}
|
|
if (slotGraphics.countText) {
|
|
slotGraphics.countText.destroy();
|
|
slotGraphics.countText = null;
|
|
}
|
|
|
|
if (slots[i]) {
|
|
const { x, y, size } = slotGraphics.userData;
|
|
const type = slots[i].type;
|
|
const textureKey = `item_${type}`; // e.g., item_axe, item_wood
|
|
|
|
// Check if texture exists
|
|
if (this.textures.exists(textureKey)) {
|
|
// Draw Sprite
|
|
const sprite = this.add.sprite(x + size / 2, y + size / 2, textureKey);
|
|
|
|
// Larger scale for better visibility (fill most of the slot)
|
|
const scale = (size - 4) / Math.max(sprite.width, sprite.height);
|
|
sprite.setScale(scale * 1.2); // 20% bigger!
|
|
|
|
// Crisp 2D rendering
|
|
sprite.setTexture(textureKey);
|
|
|
|
slotGraphics.itemSprite = sprite;
|
|
} else {
|
|
// Fallback Text - bigger and bolder
|
|
const text = this.add.text(x + size / 2, y + size / 2,
|
|
type.substring(0, 3).toUpperCase(),
|
|
{ fontSize: '16px', align: 'center', color: '#ffff00', stroke: '#000', strokeThickness: 3, fontStyle: 'bold' }
|
|
).setOrigin(0.5);
|
|
slotGraphics.itemText = text;
|
|
}
|
|
|
|
// Draw Count (if > 1) - bigger and bolder
|
|
if (slots[i].count > 1) {
|
|
const countText = this.add.text(x + size - 2, y + size - 2,
|
|
slots[i].count.toString(),
|
|
{ fontSize: '14px', align: 'right', color: '#ffffff', stroke: '#000', strokeThickness: 3, fontStyle: 'bold' }
|
|
).setOrigin(1, 1);
|
|
slotGraphics.countText = countText;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
createClock() {
|
|
if (this.clockBg) this.clockBg.destroy();
|
|
if (this.clockText) this.clockText.destroy();
|
|
|
|
// Clock box top right
|
|
const x = this.scale.width - 150;
|
|
const y = 20;
|
|
|
|
// Background
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x000000, 0.5);
|
|
bg.fillRect(x, y, 130, 40);
|
|
bg.lineStyle(2, 0xffffff, 0.8);
|
|
bg.strokeRect(x, y, 130, 40);
|
|
|
|
this.clockBg = bg; // Save ref
|
|
|
|
this.clockText = this.add.text(x + 65, y + 20, 'Day 1 - 08:00', {
|
|
fontSize: '14px',
|
|
fontFamily: 'Courier New',
|
|
fill: '#ffffff',
|
|
fontStyle: 'bold'
|
|
});
|
|
this.clockText.setOrigin(0.5, 0.5);
|
|
}
|
|
|
|
createGoldDisplay() {
|
|
if (this.goldBg) this.goldBg.destroy();
|
|
if (this.goldText) this.goldText.destroy();
|
|
|
|
const x = this.scale.width - 150;
|
|
const y = 70; // Below clock
|
|
|
|
// Background
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0xDAA520, 0.2); // Goldish bg
|
|
bg.fillRect(x, y, 130, 30);
|
|
bg.lineStyle(2, 0xFFD700, 0.8);
|
|
bg.lineStyle(2, 0xFFD700, 0.8);
|
|
bg.strokeRect(x, y, 130, 30);
|
|
|
|
this.goldBg = bg; // Save ref
|
|
|
|
this.goldText = this.add.text(x + 65, y + 15, 'GOLD: 0', {
|
|
fontSize: '16px',
|
|
fontFamily: 'Courier New',
|
|
fill: '#FFD700', // Gold color
|
|
fontStyle: 'bold'
|
|
});
|
|
this.goldText.setOrigin(0.5, 0.5);
|
|
|
|
// GENERATION / AGE UI (Below Gold)
|
|
if (this.genText) this.genText.destroy();
|
|
this.genText = this.add.text(x + 65, y + 40, 'Gen: 1 | Age: 18', {
|
|
fontSize: '12px', color: '#AAAAAA'
|
|
}).setOrigin(0.5);
|
|
}
|
|
|
|
updateAge(gen, age) {
|
|
if (this.genText) this.genText.setText(`Gen: ${gen} | Age: ${age}`);
|
|
}
|
|
|
|
updateGold(amount) {
|
|
if (this.goldText) {
|
|
this.goldText.setText(`GOLD: ${amount}`);
|
|
}
|
|
}
|
|
|
|
createDebugInfo() {
|
|
if (this.debugText) this.debugText.destroy();
|
|
if (this.debugBg) this.debugBg.destroy();
|
|
|
|
const x = this.scale.width - 170;
|
|
const y = 120; // Below Gold and Clock area
|
|
|
|
// Background
|
|
this.debugBg = this.add.graphics();
|
|
this.debugBg.fillStyle(0x000000, 0.7);
|
|
this.debugBg.fillRect(x, y, 160, 70);
|
|
this.debugBg.setDepth(2999);
|
|
|
|
this.debugText = this.add.text(x + 10, y + 10, 'Waiting for stats...', {
|
|
fontSize: '12px',
|
|
fontFamily: 'monospace',
|
|
fill: '#ffffff',
|
|
stroke: '#000000',
|
|
strokeThickness: 2
|
|
});
|
|
this.debugText.setDepth(3000);
|
|
}
|
|
|
|
update() {
|
|
if (!this.gameScene) return;
|
|
|
|
// Sync HP Bar
|
|
if (this.gameScene.player && this.healthBar) {
|
|
const hp = this.gameScene.player.hp !== undefined ? this.gameScene.player.hp : 100;
|
|
const maxHp = this.gameScene.player.maxHp || 100;
|
|
this.setBarValue(this.healthBar, (hp / maxHp) * 100);
|
|
}
|
|
|
|
if (this.gameScene.statsSystem && this.hungerBar && this.thirstBar) {
|
|
const stats = this.gameScene.statsSystem;
|
|
this.setBarValue(this.hungerBar, stats.hunger);
|
|
this.setBarValue(this.thirstBar, stats.thirst);
|
|
}
|
|
}
|
|
toggleBuildMenu(isVisible) {
|
|
if (!this.buildMenuContainer) {
|
|
this.createBuildMenuInfo();
|
|
}
|
|
|
|
if (isVisible) {
|
|
this.buildMenuContainer.setVisible(true);
|
|
this.buildMenuContainer.y = -100; // Start off-screen
|
|
this.tweens.add({
|
|
targets: this.buildMenuContainer,
|
|
y: 100, // Target pos
|
|
duration: 300,
|
|
ease: 'Back.easeOut'
|
|
});
|
|
} else {
|
|
// Slide out (optional) or just hide
|
|
this.buildMenuContainer.setVisible(false);
|
|
}
|
|
}
|
|
|
|
createBuildMenuInfo() {
|
|
this.buildMenuContainer = this.add.container(this.width / 2, 100);
|
|
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x000000, 0.7);
|
|
bg.fillRect(-150, 0, 300, 100);
|
|
bg.lineStyle(2, 0x00FF00, 1);
|
|
bg.strokeRect(-150, 0, 300, 100);
|
|
|
|
this.buildMenuContainer.add(bg);
|
|
|
|
const title = this.add.text(0, 10, 'BUILD MODE [B]', { fontSize: '18px', fill: '#00FF00', fontStyle: 'bold' }).setOrigin(0.5, 0);
|
|
this.buildMenuContainer.add(title);
|
|
|
|
const info = this.add.text(0, 40,
|
|
'[1] Fence (2 Wood)\n[2] Wall (2 Stone)\n[3] House (20W 20S 50G)',
|
|
{ fontSize: '14px', fill: '#ffffff', align: 'center' }
|
|
).setOrigin(0.5, 0);
|
|
this.buildMenuContainer.add(info);
|
|
|
|
this.selectedBuildingText = this.add.text(0, 80, 'Selected: Fence', { fontSize: '14px', fill: '#FFFF00' }).setOrigin(0.5, 0);
|
|
this.buildMenuContainer.add(this.selectedBuildingText);
|
|
|
|
this.buildMenuContainer.setVisible(false);
|
|
}
|
|
|
|
updateBuildSelection(name) {
|
|
if (this.selectedBuildingText) {
|
|
this.selectedBuildingText.setText(`Selected: ${name.toUpperCase()}`);
|
|
}
|
|
}
|
|
showProjectMenu(ruinData, onContribute) {
|
|
if (!this.projectMenuContainer) {
|
|
this.createProjectMenu();
|
|
}
|
|
|
|
// Update info
|
|
const costText = `Req: ${ruinData.reqWood} Wood, ${ruinData.reqStone} Stone`;
|
|
this.projectInfoText.setText(`RESTORING RUINS\n${costText}`);
|
|
|
|
this.onContributeCallback = onContribute;
|
|
this.projectMenuContainer.setVisible(true);
|
|
this.projectMenuContainer.setDepth(10000);
|
|
}
|
|
|
|
createProjectMenu() {
|
|
this.projectMenuContainer = this.add.container(this.width / 2, this.height / 2);
|
|
|
|
// BG
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x222222, 0.9);
|
|
bg.fillRect(-150, -100, 300, 200);
|
|
bg.lineStyle(2, 0x00FFFF, 1);
|
|
bg.strokeRect(-150, -100, 300, 200);
|
|
this.projectMenuContainer.add(bg);
|
|
|
|
// Title
|
|
const title = this.add.text(0, -80, 'PROJECT: RESTORATION', { fontSize: '20px', fill: '#00FFFF', fontStyle: 'bold' }).setOrigin(0.5);
|
|
this.projectMenuContainer.add(title);
|
|
|
|
// Info
|
|
this.projectInfoText = this.add.text(0, -20, 'Req: ???', { fontSize: '16px', fill: '#ffffff', align: 'center' }).setOrigin(0.5);
|
|
this.projectMenuContainer.add(this.projectInfoText);
|
|
|
|
// Button
|
|
const btnBg = this.add.rectangle(0, 50, 200, 40, 0x00aa00);
|
|
btnBg.setInteractive();
|
|
btnBg.on('pointerdown', () => {
|
|
if (this.onContributeCallback) this.onContributeCallback();
|
|
// Close menu? Or keep open to see result?
|
|
// For now close
|
|
this.projectMenuContainer.setVisible(false);
|
|
});
|
|
this.projectMenuContainer.add(btnBg);
|
|
|
|
const btnText = this.add.text(0, 50, 'CONTRIBUTE', { fontSize: '18px', fill: '#ffffff' }).setOrigin(0.5);
|
|
this.projectMenuContainer.add(btnText);
|
|
|
|
// Close Button
|
|
const closeBtn = this.add.text(130, -90, 'X', { fontSize: '20px', fill: '#ff0000' }).setOrigin(0.5);
|
|
closeBtn.setInteractive();
|
|
closeBtn.on('pointerdown', () => this.projectMenuContainer.setVisible(false));
|
|
this.projectMenuContainer.add(closeBtn);
|
|
|
|
this.projectMenuContainer.setVisible(false);
|
|
}
|
|
|
|
showTradeMenu(inventorySystem) {
|
|
if (!this.tradeMenuContainer) {
|
|
this.createTradeMenu(inventorySystem);
|
|
}
|
|
|
|
this.updateTradeMenu(inventorySystem);
|
|
this.tradeMenuContainer.setVisible(true);
|
|
this.tradeMenuContainer.setDepth(10000);
|
|
}
|
|
|
|
createTradeMenu(inventorySystem) {
|
|
this.tradeMenuContainer = this.add.container(this.width / 2, this.height / 2);
|
|
|
|
// BG
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x222222, 0.95);
|
|
bg.fillRect(-200, -150, 400, 300);
|
|
bg.lineStyle(2, 0xFFD700, 1); // Gold border
|
|
bg.strokeRect(-200, -150, 400, 300);
|
|
this.tradeMenuContainer.add(bg);
|
|
|
|
// Title
|
|
const title = this.add.text(0, -130, 'MERCHANT SHOP', { fontSize: '24px', fill: '#FFD700', fontStyle: 'bold' }).setOrigin(0.5);
|
|
this.tradeMenuContainer.add(title);
|
|
|
|
// Close Button
|
|
const closeBtn = this.add.text(180, -140, 'X', { fontSize: '20px', fill: '#ff0000', fontStyle: 'bold' }).setOrigin(0.5);
|
|
closeBtn.setInteractive({ useHandCursor: true });
|
|
closeBtn.on('pointerdown', () => this.tradeMenuContainer.setVisible(false));
|
|
this.tradeMenuContainer.add(closeBtn);
|
|
|
|
// Content Container (for items)
|
|
this.tradeItemsContainer = this.add.container(0, 0);
|
|
this.tradeMenuContainer.add(this.tradeItemsContainer);
|
|
}
|
|
|
|
updateTradeMenu(inventorySystem) {
|
|
this.tradeItemsContainer.removeAll(true);
|
|
|
|
// Items to Sell (Player has -> Merchant wants)
|
|
// Hardcoded prices for now
|
|
const prices = {
|
|
'wheat': { price: 10, type: 'sell' },
|
|
'wood': { price: 2, type: 'sell' },
|
|
'seeds': { price: 5, type: 'buy' }
|
|
};
|
|
|
|
const startY = -80;
|
|
let index = 0;
|
|
|
|
// Header
|
|
const header = this.add.text(-180, startY, 'ITEM PRICE ACTION', { fontSize: '16px', fill: '#888888' });
|
|
this.tradeItemsContainer.add(header);
|
|
|
|
// 1. Sell Wheat
|
|
this.createTradeRow(inventorySystem, 'wheat', prices.wheat.price, 'SELL', index++, startY + 30);
|
|
// 2. Sell Wood
|
|
this.createTradeRow(inventorySystem, 'wood', prices.wood.price, 'SELL', index++, startY + 30);
|
|
// 3. Buy Seeds
|
|
this.createTradeRow(inventorySystem, 'seeds', prices.seeds.price, 'BUY', index++, startY + 30);
|
|
}
|
|
|
|
createTradeRow(inv, itemKey, price, action, index, yOffset) {
|
|
const y = yOffset + (index * 40);
|
|
|
|
// Name
|
|
const name = this.add.text(-180, y, itemKey.toUpperCase(), { fontSize: '18px', fill: '#ffffff' });
|
|
this.tradeItemsContainer.add(name);
|
|
|
|
// Price
|
|
const priceText = this.add.text(-50, y, `${price}g`, { fontSize: '18px', fill: '#FFD700' });
|
|
this.tradeItemsContainer.add(priceText);
|
|
|
|
// Button
|
|
const btnX = 100;
|
|
const btnBg = this.add.rectangle(btnX, y + 10, 80, 30, action === 'BUY' ? 0x008800 : 0x880000);
|
|
btnBg.setInteractive({ useHandCursor: true });
|
|
|
|
const btnLabel = this.add.text(btnX, y + 10, action, { fontSize: '16px', fill: '#ffffff' }).setOrigin(0.5);
|
|
|
|
btnBg.on('pointerdown', () => {
|
|
if (action === 'SELL') {
|
|
if (inv.hasItem(itemKey, 1)) {
|
|
inv.removeItem(itemKey, 1);
|
|
inv.gold += price;
|
|
inv.updateUI();
|
|
// Refresh visuals?
|
|
} else {
|
|
// Fail feedback
|
|
btnLabel.setText('NO ITEM');
|
|
this.scene.time.delayedCall(500, () => btnLabel.setText(action));
|
|
}
|
|
} else if (action === 'BUY') {
|
|
if (inv.gold >= price) {
|
|
inv.gold -= price;
|
|
inv.addItem(itemKey, 1);
|
|
inv.updateUI();
|
|
} else {
|
|
// Fail feedback
|
|
btnLabel.setText('NO GOLD');
|
|
this.scene.time.delayedCall(500, () => btnLabel.setText(action));
|
|
}
|
|
}
|
|
});
|
|
|
|
this.tradeItemsContainer.add(btnBg);
|
|
this.tradeItemsContainer.add(btnLabel);
|
|
}
|
|
|
|
// --- SETTINGS MENU ---
|
|
|
|
createSettingsButton() {
|
|
if (this.settingsBtn) this.settingsBtn.destroy();
|
|
this.settingsBtn = this.add.text(10, 10, '⚙️ SETTINGS', {
|
|
fontSize: '16px',
|
|
fill: '#ffffff',
|
|
backgroundColor: '#000000',
|
|
padding: { x: 5, y: 5 }
|
|
});
|
|
this.settingsBtn.setInteractive({ useHandCursor: true });
|
|
this.settingsBtn.on('pointerdown', () => this.toggleSettingsMenu());
|
|
}
|
|
|
|
toggleSettingsMenu() {
|
|
if (!this.settingsContainer) {
|
|
this.createSettingsMenu();
|
|
}
|
|
this.settingsContainer.setVisible(!this.settingsContainer.visible);
|
|
if (this.settingsContainer.visible) {
|
|
this.settingsContainer.setDepth(20000); // Always on top
|
|
}
|
|
}
|
|
|
|
createSettingsMenu() {
|
|
this.settingsContainer = this.add.container(this.width / 2, this.height / 2);
|
|
|
|
// BG
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x000000, 0.9);
|
|
bg.fillRect(-150, -120, 300, 240);
|
|
bg.lineStyle(2, 0x888888, 1);
|
|
bg.strokeRect(-150, -120, 300, 240);
|
|
this.settingsContainer.add(bg);
|
|
|
|
// Title
|
|
const title = this.add.text(0, -90, 'SETTINGS', { fontSize: '24px', fontStyle: 'bold', fill: '#ffffff' }).setOrigin(0.5);
|
|
this.settingsContainer.add(title);
|
|
|
|
// Close
|
|
const closeBtn = this.add.text(130, -110, 'X', { fontSize: '20px', fill: '#ff0000' }).setOrigin(0.5);
|
|
closeBtn.setInteractive({ useHandCursor: true });
|
|
closeBtn.on('pointerdown', () => this.toggleSettingsMenu());
|
|
this.settingsContainer.add(closeBtn);
|
|
|
|
// Options
|
|
let y = -40;
|
|
|
|
// 1. View Distance (Simple Toggle for now: Low/High)
|
|
this.createSettingToggle(0, y, 'VIEW DISTANCE',
|
|
() => this.gameScene.settings.viewDistance, // getter
|
|
(val) => { // setter
|
|
this.gameScene.settings.viewDistance = val;
|
|
// Apply immediately? Or next frame.
|
|
// TerrainSystem reads it in updateCulling? We need to ensure it does.
|
|
if (this.gameScene.terrainSystem) this.gameScene.terrainSystem.lastCullX = -9999; // Force update
|
|
},
|
|
['LOW', 'HIGH']
|
|
);
|
|
|
|
y += 50;
|
|
// 2. Weather Particles
|
|
this.createSettingToggle(0, y, 'PARTICLES',
|
|
() => this.gameScene.settings.particles,
|
|
(val) => {
|
|
this.gameScene.settings.particles = val;
|
|
// Restart rain if active
|
|
if (this.gameScene.weatherSystem && (this.gameScene.weatherSystem.currentWeather === 'rain' || this.gameScene.weatherSystem.currentWeather === 'storm')) {
|
|
this.gameScene.weatherSystem.startRain(this.gameScene.weatherSystem.currentWeather === 'storm');
|
|
}
|
|
},
|
|
['NONE', 'LOW', 'HIGH']
|
|
);
|
|
|
|
y += 50;
|
|
// 3. Shadows
|
|
this.createSettingToggle(0, y, 'SHADOWS',
|
|
() => this.gameScene.settings.shadows ? 'ON' : 'OFF',
|
|
(val) => {
|
|
this.gameScene.settings.shadows = (val === 'ON');
|
|
// Trigger redraw? Complex. For now just saves state.
|
|
// Maybe reload scene?
|
|
},
|
|
['ON', 'OFF']
|
|
);
|
|
}
|
|
|
|
createSettingToggle(x, y, label, getter, setter, options) {
|
|
const labelText = this.add.text(x - 80, y, label, { fontSize: '16px', fill: '#aaaaaa' }).setOrigin(1, 0.5);
|
|
this.settingsContainer.add(labelText);
|
|
|
|
const currentVal = getter();
|
|
const valueText = this.add.text(x + 50, y, currentVal, { fontSize: '16px', fill: '#ffffff', fontStyle: 'bold' }).setOrigin(0.5, 0.5);
|
|
this.settingsContainer.add(valueText);
|
|
|
|
// Click to cycle
|
|
const hitArea = this.add.rectangle(x + 50, y, 100, 30, 0xffffff, 0.1);
|
|
hitArea.setInteractive({ useHandCursor: true });
|
|
hitArea.on('pointerdown', () => {
|
|
const cur = getter();
|
|
let idx = options.indexOf(cur);
|
|
idx = (idx + 1) % options.length;
|
|
const next = options[idx];
|
|
setter(next);
|
|
valueText.setText(next);
|
|
});
|
|
this.settingsContainer.add(hitArea);
|
|
}
|
|
|
|
updateQuestTracker(quest) {
|
|
if (!this.questContainer) {
|
|
this.createQuestTracker();
|
|
}
|
|
|
|
if (!quest) {
|
|
this.questContainer.setVisible(false);
|
|
return;
|
|
}
|
|
|
|
this.questContainer.setVisible(true);
|
|
this.questTitle.setText(quest.title.toUpperCase());
|
|
|
|
let objText = '';
|
|
quest.objectives.forEach(obj => {
|
|
const status = obj.done ? '✅' : '⬜';
|
|
let desc = '';
|
|
if (obj.type === 'collect') desc = `${obj.item}: ${obj.current}/${obj.amount}`;
|
|
else if (obj.type === 'action') desc = `${obj.action}: ${obj.current}/${obj.amount}`;
|
|
else if (obj.type === 'kill') desc = `Slay ${obj.target}: ${obj.current}/${obj.amount}`;
|
|
|
|
objText += `${status} ${desc}\n`;
|
|
});
|
|
|
|
this.questObjectives.setText(objText);
|
|
}
|
|
|
|
createQuestTracker() {
|
|
const x = this.width - 240;
|
|
const y = 20;
|
|
|
|
this.questContainer = this.add.container(x, y);
|
|
|
|
// BG
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x000000, 0.6);
|
|
bg.fillRect(0, 0, 220, 100);
|
|
bg.lineStyle(2, 0xffaa00, 0.8);
|
|
bg.strokeRect(0, 0, 220, 100);
|
|
this.questContainer.add(bg);
|
|
this.questTrackerBg = bg; // ref to resize later if needed
|
|
|
|
// Title Header
|
|
const header = this.add.text(10, 5, 'CURRENT QUEST', { fontSize: '10px', fill: '#aaaaaa' });
|
|
this.questContainer.add(header);
|
|
|
|
// Title
|
|
this.questTitle = this.add.text(10, 20, 'Quest Title', {
|
|
fontSize: '16px', fill: '#ffaa00', fontStyle: 'bold'
|
|
});
|
|
this.questContainer.add(this.questTitle);
|
|
|
|
// Objectives
|
|
this.questObjectives = this.add.text(10, 45, 'Objectives...', {
|
|
fontSize: '14px', fill: '#ffffff', lineSpacing: 5
|
|
});
|
|
this.questContainer.add(this.questObjectives);
|
|
}
|
|
|
|
createVirtualJoystick() {
|
|
const x = 120;
|
|
const y = this.height - 120;
|
|
// Warning: this.height might not be updated on resize? Use this.scale.height or just initial.
|
|
// UIScene is usually overlay, so simple coords ok. But resize needs handling.
|
|
|
|
const r = 60;
|
|
|
|
// Visuals
|
|
this.joyBase = this.add.circle(x, y, r, 0xffffff, 0.1).setScrollFactor(0).setDepth(2000).setInteractive();
|
|
this.joyStick = this.add.circle(x, y, r / 2, 0xffffff, 0.5).setScrollFactor(0).setDepth(2001);
|
|
|
|
this.virtualJoystick = { up: false, down: false, left: false, right: false };
|
|
this.joyDragging = false;
|
|
|
|
// Events
|
|
this.input.on('pointermove', (pointer) => {
|
|
if (!this.joyDragging) return;
|
|
this.updateJoystick(pointer.x, pointer.y, x, y, r);
|
|
});
|
|
|
|
this.input.on('pointerup', () => {
|
|
if (this.joyDragging) {
|
|
this.joyDragging = false;
|
|
this.joyStick.setPosition(x, y);
|
|
this.virtualJoystick = { up: false, down: false, left: false, right: false };
|
|
}
|
|
});
|
|
|
|
this.joyBase.on('pointerdown', (pointer) => {
|
|
this.joyDragging = true;
|
|
this.updateJoystick(pointer.x, pointer.y, x, y, r);
|
|
});
|
|
}
|
|
|
|
updateJoystick(px, py, cx, cy, r) {
|
|
const angle = Phaser.Math.Angle.Between(cx, cy, px, py);
|
|
const dist = Math.min(r, Phaser.Math.Distance.Between(cx, cy, px, py));
|
|
|
|
const sx = cx + Math.cos(angle) * dist;
|
|
const sy = cy + Math.sin(angle) * dist;
|
|
|
|
this.joyStick.setPosition(sx, sy);
|
|
|
|
// Normalize angle to degrees
|
|
let deg = Phaser.Math.RadToDeg(angle);
|
|
|
|
this.virtualJoystick = { up: false, down: false, left: false, right: false };
|
|
|
|
// Mapping to Isometric direction keys
|
|
// UP Key (Top-Left on screen) -> Angle -135 (-180 to -90)
|
|
// RIGHT Key (Top-Right on screen) -> Angle -45 (-90 to 0)
|
|
// DOWN Key (Bottom-Right on screen) -> Angle 45 (0 to 90)
|
|
// LEFT Key (Bottom-Left on screen) -> Angle 135 (90 to 180)
|
|
|
|
if (deg > -170 && deg <= -80) this.virtualJoystick.up = true; // Tuned slightly
|
|
else if (deg > -80 && deg <= 10) this.virtualJoystick.right = true;
|
|
else if (deg > 10 && deg <= 100) this.virtualJoystick.down = true;
|
|
else this.virtualJoystick.left = true;
|
|
}
|
|
|
|
showQuestDialog(quest, onAccept) {
|
|
const width = 400;
|
|
const height = 250;
|
|
const x = this.cameras.main.centerX;
|
|
const y = this.cameras.main.centerY;
|
|
|
|
const container = this.add.container(x, y);
|
|
container.setDepth(5000);
|
|
|
|
const bg = this.add.rectangle(0, 0, width, height, 0x222222, 0.95);
|
|
bg.setStrokeStyle(4, 0x444444);
|
|
|
|
const title = this.add.text(0, -90, quest.title.toUpperCase(), { fontSize: '24px', fontStyle: 'bold', color: '#ffcc00' }).setOrigin(0.5);
|
|
const desc = this.add.text(0, -30, quest.description, { fontSize: '16px', color: '#dddddd', align: 'center', wordWrap: { width: width - 60 } }).setOrigin(0.5);
|
|
|
|
let rText = "Reward: ";
|
|
if (quest.reward.gold) rText += `${quest.reward.gold} G `;
|
|
if (quest.reward.item) rText += `+ ${quest.reward.amount || 1} ${quest.reward.item}`;
|
|
|
|
const reward = this.add.text(0, 40, rText, { fontSize: '16px', color: '#00ff00', fontStyle: 'italic' }).setOrigin(0.5);
|
|
|
|
// Buttons
|
|
const btnAccept = this.add.rectangle(-70, 90, 120, 40, 0x228B22).setInteractive({ useHandCursor: true });
|
|
const txtAccept = this.add.text(-70, 90, 'ACCEPT', { fontSize: '18px', fontStyle: 'bold' }).setOrigin(0.5);
|
|
|
|
const btnClose = this.add.rectangle(70, 90, 120, 40, 0x8B0000).setInteractive({ useHandCursor: true });
|
|
const txtClose = this.add.text(70, 90, 'DECLINE', { fontSize: '18px', fontStyle: 'bold' }).setOrigin(0.5);
|
|
|
|
btnAccept.on('pointerdown', () => {
|
|
onAccept();
|
|
container.destroy();
|
|
});
|
|
|
|
btnClose.on('pointerdown', () => {
|
|
container.destroy();
|
|
});
|
|
|
|
container.add([bg, title, desc, reward, btnAccept, txtAccept, btnClose, txtClose]);
|
|
|
|
// Appear anim
|
|
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'
|
|
});
|
|
}
|
|
}
|
|
|
|
// --- OXYGEN BAR ---
|
|
createOxygenBar() {
|
|
this.oxygenContainer = this.add.container(this.scale.width / 2, this.scale.height - 100);
|
|
this.oxygenContainer.setDepth(2000);
|
|
|
|
// Bg
|
|
const bg = this.add.rectangle(0, 0, 204, 24, 0x000000);
|
|
bg.setStrokeStyle(2, 0xffffff);
|
|
this.oxygenContainer.add(bg);
|
|
|
|
// Bar
|
|
this.oxygenBar = this.add.rectangle(-100, 0, 200, 20, 0x00FFFF);
|
|
this.oxygenBar.setOrigin(0, 0.5);
|
|
this.oxygenContainer.add(this.oxygenBar);
|
|
|
|
// Text
|
|
const text = this.add.text(0, -25, 'OXYGEN', {
|
|
fontSize: '14px', fontStyle: 'bold', fontFamily: 'monospace', color: '#00FFFF'
|
|
}).setOrigin(0.5);
|
|
this.oxygenContainer.add(text);
|
|
|
|
this.oxygenContainer.setVisible(false);
|
|
}
|
|
|
|
updateOxygen(data) {
|
|
if (!this.oxygenContainer) return;
|
|
this.oxygenContainer.setVisible(true);
|
|
const percent = Math.max(0, data.current / data.max);
|
|
this.oxygenBar.width = 200 * percent;
|
|
|
|
// Color warning
|
|
if (percent < 0.3) this.oxygenBar.fillColor = 0xFF0000;
|
|
else this.oxygenBar.fillColor = 0x00FFFF;
|
|
}
|
|
|
|
hideOxygen() {
|
|
if (this.oxygenContainer) this.oxygenContainer.setVisible(false);
|
|
}
|
|
|
|
showFloatingText(data) {
|
|
const text = this.add.text(data.x, data.y, data.text, {
|
|
fontSize: '16px',
|
|
color: data.color || '#FFFFFF',
|
|
fontStyle: 'bold',
|
|
stroke: '#000000',
|
|
strokeThickness: 3
|
|
});
|
|
text.setOrigin(0.5);
|
|
text.setDepth(10000);
|
|
text.setScrollFactor(1);
|
|
|
|
// Animate upward and fade
|
|
this.tweens.add({
|
|
targets: text,
|
|
y: text.y - 50,
|
|
alpha: 0,
|
|
duration: 1500,
|
|
ease: 'Power2',
|
|
onComplete: () => text.destroy()
|
|
});
|
|
}
|
|
}
|