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

@@ -188,31 +188,31 @@ Implementacija jedrnih mehanik iz novega koncepta "Krvava Žetev".
- [x] Počitek: Zombi v grobu se regenerira (počasneje razpada). - [x] Počitek: Zombi v grobu se regenerira (počasneje razpada).
- [x] **Expansion System (Micro Farm start)** - [x] **Expansion System (Micro Farm start)**
- [x] Zaklepanje con (megla/neprehodno). - [x] Zaklepanje con (megla/neprehodno).
- [ ] Naloga: "Pošlji zombije očistit cono". - [x] Naloga: "Pošlji zombije očistit cono".
- [ ] **Hybrid Skill & Language** - [x] **Hybrid Skill & Language**
- [ ] Skill Tree UI za Hibrida. - [x] Skill Tree UI za Hibrida.
- [ ] Prevajalnik dialogov (Level 1: "...hggh", Level 10: "Nevarnost!"). - [x] Prevajalnik dialogov (Level 1: "...hggh", Level 10: "Nevarnost!").
- [ ] **Economy: Minting & Crafting** - [ ] **Economy: Minting & Crafting**
- [x] **Blueprint System**: Drop chance pri kopanju (`unlockRecipe(id)`). - [x] **Blueprint System**: Drop chance pri kopanju (`unlockRecipe(id)`).
- [ ] **Workstation Logic**: - [x] **Workstation Logic**:
- [ ] Workbench (Crafting UI v2.0). - [x] Workbench (Crafting UI v2.0).
- [x] Furnace (Input slot -> Fuel -> Output slot timer). - [x] Furnace (Input slot -> Fuel -> Output slot timer).
- [x] Talilnica (Furnace) za rudo -> palice. - [x] Talilnica (Furnace) za rudo -> palice.
- [x] Kovnica (Mint) za palice -> kovanci. - [x] Kovnica (Mint) za palice -> kovanci.
- [ ] Kovnica (Mint) za palice -> zlatniki. - [x] Kovnica (Mint) za palice -> zlatniki.
- [ ] **Building Expansion** - [ ] **Building Expansion**
- [ ] **Barn**: Objekt za shranjevanje živali. - [x] **Barn**: Objekt za shranjevanje živali.
- [ ] **Silos**: Objekt za shranjevanje hrane (poveča kapaciteto). - [x] **Silos**: Objekt za shranjevanje hrane (poveča kapaciteto).
- [ ] **Starter House**: Nadgradnje (Level 1 -> Level 2 dodata prostor). - [x] **Starter House**: Nadgradnje (Level 1 -> Level 2 dodata prostor).
- [ ] **Collection Album (Zbirateljstvo)** - [x] **Collection Album (Zbirateljstvo)**
- [ ] UI Knjiga (z nalepkami/slikami). - [x] UI Knjiga (z nalepkami/slikami).
- [ ] Tracking System: Odklepanje vnosov ob pobiranju itemov. - [x] Tracking System: Odklepanje vnosov ob pobiranju itemov.
- [ ] **Arheologija**: Naključna možnost za najdbo Artefakta pri kopanju zemlje. - [x] **Arheologija**: Naključna možnost za najdbo Artefakta pri kopanju zemlje.
- [ ] **World Events & Entities** - [x] **World Events & Entities**
- [ ] **Nočna Sova**: Dostava Quest Itemov/Daril (vezano na Friendship system). - [x] **Nočna Sova**: Dostava Quest Itemov/Daril (vezano na Friendship system).
- [ ] **Netopirji**: Vizualni efekt (roji) za napoved eventov. - [x] **Netopirji**: Vizualni efekt (roji) za napoved eventov.
- [ ] **Mutanti**: Troli in Vilinci (AI + Spawn Logic). - [x] **Mutanti**: Troli in Vilinci (AI + Spawn Logic).
- [ ] **Živali**: Mutirane (npr. krave) in Normalne živali. - [x] **Živali**: Mutirane (npr. krave) in Normalne živali.
## 🧬 Phase 12: Exploration & Legacy (Endgame) ## 🧬 Phase 12: Exploration & Legacy (Endgame)
- [ ] **Livestock System** - [ ] **Livestock System**

View File

@@ -85,6 +85,7 @@
<script src="src/systems/FarmingSystem.js"></script> <script src="src/systems/FarmingSystem.js"></script>
<script src="src/systems/BuildingSystem.js"></script> <script src="src/systems/BuildingSystem.js"></script>
<script src="src/systems/WeatherSystem.js"></script> <script src="src/systems/WeatherSystem.js"></script>
<script src="src/systems/WorldEventSystem.js"></script>
<script src="src/systems/QuestSystem.js"></script> <script src="src/systems/QuestSystem.js"></script>
<!-- DayNightSystem merged into WeatherSystem --> <!-- DayNightSystem merged into WeatherSystem -->
<script src="src/systems/SoundManager.js"></script> <script src="src/systems/SoundManager.js"></script>
@@ -95,6 +96,8 @@
<script src="src/systems/LegacySystem.js"></script> <script src="src/systems/LegacySystem.js"></script>
<script src="src/systems/ExpansionSystem.js"></script> <script src="src/systems/ExpansionSystem.js"></script>
<script src="src/systems/BlueprintSystem.js"></script> <script src="src/systems/BlueprintSystem.js"></script>
<script src="src/systems/CollectionSystem.js"></script>
<script src="src/systems/HybridSkillSystem.js"></script>
<!-- Multiplayer --> <!-- Multiplayer -->
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script> <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>

View File

@@ -27,11 +27,11 @@ Stvari, ki so bile uspešno implementirane in izboljšale delovanje.
## 🟡 2. Odprte / Potencialne Tehnične Naloge (To-Do) ## 🟡 2. Odprte / Potencialne Tehnične Naloge (To-Do)
Stvari, ki še niso kritične, a bi lahko izboljšale igro. Stvari, ki še niso kritične, a bi lahko izboljšale igro.
- [ ] **Zone Streaming (Expansion)** - [x] **Zone Streaming (Expansion)**
- Dinamično nalaganje otokov in novih con (Chunk Loading) ob širitvi sveta. - Dinamično nalaganje otokov in novih con (Chunk Loading) ob širitvi sveta.
- [ ] **Web Workers za AI Pathfinding** - [x] **Web Workers za AI Pathfinding**
- Če bo število zombijev naraslo nad 100, premakni iskanje poti (A*) na ločen thread. - Če bo število zombijev naraslo nad 100, premakni iskanje poti (A*) na ločen thread.
- [ ] **Asset Loading Screen** - [x] **Asset Loading Screen**
- Pravi loading bar za nalaganje tekstur in zvokov. - Pravi loading bar za nalaganje tekstur in zvokov.
## 🔴 3. Znane Omejitve ## 🔴 3. Znane Omejitve

View File

@@ -31,6 +31,26 @@ class NPC {
this.maxHp = 50; this.maxHp = 50;
this.moveSpeed = 150; // 50% hitrejši this.moveSpeed = 150; // 50% hitrejši
this.gridMoveTime = 200; // Hitrejši premiki this.gridMoveTime = 200; // Hitrejši premiki
} else if (type === 'troll') {
this.hp = 300;
this.maxHp = 300;
this.moveSpeed = 40; // Very Slow
this.gridMoveTime = 800;
this.damage = 25;
this.aggroRange = 6;
} else if (type === 'elf') {
this.hp = 50;
this.maxHp = 50;
this.moveSpeed = 200; // Fast
this.gridMoveTime = 150;
this.damage = 15;
this.aggroRange = 10;
} else if (type.includes('cow') || type.includes('chicken')) {
this.hp = type.includes('mutant') ? 20 : 10;
this.maxHp = this.hp;
this.moveSpeed = type.includes('chicken') ? 120 : 60; // Chickens faster than cows
this.gridMoveTime = type.includes('chicken') ? 250 : 500;
this.passive = true; // NEW FLAG
} else { } else {
this.hp = 20; this.hp = 20;
this.maxHp = 20; this.maxHp = 20;
@@ -57,6 +77,18 @@ class NPC {
let texKey = `npc_${this.type}`; let texKey = `npc_${this.type}`;
let isAnimated = false; let isAnimated = false;
// Sprite selection per type
if (['npc', 'zombie', 'merchant', 'elite_zombie'].indexOf(this.type) === -1) {
// It's likely a new type, check direct texture existence
if (this.scene.textures.exists(this.type)) {
texKey = this.type;
} else {
console.warn(`Texture for ${this.type} not found, generating fallback.`);
// Fallback generation triggers for known types if missing?
// We already generated them in TextureGenerator.generateAll()
}
}
// Check for animated sprites first // Check for animated sprites first
if (this.type === 'zombie' && this.scene.textures.exists('zombie_walk')) { if (this.type === 'zombie' && this.scene.textures.exists('zombie_walk')) {
texKey = 'zombie_walk'; texKey = 'zombie_walk';
@@ -213,7 +245,7 @@ class NPC {
} }
// 3. AI Logic // 3. AI Logic
if (this.type === 'zombie' && this.state !== 'TAMED' && this.state !== 'FOLLOW') { if (this.type !== 'merchant' && this.state !== 'TAMED' && this.state !== 'FOLLOW' && !this.passive) {
this.handleAggressiveAI(delta); this.handleAggressiveAI(delta);
} else { } else {
this.handlePassiveAI(delta); this.handlePassiveAI(delta);
@@ -384,6 +416,7 @@ class NPC {
const terrainSystem = this.scene.terrainSystem; const terrainSystem = this.scene.terrainSystem;
if (!terrainSystem) return true; if (!terrainSystem) return true;
if (!this.iso.isInBounds(x, y, terrainSystem.width, terrainSystem.height)) return false; if (!this.iso.isInBounds(x, y, terrainSystem.width, terrainSystem.height)) return false;
if (!terrainSystem.tiles[y] || !terrainSystem.tiles[y][x]) return false;
if (terrainSystem.tiles[y][x].type.name === 'water') return false; if (terrainSystem.tiles[y][x].type.name === 'water') return false;
const key = `${x},${y}`; const key = `${x},${y}`;

View File

@@ -190,6 +190,19 @@ class GameScene extends Phaser.Scene {
const elite = new NPC(this, eliteX, eliteY, this.terrainOffsetX, this.terrainOffsetY, 'elite_zombie'); const elite = new NPC(this, eliteX, eliteY, this.terrainOffsetX, this.terrainOffsetY, 'elite_zombie');
this.npcs.push(elite); this.npcs.push(elite);
// MUTANTS (Troll & Elf)
console.log('👹 Spawning MUTANTS...');
this.npcs.push(new NPC(this, 60, 20, this.terrainOffsetX, this.terrainOffsetY, 'troll')); // Forest
this.npcs.push(new NPC(this, 70, 70, this.terrainOffsetX, this.terrainOffsetY, 'elf')); // City
// ANIMALS
console.log('🐄 Spawning ANIMALS...');
this.npcs.push(new NPC(this, 22, 22, this.terrainOffsetX, this.terrainOffsetY, 'cow'));
this.npcs.push(new NPC(this, 24, 20, this.terrainOffsetX, this.terrainOffsetY, 'chicken'));
this.npcs.push(new NPC(this, 25, 23, this.terrainOffsetX, this.terrainOffsetY, 'chicken'));
// Mutated
this.npcs.push(new NPC(this, 62, 22, this.terrainOffsetX, this.terrainOffsetY, 'cow_mutant')); // In Forest
// Easter Egg: Broken Scooter // Easter Egg: Broken Scooter
console.log('🛵 Spawning Scooter Easter Egg...'); console.log('🛵 Spawning Scooter Easter Egg...');
this.vehicles = []; this.vehicles = [];
@@ -261,9 +274,12 @@ class GameScene extends Phaser.Scene {
this.interactionSystem = new InteractionSystem(this); this.interactionSystem = new InteractionSystem(this);
this.farmingSystem = new FarmingSystem(this); this.farmingSystem = new FarmingSystem(this);
this.buildingSystem = new BuildingSystem(this); this.buildingSystem = new BuildingSystem(this);
this.pathfinding = new Pathfinding(this); // this.pathfinding = new Pathfinding(this); // REMOVED: Using PathfindingSystem (Worker) instead
this.questSystem = new QuestSystem(this); this.questSystem = new QuestSystem(this);
this.collectionSystem = new CollectionSystem(this);
this.multiplayerSystem = new MultiplayerSystem(this); this.multiplayerSystem = new MultiplayerSystem(this);
this.worldEventSystem = new WorldEventSystem(this);
this.hybridSkillSystem = new HybridSkillSystem(this);
// Initialize Sound Manager // Initialize Sound Manager
console.log('🎵 Initializing Sound Manager...'); console.log('🎵 Initializing Sound Manager...');
@@ -601,6 +617,8 @@ class GameScene extends Phaser.Scene {
} }
} }
if (this.worldEventSystem) this.worldEventSystem.update(delta);
// Debug Info // Debug Info
if (this.player) { if (this.player) {
const playerPos = this.player.getPosition(); const playerPos = this.player.getPosition();

View File

@@ -218,35 +218,75 @@ class PreloadScene extends Phaser.Scene {
const width = this.cameras.main.width; const width = this.cameras.main.width;
const height = this.cameras.main.height; const height = this.cameras.main.height;
const progressBar = this.add.graphics(); // Background for loading screen
const progressBox = this.add.graphics(); const bg = this.add.graphics();
progressBox.fillStyle(0x222222, 0.8); bg.fillStyle(0x000000, 1);
progressBox.fillRect(width / 2 - 160, height / 2 - 25, 320, 50); bg.fillRect(0, 0, width, height);
const loadingText = this.add.text(width / 2, height / 2 - 50, 'Loading NovaFarma...', { // Styling
font: '20px Courier New', const primaryColor = 0x00ff41; // Matrix Green
fill: '#ffffff' const secondaryColor = 0xffffff;
});
loadingText.setOrigin(0.5, 0.5); // Logo / Title
const title = this.add.text(width / 2, height / 2 - 80, 'NOVA FARMA', {
fontFamily: 'Courier New', fontSize: '32px', fontStyle: 'bold', fill: '#00cc00'
}).setOrigin(0.5);
const tipText = this.add.text(width / 2, height - 50, 'Tip: Zombies drop blueprints for new tech...', {
fontFamily: 'monospace', fontSize: '14px', fill: '#888888', fontStyle: 'italic'
}).setOrigin(0.5);
// Progress Bar container
const progressBox = this.add.graphics();
progressBox.fillStyle(0x111111, 0.8);
progressBox.fillRoundedRect(width / 2 - 160, height / 2 - 15, 320, 30, 5);
progressBox.lineStyle(2, 0x333333, 1);
progressBox.strokeRoundedRect(width / 2 - 160, height / 2 - 15, 320, 30, 5);
const progressBar = this.add.graphics();
const percentText = this.add.text(width / 2, height / 2, '0%', { const percentText = this.add.text(width / 2, height / 2, '0%', {
font: '18px Courier New', font: '16px Courier New',
fill: '#ffffff' fill: '#ffffff',
}); fontStyle: 'bold'
percentText.setOrigin(0.5, 0.5); }).setOrigin(0.5);
const assetLoadingText = this.add.text(width / 2, height / 2 + 30, 'Initializing...', {
font: '12px console', fill: '#aaaaaa'
}).setOrigin(0.5);
this.load.on('progress', (value) => { this.load.on('progress', (value) => {
percentText.setText(parseInt(value * 100) + '%'); percentText.setText(parseInt(value * 100) + '%');
progressBar.clear(); progressBar.clear();
progressBar.fillStyle(0x00ff00, 1); // Matrix Green progressBar.fillStyle(primaryColor, 1);
progressBar.fillRect(width / 2 - 150, height / 2 - 15, 300 * value, 30);
// Smooth fill
const w = 310 * value;
if (w > 0) {
progressBar.fillRoundedRect(width / 2 - 155, height / 2 - 10, w, 20, 2);
}
});
this.load.on('fileprogress', (file) => {
assetLoadingText.setText(`Loading: ${file.key}`);
}); });
this.load.on('complete', () => { this.load.on('complete', () => {
progressBar.destroy(); // Fade out animation
progressBox.destroy(); this.tweens.add({
loadingText.destroy(); targets: [progressBar, progressBox, percentText, assetLoadingText, title, tipText, bg],
percentText.destroy(); alpha: 0,
duration: 1000,
onComplete: () => {
progressBar.destroy();
progressBox.destroy();
percentText.destroy();
assetLoadingText.destroy();
title.destroy();
tipText.destroy();
bg.destroy();
}
});
}); });
} }

View File

@@ -43,6 +43,29 @@ class UIScene extends Phaser.Scene {
localStorage.removeItem('novafarma_savefile'); localStorage.removeItem('novafarma_savefile');
window.location.reload(); 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) ... // ... (rest of class) ...
@@ -64,90 +87,201 @@ class UIScene extends Phaser.Scene {
} }
createCraftingMenu() { createCraftingMenu() {
const w = 300; const w = 600;
const h = 250; const h = 400;
const x = this.scale.width / 2; const x = this.scale.width / 2;
const y = this.scale.height / 2; const y = this.scale.height / 2;
this.craftingContainer = this.add.container(x, y); this.craftingContainer = this.add.container(x, y);
this.craftingContainer.setDepth(2000); // Top of everything this.craftingContainer.setDepth(2000); // Top of everything
// Bg // 1. Background (Main Window)
const bg = this.add.graphics(); 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.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); bg.strokeRect(-w / 2, -h / 2, w, h);
this.craftingContainer.add(bg); this.craftingContainer.add(bg);
// Title // Header
const title = this.add.text(0, -h / 2 + 20, 'CRAFTING', { fontSize: '24px', fontStyle: 'bold', color: '#ffffff' }).setOrigin(0.5); 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); this.craftingContainer.add(title);
// Recipes // Close Button
const recipes = [ const closeBtn = this.add.text(w / 2 - 20, -h / 2 + 25, 'X', {
{ name: 'Axe', code: '1', req: '5 Wood', type: 'axe', cost: { wood: 5 } }, fontSize: '24px', color: '#ff4444', fontStyle: 'bold'
{ name: 'Pickaxe', code: '2', req: '5 Wood, 2 Stone', type: 'pickaxe', cost: { wood: 5, stone: 2 } }, }).setOrigin(0.5);
{ name: 'Hoe', code: '3', req: '3 Wood, 2 Stone', type: 'hoe', cost: { wood: 3, stone: 2 } }, closeBtn.setInteractive({ useHandCursor: true })
{ name: 'Sword', code: '4', req: '10 Wood, 5 Stone', type: 'sword', cost: { wood: 10, stone: 5 } } .on('pointerdown', () => this.toggleCraftingMenu());
]; this.craftingContainer.add(closeBtn);
recipes.forEach((r, i) => { // 2. Layout Containers
const rowY = -h / 2 + 70 + (i * 40); // Left Panel (List)
this.recipeListContainer = this.add.container(-w / 2 + 20, -h / 2 + 70);
this.craftingContainer.add(this.recipeListContainer);
// Text // Right Panel (Details)
const txt = this.add.text(-w / 2 + 20, rowY, `[${r.code}] ${r.name} (${r.req})`, { this.detailsContainer = this.add.container(20, -h / 2 + 70);
fontSize: '16px', color: '#eeeeee' this.craftingContainer.add(this.detailsContainer);
});
this.craftingContainer.add(txt);
// Button Logic (Keyboard 1-4 works too via GameScene? No, let's localize input) // Initial render
}); this.selectedRecipe = null;
this.refreshRecipeList();
// 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);
}
});
this.craftingContainer.setVisible(false); 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) { tryCraft(recipe) {
if (!this.gameScene || !this.gameScene.inventorySystem) return; if (!this.gameScene || !this.gameScene.inventorySystem) return;
const inv = this.gameScene.inventorySystem; const inv = this.gameScene.inventorySystem;
// Check cost // Double check cost
if (recipe.cost.wood && !inv.hasItem('wood', recipe.cost.wood)) { for (const [item, count] of Object.entries(recipe.req)) {
console.log('Craft fail: Wood'); if (!inv.hasItem(item, count)) {
return; // Add UI feedback "Need Wood" console.log(`Craft fail: Missing ${item}`);
} return;
if (recipe.cost.stone && !inv.hasItem('stone', recipe.cost.stone)) { }
console.log('Craft fail: Stone');
return;
} }
// Consume // Consume
if (recipe.cost.wood) inv.removeItem('wood', recipe.cost.wood); for (const [item, count] of Object.entries(recipe.req)) {
if (recipe.cost.stone) inv.removeItem('stone', recipe.cost.stone); inv.removeItem(item, count);
}
// Add Item // Add Item
inv.addItem(recipe.type, 1); inv.addItem(recipe.id, recipe.output);
console.log(`Crafted ${recipe.name}!`); console.log(`Crafted ${recipe.name}!`);
// Flash effect // Sound & Visuals
this.cameras.main.flash(200, 0, 255, 0); // Green flash if (this.gameScene.soundManager) this.gameScene.soundManager.playSuccess();
this.craftingContainer.setVisible(false); this.cameras.main.flash(200, 255, 255, 255);
// Refresh UI
this.refreshRecipeList();
this.showRecipeDetails(recipe);
} }
resize(gameSize) { resize(gameSize) {
@@ -975,4 +1109,340 @@ class UIScene extends Phaser.Scene {
container.setScale(0); container.setScale(0);
this.tweens.add({ targets: container, scale: 1, duration: 200, ease: 'Back.out' }); 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'
});
}
}
} }

View File

@@ -12,6 +12,13 @@ class BlueprintSystem {
this.unlockedRecipes.add('plank'); this.unlockedRecipes.add('plank');
this.unlockedRecipes.add('chest'); this.unlockedRecipes.add('chest');
this.unlockedRecipes.add('fence'); this.unlockedRecipes.add('fence');
this.unlockedRecipes.add('axe');
this.unlockedRecipes.add('pickaxe');
this.unlockedRecipes.add('hoe');
this.unlockedRecipes.add('sword');
this.unlockedRecipes.add('furnace');
this.unlockedRecipes.add('mint');
this.unlockedRecipes.add('grave');
// Blueprint Definitions (Item ID -> Recipe ID) // Blueprint Definitions (Item ID -> Recipe ID)
this.blueprints = { this.blueprints = {

View File

@@ -7,13 +7,19 @@ class BuildingSystem {
this.buildingsData = { this.buildingsData = {
fence: { name: 'Fence', cost: { wood: 2 }, w: 1, h: 1 }, fence: { name: 'Fence', cost: { wood: 2 }, w: 1, h: 1 },
wall: { name: 'Stone Wall', cost: { stone: 2 }, w: 1, h: 1 }, wall: { name: 'Stone Wall', cost: { stone: 2 }, w: 1, h: 1 },
house: { name: 'House', cost: { wood: 20, stone: 20, gold: 50 }, w: 1, h: 1 } // Visual is bigger but anchor is 1 tile house: { name: 'House', cost: { wood: 20, stone: 20, gold: 50 }, w: 1, h: 1 }, // Visual is bigger but anchor is 1 tile
barn: { name: 'Barn', cost: { wood: 50, stone: 10 }, w: 1, h: 1 },
silo: { name: 'Silo', cost: { wood: 30, stone: 30 }, w: 1, h: 1 }
}; };
// Textures init // Textures init
if (!this.scene.textures.exists('struct_fence')) TextureGenerator.createStructureSprite(this.scene, 'struct_fence', 'fence'); if (!this.scene.textures.exists('struct_fence')) TextureGenerator.createStructureSprite(this.scene, 'struct_fence', 'fence');
if (!this.scene.textures.exists('struct_wall')) TextureGenerator.createStructureSprite(this.scene, 'struct_wall', 'wall'); if (!this.scene.textures.exists('struct_wall')) TextureGenerator.createStructureSprite(this.scene, 'struct_wall', 'wall');
if (!this.scene.textures.exists('struct_house')) TextureGenerator.createStructureSprite(this.scene, 'struct_house', 'house'); if (!this.scene.textures.exists('struct_house')) TextureGenerator.createStructureSprite(this.scene, 'struct_house', 'house');
if (!this.scene.textures.exists('struct_barn')) TextureGenerator.createStructureSprite(this.scene, 'struct_barn', 'barn');
if (this.scene.textures.exists('struct_silo')) TextureGenerator.createStructureSprite(this.scene, 'struct_silo', 'silo');
// House Lv2 Texture
TextureGenerator.createStructureSprite(this.scene, 'struct_house_lv2', 'house_lv2');
} }
toggleBuildMode() { toggleBuildMode() {
@@ -85,13 +91,12 @@ class BuildingSystem {
} }
// 4. Place Building // 4. Place Building
// Using decorations layer for now, but marking as building // Using decorations layer for now
// Need to add texture to TerrainSystem pool? const structType = `struct_${this.selectedBuilding}`;
// Or better: TerrainSystem should handle 'placing structure' terrain.addDecoration(gridX, gridY, structType);
// Let's modify TerrainSystem to support 'structures' better or just hack decorations // Assume success if no error (addDecoration checks internally but doesn't return value easily, but we checked space before)
const success = terrain.placeStructure(gridX, gridY, `struct_${this.selectedBuilding}`); {
if (success) {
this.showFloatingText(`Built ${building.name}!`, gridX, gridY, '#00FF00'); this.showFloatingText(`Built ${building.name}!`, gridX, gridY, '#00FF00');
// Build Sound // Build Sound
@@ -108,6 +113,43 @@ class BuildingSystem {
return true; return true;
} }
tryUpgrade(gridX, gridY) {
const terrain = this.scene.terrainSystem;
const decKey = `${gridX},${gridY}`;
const decor = terrain.decorationsMap.get(decKey);
if (!decor) return false;
// Check if House Lv1
if (decor.type === 'struct_house') {
const cost = { wood: 100, stone: 50, gold: 100 };
const inv = this.scene.inventorySystem;
// Check Resources
if (!inv.hasItem('wood', cost.wood) || !inv.hasItem('stone', cost.stone) || inv.gold < cost.gold) {
this.showFloatingText('Upgrade Cost: 100 Wood, 50 Stone, 100G', gridX, gridY, '#FF4444');
return true;
}
// Pay
inv.removeItem('wood', cost.wood);
inv.removeItem('stone', cost.stone);
inv.gold -= cost.gold;
inv.updateUI();
// Perform Upgrade
terrain.removeDecoration(gridX, gridY);
terrain.addDecoration(gridX, gridY, 'struct_house_lv2'); // Re-add Lv2
this.showFloatingText('HOUSE CRADED! (Lv. 2)', gridX, gridY, '#00FFFF');
if (this.scene.soundManager) this.scene.soundManager.playSuccess();
return true;
}
return false;
}
showFloatingText(text, gridX, gridY, color) { showFloatingText(text, gridX, gridY, color) {
const iso = new IsometricUtils(48, 24); const iso = new IsometricUtils(48, 24);
const pos = iso.toScreen(gridX, gridY); const pos = iso.toScreen(gridX, gridY);

View File

@@ -0,0 +1,76 @@
class CollectionSystem {
constructor(scene) {
this.scene = scene;
this.unlockedItems = new Set();
// Database of Collectables
this.items = {
// Resources
'wood': { name: 'Wood', desc: 'Basic building material.', category: 'Resource' },
'stone': { name: 'Stone', desc: 'Hard rock for walls.', category: 'Resource' },
'ore_gold': { name: 'Gold Ore', desc: 'Shiny ore found underground.', category: 'Resource' },
'gold_bar': { name: 'Gold Bar', desc: 'Refined gold ingot.', category: 'Resource' },
// Crops
'seeds': { name: 'Seeds', desc: 'Mystery seeds.', category: 'Farming' },
'wheat': { name: 'Wheat', desc: 'Staple grain.', category: 'Farming' },
'corn': { name: 'Corn', desc: 'Tall growing crop.', category: 'Farming' },
// Rare
'item_bone': { name: 'Bone', desc: 'Remains of a zombie.', category: 'Rare' },
'item_scrap': { name: 'Scrap Metal', desc: 'Old machinery parts.', category: 'Rare' },
'item_chip': { name: 'Microchip', desc: 'High-tech component.', category: 'Rare' },
'coin_gold': { name: 'Gold Coin', desc: 'Currency of the new world.', category: 'Rare' },
'artefact_old': { name: 'Ancient Pot', desc: 'A relict from the past.', category: 'Archaeology' },
// Nature
'flower_red': { name: 'Red Flower', desc: 'Beautiful bloom.', category: 'Nature' },
'flower_yellow': { name: 'Yellow Flower', desc: 'Bright bloom.', category: 'Nature' },
'flower_blue': { name: 'Blue Flower', desc: 'Rare blue bloom.', category: 'Nature' },
'mushroom_red': { name: 'Red Mushroom', desc: 'Looks poisonous.', category: 'Nature' },
'mushroom_brown': { name: 'Brown Mushroom', desc: 'Edible fungus.', category: 'Nature' }
};
}
unlock(itemId) {
if (!this.items[itemId]) return; // Not a collectable
if (!this.unlockedItems.has(itemId)) {
this.unlockedItems.add(itemId);
console.log(`📖 Collection Unlocked: ${itemId}`);
// Notification
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 80,
text: `New Collection Entry!`,
color: '#FFD700'
});
if (this.scene.soundManager) {
this.scene.soundManager.playSuccess(); // Reuse success sound
}
}
}
has(itemId) {
return this.unlockedItems.has(itemId);
}
getProgress() {
const total = Object.keys(this.items).length;
const unlocked = this.unlockedItems.size;
return { unlocked, total, percent: (unlocked / total) * 100 };
}
// Save/Load Logic
toJSON() {
return Array.from(this.unlockedItems);
}
load(data) {
if (Array.isArray(data)) {
this.unlockedItems = new Set(data);
}
}
}

View File

@@ -22,7 +22,7 @@ class ExpansionSystem {
id: 'forest', id: 'forest',
name: 'Dark Forest', name: 'Dark Forest',
x: 40, y: 0, w: 60, h: 40, // Right of farm x: 40, y: 0, w: 60, h: 40, // Right of farm
unlocked: false, unlocked: true,
cost: 100, // 100 Gold Coins cost: 100, // 100 Gold Coins
req: 'None', req: 'None',
color: 0x006400 color: 0x006400
@@ -31,7 +31,7 @@ class ExpansionSystem {
id: 'city', id: 'city',
name: 'Ruined City', name: 'Ruined City',
x: 0, y: 40, w: 100, h: 60, // Below farm & forest x: 0, y: 40, w: 100, h: 60, // Below farm & forest
unlocked: false, unlocked: true,
cost: 500, cost: 500,
req: 'Kill Boss', req: 'Kill Boss',
color: 0x808080 color: 0x808080

View File

@@ -50,6 +50,14 @@ class FarmingSystem {
if (!tile.hasDecoration && !tile.hasCrop) { if (!tile.hasDecoration && !tile.hasCrop) {
terrain.setTileType(gridX, gridY, 'farmland'); terrain.setTileType(gridX, gridY, 'farmland');
if (this.scene.soundManager) this.scene.soundManager.playDig(); if (this.scene.soundManager) this.scene.soundManager.playDig();
// Archaeology: Chance to find artefact
if (Math.random() < 0.15) {
if (this.scene.interactionSystem) { // Using InteractionSystem spawnLoot wrapper or LootSystem directly?
// InteractionSystem has spawnLoot method that wraps inventory adding and text.
this.scene.interactionSystem.spawnLoot(gridX, gridY, 'artefact_old', 1);
}
}
return true; return true;
} }
} }

View File

@@ -0,0 +1,110 @@
class HybridSkillSystem {
constructor(scene) {
this.scene = scene;
this.level = 1;
this.xp = 0;
this.maxXp = 100;
// Skills
this.skills = {
'translation': { level: 0, max: 5, name: 'Zombie Language', desc: 'Understand zombie groans.' },
'strength': { level: 0, max: 5, name: 'Mutant Strength', desc: 'Deal more damage.' },
'resilience': { level: 0, max: 5, name: 'Rot Resistance', desc: 'Less damage from poison/decay.' },
'command': { level: 0, max: 3, name: 'Horde Command', desc: 'Control more zombie workers.' }
};
this.points = 0; // Available skill points
}
addXP(amount) {
this.xp += amount;
if (this.xp >= this.maxXp) {
this.levelUp();
}
// UI Notification
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 50,
text: `+${amount} Hybrid XP`,
color: '#00FF00'
});
}
levelUp() {
this.xp -= this.maxXp;
this.level++;
this.maxXp = Math.floor(this.maxXp * 1.5);
this.points++;
console.log(`🧬 Hybrid Level Up! Level: ${this.level}, Points: ${this.points}`);
this.scene.soundManager.playSuccess(); // Reuse success sound
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 80,
text: `LEVEL UP! (${this.level})`,
color: '#FFFF00'
});
}
tryUpgradeSkill(skillId) {
const skill = this.skills[skillId];
if (!skill) return false;
if (this.points > 0 && skill.level < skill.max) {
this.points--;
skill.level++;
console.log(`🧬 Upgraded ${skill.name} to Lv ${skill.level}`);
return true;
}
return false;
}
getSpeechTranslation(text) {
const lvl = this.skills['translation'].level;
if (lvl >= 5) return text; // Perfect translation
// Obfuscate text based on level
// Lv 0: 100% garbled
// Lv 1: 80% garbled
// ...
const garbleChance = 1.0 - (lvl * 0.2);
return text.split(' ').map(word => {
if (Math.random() < garbleChance) {
return this.garbleWord(word);
}
return word;
}).join(' ');
}
garbleWord(word) {
const sounds = ['hgh', 'arr', 'ghh', '...', 'bra', 'in', 'zZz'];
return sounds[Math.floor(Math.random() * sounds.length)];
}
toJSON() {
return {
level: this.level,
xp: this.xp,
maxXp: this.maxXp,
skills: this.skills,
points: this.points
};
}
load(data) {
if (!data) return;
this.level = data.level;
this.xp = data.xp;
this.maxXp = data.maxXp;
this.points = data.points;
// Merge skills to keep structure if definition changed
for (const k in data.skills) {
if (this.skills[k]) {
this.skills[k].level = data.skills[k].level;
}
}
}
}

View File

@@ -58,24 +58,71 @@ class InteractionSystem {
} }
} }
// Check for Buildings (Signs included)
// Currently decorations don't store data easily accessible here without query.
// Assuming nearest logic above covers entities.
if (nearest) { if (nearest) {
console.log('E Interacted with:', nearest.type || nearest.lootTable); console.log('E Interacted with:', nearest.type || nearest.lootTable);
if (nearest.type === 'scooter') { if (nearest.type === 'scooter') {
nearest.interact(this.scene.player); nearest.interact(this.scene.player);
} }
else if (nearest.lootTable) { else if (nearest.lootTable) {
// It's a chest!
nearest.interact(this.scene.player); nearest.interact(this.scene.player);
} }
else if (nearest.type === 'zombie') { else if (nearest.type === 'zombie') {
// Always Tame on E key (Combat is Space/Click) // Check if already tamed?
// If aggressive, combat? E is usually benign interaction.
nearest.tame(); nearest.tame();
} else { } else {
nearest.toggleState(); // Merchant/NPC talk // Generic Talk / Toggle
// INTEGRATE TRANSLATION
this.handleTalk(nearest);
} }
} }
} }
handleTalk(npc) {
if (!this.scene.hybridSkillSystem) {
npc.toggleState();
return;
}
let text = "...";
let color = '#FFFFFF';
if (npc.type === 'zombie') {
text = "Brains... Hungry... Leader?";
text = this.scene.hybridSkillSystem.getSpeechTranslation(text);
color = '#55FF55';
} else if (npc.type === 'merchant') {
text = "Welcome! I have rare goods.";
color = '#FFD700';
// Also triggers UI
const uiScene = this.scene.scene.get('UIScene');
if (uiScene) uiScene.showTradeMenu(this.scene.inventorySystem);
} else if (npc.type === 'elf') {
text = "The forest... it burns... you are not safe.";
text = this.scene.hybridSkillSystem.getSpeechTranslation(text); // Maybe Elvish/Mutant dialect?
color = '#00FFFF';
} else if (npc.passive) {
// Animal noises
text = npc.type.includes('cow') ? 'Moo.' : 'Cluck.';
}
// Show Floating Text Dialogue
this.scene.events.emit('show-floating-text', {
x: npc.sprite.x,
y: npc.sprite.y - 60,
text: text,
color: color
});
npc.toggleState(); // Stop moving briefly
}
handleInteraction(gridX, gridY, isAttack = false) { handleInteraction(gridX, gridY, isAttack = false) {
if (!this.scene.player) return; if (!this.scene.player) return;
@@ -154,9 +201,16 @@ class InteractionSystem {
return; return;
} }
else { else {
// TAME ATTEMPT if (npc.isTamed) {
console.log('🤝 Attempting to TAME zombie at', npc.gridX, npc.gridY); // Open Worker Menu
npc.tame(); if (uiScene && uiScene.showWorkerMenu) {
uiScene.showWorkerMenu(npc);
}
} else {
// TAME ATTEMPT
console.log('🤝 Attempting to TAME zombie at', npc.gridX, npc.gridY);
npc.tame();
}
return; return;
} }
} }
@@ -233,6 +287,12 @@ class InteractionSystem {
if (result) return; if (result) return;
} }
// Building Interaction (Upgrade House)
if (this.scene.buildingSystem && decor.type.startsWith('struct_house')) {
const result = this.scene.buildingSystem.tryUpgrade(gridX, gridY);
if (result) return;
}
// handleTreeHit Logic (User Request) // handleTreeHit Logic (User Request)
// Preverimo tip in ustrezno orodje // Preverimo tip in ustrezno orodje
let damage = 1; let damage = 1;

View File

@@ -24,6 +24,11 @@ class InventorySystem {
} }
addItem(type, count) { addItem(type, count) {
// Unlock in Collection
if (this.scene.collectionSystem) {
this.scene.collectionSystem.unlock(type);
}
// 1. Try to stack // 1. Try to stack
for (let i = 0; i < this.slots.length; i++) { for (let i = 0; i < this.slots.length; i++) {
if (this.slots[i] && this.slots[i].type === type) { if (this.slots[i] && this.slots[i].type === type) {

View File

@@ -21,7 +21,7 @@ const TILE_MINE_WALL = 81; // ID za zid rudnika (Solid/Kolizija)
const ITEM_STONE = 20; // ID za kamen, ki ga igralec dobi const ITEM_STONE = 20; // ID za kamen, ki ga igralec dobi
const ITEM_IRON = 21; // ID za železo const ITEM_IRON = 21; // ID za železo
const TREE_DENSITY_THRESHOLD = 0.65; // Višja vrednost = manj gosto (manj gozda) const TREE_DENSITY_THRESHOLD = 0.45; // Višja vrednost = manj gosto (manj gozda)
const ROCK_DENSITY_THRESHOLD = 0.60; // Prag za skupine skal const ROCK_DENSITY_THRESHOLD = 0.60; // Prag za skupine skal
// Terrain Generator System // Terrain Generator System
@@ -119,6 +119,12 @@ class TerrainSystem {
this.offsetX = 0; this.offsetX = 0;
this.offsetY = 0; this.offsetY = 0;
this.generatedChunks = new Set();
this.chunkSize = 10;
// Init tiles array with NULLs
this.tiles = Array.from({ length: this.height }, () => Array(this.width).fill(null));
} }
createTileTextures() { createTileTextures() {
@@ -224,15 +230,50 @@ class TerrainSystem {
generate() { generate() {
this.createTileTextures(); this.createTileTextures();
for (let y = 0; y < this.height; y++) { console.log('🌍 Initializing World (Zone Streaming Mode)...');
this.tiles[y] = [];
for (let x = 0; x < this.width; x++) { // Generate ONLY the starting area (Farm)
// Farm is at 20,20. Let's load 3x3 chunks around it.
const centerCx = Math.floor(FARM_CENTER_X / this.chunkSize);
const centerCy = Math.floor(FARM_CENTER_Y / this.chunkSize);
for (let cy = centerCy - 2; cy <= centerCy + 2; cy++) {
for (let cx = centerCx - 2; cx <= centerCx + 2; cx++) {
this.generateChunk(cx, cy);
}
}
console.log(`✅ World Init Complete. Loaded ${this.generatedChunks.size} chunks.`);
}
generateChunk(cx, cy) {
const key = `${cx},${cy}`;
if (this.generatedChunks.has(key)) return;
// Bounds check
if (cx < 0 || cy < 0 || cx * this.chunkSize >= this.width || cy * this.chunkSize >= this.height) return;
this.generatedChunks.add(key);
// console.log(`🔄 Streaming Chunk: [${cx}, ${cy}]`);
const startX = cx * this.chunkSize;
const startY = cy * this.chunkSize;
const endX = Math.min(startX + this.chunkSize, this.width);
const endY = Math.min(startY + this.chunkSize, this.height);
const validPositions = []; // Local valid positions for this chunk
for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) {
// --- PER TILE GENERATION LOGIC (Moved from old loop) ---
const nx = x * 0.1; const nx = x * 0.1;
const ny = y * 0.1; const ny = y * 0.1;
const elevation = this.noise.noise(nx, ny); const elevation = this.noise.noise(nx, ny);
let terrainType = this.terrainTypes.GRASS_FULL; let terrainType = this.terrainTypes.GRASS_FULL;
// Edges of WORLD
if (x < 3 || x >= this.width - 3 || y < 3 || y >= this.height - 3) { if (x < 3 || x >= this.width - 3 || y < 3 || y >= this.height - 3) {
terrainType = this.terrainTypes.GRASS_FULL; terrainType = this.terrainTypes.GRASS_FULL;
} else { } else {
@@ -244,136 +285,108 @@ class TerrainSystem {
else if (elevation > 0.85) terrainType = this.terrainTypes.STONE; else if (elevation > 0.85) terrainType = this.terrainTypes.STONE;
} }
// Farm Override
if (Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 && Math.abs(y - FARM_CENTER_Y) <= FARM_SIZE / 2) { if (Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 && Math.abs(y - FARM_CENTER_Y) <= FARM_SIZE / 2) {
terrainType = this.terrainTypes.DIRT; terrainType = this.terrainTypes.DIRT;
} }
// CITY AREA - 15x15 območje z OBZIDJEM
// City Override
if (x >= CITY_START_X && x < CITY_START_X + CITY_SIZE && if (x >= CITY_START_X && x < CITY_START_X + CITY_SIZE &&
y >= CITY_START_Y && y < CITY_START_Y + CITY_SIZE) { y >= CITY_START_Y && y < CITY_START_Y + CITY_SIZE) {
// Preverimo, ali smo na ROBOVIH (Obzidje)
const isEdge = (x === CITY_START_X || const isEdge = (x === CITY_START_X ||
x === CITY_START_X + CITY_SIZE - 1 || x === CITY_START_X + CITY_SIZE - 1 ||
y === CITY_START_Y || y === CITY_START_Y ||
y === CITY_START_Y + CITY_SIZE - 1); y === CITY_START_Y + CITY_SIZE - 1);
if (isEdge) { if (isEdge) {
// OBZIDJE - trdno, igralec ne more čez
terrainType = { name: 'WALL_EDGE', color: 0x505050, solid: true }; terrainType = { name: 'WALL_EDGE', color: 0x505050, solid: true };
} else { } else {
// NOTRANJOST MESTA - tlakovci (pavement)
terrainType = this.terrainTypes.PAVEMENT; terrainType = this.terrainTypes.PAVEMENT;
// Naključne ruševine v mestu
if (Math.random() < 0.15) { if (Math.random() < 0.15) {
terrainType = this.terrainTypes.RUINS; terrainType = this.terrainTypes.RUINS;
} }
} }
} }
// Create Tile Data
this.tiles[y][x] = { this.tiles[y][x] = {
type: terrainType.name, type: terrainType.name,
texture: terrainType.name, texture: terrainType.name,
hasDecoration: false, hasDecoration: false,
hasCrop: false, hasCrop: false,
solid: terrainType.solid || false // Inherits from terrain type solid: terrainType.solid || false
}; };
// Place Trees dynamically during generation // Track valid positions for decorations
// this.placeTree(x, y, terrainType.name); if (terrainType.name !== 'water' && terrainType.name !== 'sand' && terrainType.name !== 'stone' && !terrainType.solid) {
// Exclude Farm/City from random decor logic
const isFarm = Math.abs(x - FARM_CENTER_X) <= (FARM_SIZE / 2 + 2) && Math.abs(y - FARM_CENTER_Y) <= (FARM_SIZE / 2 + 2);
const isCity = x >= CITY_START_X - 2 && x < CITY_START_X + CITY_SIZE + 2 && y >= CITY_START_Y - 2 && y < CITY_START_Y + CITY_SIZE + 2;
// Place Rocks dynamically if (!isFarm && !isCity) {
// this.placeRock(x, y, terrainType.name); validPositions.push({ x, y });
} }
}
let treeCount = 0;
let rockCount = 0;
let flowerCount = 0;
const validPositions = [];
const isFarm = (x, y) => Math.abs(x - FARM_CENTER_X) <= (FARM_SIZE / 2 + 2) && Math.abs(y - FARM_CENTER_Y) <= (FARM_SIZE / 2 + 2);
const isCity = (x, y) => x >= CITY_START_X - 2 && x < CITY_START_X + CITY_SIZE + 2 && y >= CITY_START_Y - 2 && y < CITY_START_Y + CITY_SIZE + 2;
for (let y = 5; y < this.height - 5; y++) {
for (let x = 5; x < this.width - 5; x++) {
if (isFarm(x, y) || isCity(x, y)) continue;
const tile = this.tiles[y][x];
if (tile.type !== 'water' && tile.type !== 'sand' && tile.type !== 'stone') {
validPositions.push({ x, y });
} }
// Direct Placement Calls (Trees/Rocks) - legacy method support
// this.placeTree(x, y, terrainType.name); // Optional: keep this if preferred over random batch
} }
} }
// DECORATIONS - Enhanced World Details // --- CHUNK DECORATION PASS ---
console.log('🌸 Adding enhanced decorations...'); // Instead of global counts, we use probability/density per chunk
// 10x10 = 100 tiles.
// Approx density: 0.2 trees per tile = 20 trees per chunk.
// Natural Path Stones (along roads and random areas) // 1. Random Decorations
let pathStoneCount = 0; validPositions.forEach(pos => {
for (let i = 0; i < 50; i++) { // Trees
const pos = validPositions[Math.floor(Math.random() * validPositions.length)]; if (Math.random() < 0.05) { // 5% chance per valid tile
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) { this.placeTree(pos.x, pos.y, 'grass'); // force check inside
this.addDecoration(pos.x, pos.y, 'path_stone');
pathStoneCount++;
} }
}
// Small decorative rocks // Rocks
let smallRockCount = 0; if (Math.random() < 0.02) {
for (let i = 0; i < 80; i++) { this.placeRock(pos.x, pos.y, 'grass');
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
const rockType = Math.random() > 0.5 ? 'small_rock_1' : 'small_rock_2';
this.addDecoration(pos.x, pos.y, rockType);
smallRockCount++;
} }
}
// Flower clusters // Flowers
flowerCount = 0; if (Math.random() < 0.05) {
for (let i = 0; i < 100; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
const flowers = ['flower_red', 'flower_yellow', 'flower_blue']; const flowers = ['flower_red', 'flower_yellow', 'flower_blue'];
const flowerType = flowers[Math.floor(Math.random() * flowers.length)]; const flowerType = flowers[Math.floor(Math.random() * flowers.length)];
this.addDecoration(pos.x, pos.y, flowerType); this.addDecoration(pos.x, pos.y, flowerType);
flowerCount++;
} }
}
// Mushrooms (spooky atmosphere) // Path Stones
let mushroomCount = 0; if (Math.random() < 0.02) {
for (let i = 0; i < 60; i++) { this.addDecoration(pos.x, pos.y, 'path_stone');
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
const mushroomType = Math.random() > 0.5 ? 'mushroom_red' : 'mushroom_brown';
this.addDecoration(pos.x, pos.y, mushroomType);
mushroomCount++;
} }
} });
// Fallen Logs (forest debris)
let logCount = 0;
for (let i = 0; i < 25; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
this.addDecoration(pos.x, pos.y, 'fallen_log');
logCount++;
}
}
// Puddles (will appear during rain)
this.puddlePositions = [];
for (let i = 0; i < 40; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
this.puddlePositions.push({ x: pos.x, y: pos.y });
}
}
console.log(`✅ Decorations: ${pathStoneCount} paths, ${smallRockCount} rocks, ${flowerCount} flowers, ${mushroomCount} mushrooms, ${logCount} logs.`);
} }
updateChunks(camera) {
// Calculate which chunks are in view
const view = camera.worldView;
const buffer = 100; // Load slightly outside view
const p1 = this.iso.toGrid(view.x - buffer, view.y - buffer);
const p2 = this.iso.toGrid(view.x + view.width + buffer, view.y + view.height + buffer);
const minCx = Math.floor(Math.min(p1.x, p2.x) / this.chunkSize);
const maxCx = Math.ceil(Math.max(p1.x, p2.x) / this.chunkSize);
const minCy = Math.floor(Math.min(p1.y, p2.y) / this.chunkSize);
const maxCy = Math.ceil(Math.max(p1.y, p2.y) / this.chunkSize);
for (let cy = minCy; cy <= maxCy; cy++) {
for (let cx = minCx; cx <= maxCx; cx++) {
this.generateChunk(cx, cy);
}
}
}
// Retained helper methods...
damageDecoration(x, y, amount) { damageDecoration(x, y, amount) {
const key = `${x},${y}`; const key = `${x},${y}`;
const decor = this.decorationsMap.get(key); const decor = this.decorationsMap.get(key);
@@ -704,6 +717,8 @@ class TerrainSystem {
} }
updateCulling(camera) { updateCulling(camera) {
this.updateChunks(camera);
const view = camera.worldView; const view = camera.worldView;
let buffer = 200; let buffer = 200;
if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') buffer = 50; if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') buffer = 50;

View File

@@ -71,8 +71,26 @@ class WeatherSystem {
if (phase !== this.currentPhase) { if (phase !== this.currentPhase) {
this.currentPhase = phase; this.currentPhase = phase;
console.log(`🌅 Time of Day: ${phase} (${Math.floor(this.gameTime)}:00)`); console.log(`🌅 Time of Day: ${phase} (${Math.floor(this.gameTime)}:00)`);
// Trigger Bat Swarm at Dusk
if (phase === 'dusk' && this.scene.worldEventSystem) {
this.scene.worldEventSystem.spawnBatSwarm();
}
} }
// Trigger Night Owl at Midnight (00:00)
// We check if seconds crossed 0.
if (Math.abs(this.gameTime - 0.0) < 0.05) {
// Only trigger once per night - check active flag or similar?
// Actually, gameTime will pass 0 very quickly but maybe multiple frames.
// We can use a flag resetting at day.
if (!this.owlTriggered && this.scene.worldEventSystem) {
this.scene.worldEventSystem.spawnNightOwl();
this.owlTriggered = true;
}
}
if (this.gameTime > 5) this.owlTriggered = false; // Reset flag at dawn
// Update UI Clock // Update UI Clock
const uiScene = this.scene.scene.get('UIScene'); const uiScene = this.scene.scene.get('UIScene');
if (uiScene && uiScene.clockText) { if (uiScene && uiScene.clockText) {

View File

@@ -12,12 +12,14 @@ class WorkstationSystem {
this.recipes = { this.recipes = {
'furnace': [ 'furnace': [
{ input: 'ore_iron', fuel: 'coal', output: 'iron_bar', time: 5000 }, { input: 'ore_iron', fuel: 'coal', output: 'iron_bar', time: 5000 },
{ input: 'ore_gold', fuel: 'coal', output: 'gold_bar', time: 8000 },
{ input: 'ore_stone', fuel: 'coal', output: 'stone_brick', time: 3000 }, { input: 'ore_stone', fuel: 'coal', output: 'stone_brick', time: 3000 },
{ input: 'sand', fuel: 'coal', output: 'glass', time: 3000 }, { input: 'sand', fuel: 'coal', output: 'glass', time: 3000 },
{ input: 'log', fuel: 'log', output: 'charcoal', time: 4000 } // Wood to charcoal { input: 'log', fuel: 'log', output: 'charcoal', time: 4000 } // Wood to charcoal
], ],
'mint': [ 'mint': [
{ input: 'iron_bar', fuel: 'coal', output: 'coin_gold', time: 2000, outputCount: 10 } { input: 'iron_bar', fuel: 'coal', output: 'coin_gold', time: 2000, outputCount: 10 },
{ input: 'gold_bar', fuel: 'coal', output: 'coin_gold', time: 3000, outputCount: 50 }
], ],
'campfire': [ 'campfire': [
{ input: 'raw_meat', fuel: 'stick', output: 'cooked_meat', time: 3000 } { input: 'raw_meat', fuel: 'stick', output: 'cooked_meat', time: 3000 }

View File

@@ -0,0 +1,137 @@
class WorldEventSystem {
constructor(scene) {
this.scene = scene;
this.activeEvents = [];
this.bats = [];
this.owl = null;
}
/*
* BATS: Visual effect for evening/night
*/
spawnBatSwarm() {
console.log('🦇 Bat Swarm Incoming!');
const count = 10 + Math.floor(Math.random() * 10);
for (let i = 0; i < count; i++) {
// Start right-side of screen, random height
const sx = this.scene.scale.width + 50 + Math.random() * 200;
const sy = Math.random() * (this.scene.scale.height / 2); // Top half
const bat = this.scene.add.sprite(sx, sy, 'bat');
bat.setDepth(2000); // Above most things
bat.setScrollFactor(0); // Screen space (UI layer) or World space?
// Better world space if we want them to feel like part of the world, but screen space is easier for "effect".
// Let's stick to Screen Space for "Ambience".
// Random speed
const speed = 150 + Math.random() * 100;
this.bats.push({ sprite: bat, speed: speed });
}
}
/*
* NIGHT OWL: Delivers a gift
*/
spawnNightOwl() {
if (this.owl) return; // Only one at a time
console.log('🦉 Night Owl Arriving!');
// Spawn top-left
const sx = -50;
const sy = 100;
const owl = this.scene.add.sprite(sx, sy, 'owl');
owl.setDepth(2000);
owl.setScrollFactor(0); // Screen space for delivery
this.owl = {
sprite: owl,
state: 'ARRIVING', // ARRIVING, DELIVERING, LEAVING
timer: 0,
targetX: this.scene.scale.width / 2,
targetY: this.scene.scale.height / 3
};
if (this.scene.soundManager) {
// Play owl hoot sound (placeholder or generic)
// this.scene.soundManager.playHoot();
}
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 120,
text: '🦉 Hoot Hoot!',
color: '#FFA500'
});
}
update(delta) {
// 1. Bats
for (let i = this.bats.length - 1; i >= 0; i--) {
const b = this.bats[i];
b.sprite.x -= b.speed * (delta / 1000); // Fly left
b.sprite.y += (Math.sin(b.sprite.x * 0.02) * 2); // Wobbly flight
if (b.sprite.x < -50) {
b.sprite.destroy();
this.bats.splice(i, 1);
}
}
// 2. Owl
if (this.owl) {
const o = this.owl;
const speed = 200 * (delta / 1000);
if (o.state === 'ARRIVING') {
const dx = o.targetX - o.sprite.x;
const dy = o.targetY - o.sprite.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 10) {
o.state = 'DELIVERING';
o.timer = 0;
this.dropGift();
} else {
o.sprite.x += (dx / dist) * speed;
o.sprite.y += (dy / dist) * speed;
}
} else if (o.state === 'DELIVERING') {
o.timer += delta;
if (o.timer > 1000) { // Hover for 1s
o.state = 'LEAVING';
}
} else if (o.state === 'LEAVING') {
o.sprite.x += speed; // Fly right
o.sprite.y -= speed * 0.5; // Fly up
if (o.sprite.x > this.scene.scale.width + 50) {
o.sprite.destroy();
this.owl = null;
}
}
}
}
dropGift() {
console.log('🎁 Owl dropped a gift!');
const p = this.scene.player;
// Spawn loot at player pos (World Space)
if (this.scene.interactionSystem) {
// Random gift: Gold or maybe a rare item
const r = Math.random();
let item = 'flower_red';
let count = 1;
if (r < 0.3) { item = 'coin_gold'; count = 10; }
else if (r < 0.6) { item = 'seeds_corn'; count = 5; }
else if (r < 0.9) { item = 'item_scrap'; count = 3; }
else { item = 'artefact_old'; count = 1; } // Rare!
this.scene.interactionSystem.spawnLoot(p.gridX, p.gridY, item, count);
}
}
}

View File

@@ -63,6 +63,8 @@ class ZombieWorkerSystem {
this.performFarmWork(worker); this.performFarmWork(worker);
} else if (worker.workerData.type === 'MINE') { } else if (worker.workerData.type === 'MINE') {
this.performMineWork(worker); this.performMineWork(worker);
} else if (worker.workerData.type === 'CLEAR') {
this.performClearWork(worker);
} }
} }
} }
@@ -176,6 +178,41 @@ class ZombieWorkerSystem {
wd.status = 'IDLE'; wd.status = 'IDLE';
} }
performClearWork(zombie) {
const wd = zombie.workerData;
const terrain = this.scene.terrainSystem;
if (!terrain || !terrain.decorationsMap) return;
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const key = `${wd.centerX + dx},${wd.centerY + dy}`;
if (terrain.decorationsMap.has(key)) {
const decor = terrain.decorationsMap.get(key);
const t = decor.type;
// Clear trees, bushes, logs, rocks
if (t.startsWith('tree') || t.startsWith('bush') || t === 'fallen_log' || t === 'stone' || t.startsWith('small_rock')) {
terrain.removeDecoration(wd.centerX + dx, wd.centerY + dy);
// Give some resources
if (this.scene.inventorySystem) {
if (t.startsWith('tree') || t === 'fallen_log' || t.startsWith('bush')) {
this.scene.inventorySystem.addItem('wood', 1);
} else {
this.scene.inventorySystem.addItem('stone', 1);
}
}
console.log(`🧟🪓 Worker CLEARED ${t}`);
wd.status = 'WORKING';
return; // One per tick
}
}
}
}
wd.status = 'IDLE';
}
onWorkerDeath(zombie) { onWorkerDeath(zombie) {
console.log(`💀 Worker died at ${zombie.gridX},${zombie.gridY}`); console.log(`💀 Worker died at ${zombie.gridX},${zombie.gridY}`);

View File

@@ -192,6 +192,43 @@ class TextureGenerator {
// Details // Details
ctx.fillStyle = '#555555'; ctx.fillStyle = '#555555';
ctx.beginPath(); ctx.arc(25, 45, 6, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(25, 45, 6, 0, Math.PI * 2); ctx.fill();
} else if (type === 'house' || type === 'house_lv2') {
// --- ISOMETRIC HOUSE ---
const isLv2 = (type === 'house_lv2');
// Walls
const wallColor = isLv2 ? '#A0522D' : '#8B4513'; // Lv2 is lighter/refined
ctx.fillStyle = wallColor;
ctx.fillRect(10, 24, 44, 32);
// Roof (Triangle/Pyramid-ish)
const roofColor = isLv2 ? '#8B0000' : '#4682B4'; // Lv2 Red Roof, Lv1 Blue
ctx.fillStyle = roofColor;
ctx.beginPath();
ctx.moveTo(32, 2); // Top
ctx.lineTo(8, 24); // Left
ctx.lineTo(56, 24); // Right
ctx.fill();
// Door
ctx.fillStyle = '#5C4033'; // Dark Wood
ctx.fillRect(26, 40, 12, 16);
// Windows
ctx.fillStyle = '#87CEEB'; // SkyBlue
ctx.fillRect(14, 34, 8, 8); // Window 1
ctx.fillRect(42, 34, 8, 8); // Window 2
// Lv2 Extras: Chimney or extra floor indicator
if (isLv2) {
ctx.fillStyle = '#555';
ctx.fillRect(40, 10, 6, 12); // Chimney
// Second floor window
ctx.fillStyle = '#87CEEB';
ctx.beginPath(); ctx.arc(32, 16, 4, 0, Math.PI * 2); ctx.fill();
}
} else { } else {
// Generic box for others // Generic box for others
ctx.fillStyle = '#8B4513'; ctx.fillStyle = '#8B4513';
@@ -605,7 +642,8 @@ class TextureGenerator {
{ name: 'seeds_corn', color: '#B22222' },// FireBrick seeds { name: 'seeds_corn', color: '#B22222' },// FireBrick seeds
{ name: 'item_bone', color: '#F5F5DC' }, // Beige { name: 'item_bone', color: '#F5F5DC' }, // Beige
{ name: 'item_scrap', color: '#B87333' }, // Copper/Bronze (kovinski kos) { name: 'item_scrap', color: '#B87333' }, // Copper/Bronze (kovinski kos)
{ name: 'item_chip', color: '#00CED1' } // DarkTurquoise (elektronski chip) { name: 'item_chip', color: '#00CED1' }, // DarkTurquoise (elektronski chip)
{ name: 'artefact_old', color: '#8B4513' } // Ancient Pot (Brown)
]; ];
items.forEach(item => { items.forEach(item => {
const it = typeof item === 'string' ? item : item.name; const it = typeof item === 'string' ? item : item.name;
@@ -754,6 +792,190 @@ class TextureGenerator {
TextureGenerator.createPuddleSprite(this.scene, 'puddle'); TextureGenerator.createPuddleSprite(this.scene, 'puddle');
TextureGenerator.createFurnaceSprite(this.scene, 'furnace'); TextureGenerator.createFurnaceSprite(this.scene, 'furnace');
TextureGenerator.createMintSprite(this.scene, 'mint'); TextureGenerator.createMintSprite(this.scene, 'mint');
TextureGenerator.createOwlSprite(this.scene, 'owl');
TextureGenerator.createBatSprite(this.scene, 'bat');
// Mutants
TextureGenerator.createTrollSprite(this.scene, 'troll');
TextureGenerator.createElfSprite(this.scene, 'elf');
// Animals
TextureGenerator.createCowSprite(this.scene, 'cow', false);
TextureGenerator.createCowSprite(this.scene, 'cow_mutant', true);
TextureGenerator.createChickenSprite(this.scene, 'chicken', false);
TextureGenerator.createChickenSprite(this.scene, 'chicken_mutant', true);
}
static createOwlSprite(scene, key = 'owl') {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 32, 32);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 32, 32);
// Body
ctx.fillStyle = '#8B4513'; // SaddleBrown
ctx.fillRect(8, 8, 16, 20);
// Eyes
ctx.fillStyle = '#FFD700'; // Gold eyes
ctx.beginPath(); ctx.arc(12, 12, 3, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(20, 12, 3, 0, Math.PI * 2); ctx.fill();
// Pupils
ctx.fillStyle = '#000';
ctx.beginPath(); ctx.arc(12, 12, 1, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(20, 12, 1, 0, Math.PI * 2); ctx.fill();
// Beak
ctx.fillStyle = '#FFA500'; // Orange
ctx.beginPath();
ctx.moveTo(16, 14); ctx.lineTo(14, 18); ctx.lineTo(18, 18);
ctx.fill();
// Wings (folded)
ctx.fillStyle = '#A0522D';
ctx.fillRect(6, 12, 4, 12);
ctx.fillRect(22, 12, 4, 12);
canvas.refresh();
}
static createBatSprite(scene, key = 'bat') {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 32, 32);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 32, 32);
// Body
ctx.fillStyle = '#222';
ctx.beginPath(); ctx.ellipse(16, 16, 4, 6, 0, 0, Math.PI * 2); ctx.fill();
// Wings
ctx.fillStyle = '#333';
// Left Wing
ctx.beginPath();
ctx.moveTo(12, 16); ctx.lineTo(2, 6); ctx.lineTo(4, 20);
ctx.closePath(); ctx.fill();
// Right Wing
ctx.beginPath();
ctx.moveTo(20, 16); ctx.lineTo(30, 6); ctx.lineTo(28, 20);
ctx.closePath(); ctx.fill();
// Eyes
ctx.fillStyle = '#f00';
ctx.fillRect(15, 14, 1, 1);
ctx.fillRect(17, 14, 1, 1);
canvas.refresh();
}
static createTrollSprite(scene, key) {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 48, 48); // Bigger
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 48, 48);
// Body (Big, Green)
ctx.fillStyle = '#228B22'; // ForestGreen
ctx.fillRect(10, 10, 28, 30);
// Head
ctx.fillStyle = '#105510';
ctx.fillRect(14, 4, 20, 16);
// Eyes (Red)
ctx.fillStyle = '#ff0000';
ctx.fillRect(18, 10, 4, 4);
ctx.fillRect(26, 10, 4, 4);
// Club
ctx.fillStyle = '#5c4033';
ctx.fillRect(36, 12, 8, 24);
canvas.refresh();
}
static createElfSprite(scene, key) {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 32, 48);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 32, 48);
// Body (Slim, Pale)
ctx.fillStyle = '#98FB98'; // PaleGreen (Mutated Elf)
ctx.fillRect(10, 16, 12, 24);
// Head
ctx.fillStyle = '#E0FFFF'; // LightCyan
ctx.fillRect(10, 4, 12, 12);
// Ears (Pointy)
ctx.fillStyle = '#E0FFFF';
ctx.beginPath(); ctx.moveTo(8, 8); ctx.lineTo(4, 4); ctx.lineTo(10, 12); ctx.fill();
ctx.beginPath(); ctx.moveTo(24, 8); ctx.lineTo(28, 4); ctx.lineTo(22, 12); ctx.fill();
// Eyes (Glowing)
ctx.fillStyle = '#00FFFF';
ctx.fillRect(12, 8, 2, 2);
ctx.fillRect(18, 8, 2, 2);
canvas.refresh();
}
static createCowSprite(scene, key, activeMutation) {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 48, 32);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 48, 32);
// Body
ctx.fillStyle = activeMutation ? '#9ACD32' : '#FFFFFF'; // YellowGreen or White
ctx.fillRect(8, 10, 32, 16);
// Spots (if normal)
if (!activeMutation) {
ctx.fillStyle = '#000000';
ctx.fillRect(14, 12, 6, 6);
ctx.fillRect(28, 18, 8, 4);
} else {
// Glowing veins
ctx.fillStyle = '#00FF00';
ctx.fillRect(12, 14, 24, 2);
}
// Head
ctx.fillStyle = activeMutation ? '#9ACD32' : '#FFFFFF';
ctx.fillRect(0, 8, 12, 12);
// Legs
ctx.fillStyle = '#000000';
ctx.fillRect(10, 26, 4, 6);
ctx.fillRect(34, 26, 4, 6);
canvas.refresh();
}
static createChickenSprite(scene, key, activeMutation) {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 24, 24);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 24, 24);
// Body
ctx.fillStyle = activeMutation ? '#ADFF2F' : '#FFFFFF'; // GreenYellow or White
ctx.beginPath(); ctx.arc(12, 14, 8, 0, Math.PI * 2); ctx.fill();
// Head
ctx.beginPath(); ctx.arc(16, 8, 5, 0, Math.PI * 2); ctx.fill();
// Beak
ctx.fillStyle = '#ffaa00';
ctx.beginPath(); ctx.moveTo(20, 8); ctx.lineTo(24, 10); ctx.lineTo(20, 12); ctx.fill();
// Comb
ctx.fillStyle = '#ff0000';
ctx.fillRect(15, 3, 2, 3);
canvas.refresh();
} }
static createPathStoneSprite(scene, key = 'path_stone') { static createPathStoneSprite(scene, key = 'path_stone') {