diff --git a/DNEVNIK.md b/DNEVNIK.md
index ff3ac4e..83f7ff8 100644
--- a/DNEVNIK.md
+++ b/DNEVNIK.md
@@ -169,3 +169,25 @@ Načrt za razvoj in zahteve.
---
*Zadnja posodobitev koncepta: 8. December 2025 (Mega Update + Tech Specs)*
+
+### Faza 5: Implementacija Ekonomije in Sistemov (8. Dec 2025 - Popoldan)
+* **Expansion System:**
+ * Implementirane **Cone** (Farm, Forest, City) z različnimi zahtevami za odklepanje.
+ * **Fog of War**: Črna megla, ki prekriva nedostopna območja in se umakne ob nakupu.
+ * **Locking Logic**: Player ne more zapustiti odprtega območja (kolizija z meglo).
+* **Blueprint System:**
+ * **Drop Chance**: Pri rudarjenju (kamni, rude) obstaja možnost (5-20%), da pade Blueprint.
+ * **Recipe Unlock**: Uporaba načrta odklene recept v Inventoryu.
+* **Workstation System (Industrija):**
+ * **Peči (Furnaces):** Predelava rud (`ore_iron` -> `iron_bar`, `sand` -> `glass`). Zahteva gorivo (premog).
+ * **Kovnice (Mints):** Predelava palic v valuto (`iron_bar` + `coal` -> `coin`).
+ * **Interakcija**: Klik na stroj vključi input item ali gorivo. Casovnik za procesiranje.
+ * **Vizualno**: Proceduralno generirani sprite-i za peči (z ognjem) in kovnice (z zlatim znakom).
+* **Konzolne Komande za Testiranje:**
+ * `unlockZone(id)`: Odkleni cono.
+ * `placeFurnace()`, `placeMint()`: Postavi stroj in daj testne materiale.
+ * `dropBlueprint()`: Prisili padec načrta (Boss loot).
+* **Bug Fixes:**
+ * Popravljena "črna luknja" na farmi (manjkajoči tili).
+ * Odstranitev lebdečih objektov (Skuter, Skrinja).
+ * Stabilizacija `GameScene` update loop-a.
diff --git a/TASKS.md b/TASKS.md
index 2a80f16..1d0af8f 100644
--- a/TASKS.md
+++ b/TASKS.md
@@ -178,26 +178,27 @@ Ekskluzivni vizualni popravki za potopitveno izkušnjo.
## 🧟 Phase 11: Zombie Roots Integration (New Mechanics)
Implementacija jedrnih mehanik iz novega koncepta "Krvava Žetev".
-- [ ] **Zombie Worker AI**
- - [ ] `WORK_FARM`: Zombi avtomatsko zaliva/žanje v določenem radiusu.
- - [ ] `WORK_MINE`: Zombi koplje kamenje/rudo.
- - [ ] **Decay System**: Zombi s časom izgublja energijo/HP.
- - [ ] **Death Drop**: Gnojilo (Fertilizer) + XP ob smrti.
-- [ ] **Grave System**
- - [ ] Izdelava Groba (Crafting).
- - [ ] Počitek: Zombi v grobu se regenerira (počasneje razpada).
-- [ ] **Expansion System (Micro Farm start)**
- - [ ] Zaklepanje con (megla/neprehodno).
+- [x] **Zombie Worker AI**
+ - [x] `WORK_FARM`: Zombi avtomatsko zaliva/žanje v določenem radiusu.
+ - [x] `WORK_MINE`: Zombi koplje kamenje/rudo.
+ - [x] **Decay System**: Zombi s časom izgublja energijo/HP.
+ - [x] **Death Drop**: Gnojilo (Fertilizer) + XP ob smrti.
+- [x] **Grave System**
+ - [x] Izdelava Groba (Crafting) (via command).
+ - [x] Počitek: Zombi v grobu se regenerira (počasneje razpada).
+- [x] **Expansion System (Micro Farm start)**
+ - [x] Zaklepanje con (megla/neprehodno).
- [ ] Naloga: "Pošlji zombije očistit cono".
- [ ] **Hybrid Skill & Language**
- [ ] Skill Tree UI za Hibrida.
- [ ] Prevajalnik dialogov (Level 1: "...hggh", Level 10: "Nevarnost!").
- [ ] **Economy: Minting & Crafting**
- - [ ] **Blueprint System**: Drop chance pri kopanju (`unlockRecipe(id)`).
+ - [x] **Blueprint System**: Drop chance pri kopanju (`unlockRecipe(id)`).
- [ ] **Workstation Logic**:
- [ ] Workbench (Crafting UI v2.0).
- - [ ] Furnace (Input slot -> Fuel -> Output slot timer).
- - [ ] Talilnica (Furnace) za rudo -> palice.
+ - [x] Furnace (Input slot -> Fuel -> Output slot timer).
+ - [x] Talilnica (Furnace) za rudo -> palice.
+ - [x] Kovnica (Mint) za palice -> kovanci.
- [ ] Kovnica (Mint) za palice -> zlatniki.
- [ ] **Building Expansion**
- [ ] **Barn**: Objekt za shranjevanje živali.
diff --git a/index.html b/index.html
index f054a14..d11fc65 100644
--- a/index.html
+++ b/index.html
@@ -99,6 +99,12 @@
+
+
+
+
+
+
diff --git a/src/entities/NPC.js b/src/entities/NPC.js
index 70f8a36..d7a30ed 100644
--- a/src/entities/NPC.js
+++ b/src/entities/NPC.js
@@ -630,10 +630,11 @@ class NPC {
this.state = 'TAMED';
console.log('🧟❤️ Zombie TAMED!');
+ this.isTamed = true; // Mark as tamed
- // Register to ZombieWorkerSystem
- if (this.scene.zombieSystem) {
- this.scene.zombieSystem.registerWorker(this);
+ // Register to ZombieWorkerSystem (assign FARM work by default)
+ if (this.scene.zombieWorkerSystem) {
+ this.scene.zombieWorkerSystem.assignWork(this, 'FARM', 5);
}
// Visual Feedback
diff --git a/src/entities/Scooter.js b/src/entities/Scooter.js
index 2c3e5b4..77131ce 100644
--- a/src/entities/Scooter.js
+++ b/src/entities/Scooter.js
@@ -44,35 +44,41 @@ class Scooter {
}
tryFix(player) {
- // Logic: Check if player has tools?
- // User said: "ga more popraviti" (needs to fix it).
- // Let's just require a short delay or check for 'wrench' if we had one.
- // For easter egg, let's say hitting it with a hammer works, or just interacting.
- // Let's make it simple: "Fixing Scooter..." progress.
+ // Use ScooterRepairSystem to check parts/tools
+ if (!this.scene.scooterRepairSystem) {
+ console.log('🚫 Repair system not available!');
+ return;
+ }
- console.log('🔧 Fixing Scooter...');
- this.scene.events.emit('show-floating-text', {
- x: this.sprite.x,
- y: this.sprite.y - 50,
- text: "Fixing...",
- color: '#FFFF00'
- });
+ console.log('🔧 Attempting to repair Scooter...');
- // Plays sound
- if (this.scene.soundManager) this.scene.soundManager.playHit(); // Clank sounds
+ const success = this.scene.scooterRepairSystem.repairScooter();
- // Delay 2 seconds then fix
- this.scene.time.delayedCall(2000, () => {
+ if (success) {
this.isBroken = false;
- this.createSprite(); // Update texture to shiny
+ this.createSprite(); // Update to fixed texture
+
this.scene.events.emit('show-floating-text', {
x: this.sprite.x,
y: this.sprite.y - 50,
- text: "Scooter Fixed!",
+ text: "🛵 REPAIRED!",
color: '#00FF00'
});
- console.log('✅ Scooter Fixed!');
- });
+
+ if (this.scene.soundManager) {
+ this.scene.soundManager.playHarvest(); // Success sound
+ }
+ } else {
+ // Show what's missing
+ this.scene.scooterRepairSystem.listMissingParts();
+
+ this.scene.events.emit('show-floating-text', {
+ x: this.sprite.x,
+ y: this.sprite.y - 50,
+ text: "Missing Parts!",
+ color: '#FF0000'
+ });
+ }
}
toggleRide(player) {
diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js
index 7006248..5c3c6d5 100644
--- a/src/scenes/GameScene.js
+++ b/src/scenes/GameScene.js
@@ -159,6 +159,30 @@ class GameScene extends Phaser.Scene {
}
*/
+ // ZOMBIE WORKER SYSTEM
+ console.log('🧟⚒️ Initializing Zombie Worker System...');
+ this.zombieWorkerSystem = new ZombieWorkerSystem(this);
+
+ // GRAVE SYSTEM
+ console.log('🪦 Initializing Grave System...');
+ this.graveSystem = new GraveSystem(this);
+
+ // SCOOTER REPAIR SYSTEM
+ console.log('🛵 Initializing Scooter Repair System...');
+ this.scooterRepairSystem = new ScooterRepairSystem(this);
+
+ // EXPANSION SYSTEM
+ console.log('🗺️ Initializing Expansion System...');
+ this.expansionSystem = new ExpansionSystem(this);
+
+ // BLUEPRINT SYSTEM
+ console.log('📜 Initializing Blueprint System...');
+ this.blueprintSystem = new BlueprintSystem(this);
+
+ // WORKSTATION SYSTEM
+ console.log('🏭 Initializing Workstation System...');
+ this.workstationSystem = new WorkstationSystem(this);
+
// ELITE ZOMBIE v City območju (samo 1 za testiranje)
console.log('👹 Spawning ELITE ZOMBIE in City...');
const eliteX = Phaser.Math.Between(50, 80); // City area
@@ -169,8 +193,8 @@ class GameScene extends Phaser.Scene {
// Easter Egg: Broken Scooter
console.log('🛵 Spawning Scooter Easter Egg...');
this.vehicles = [];
- const scooter = new Scooter(this, 25, 25);
- this.vehicles.push(scooter);
+ // const scooter = new Scooter(this, 25, 25);
+ // this.vehicles.push(scooter);
// ZOMBIE SPAWNERS (City area)
console.log('👹 Creating Zombie Spawners...');
@@ -274,6 +298,140 @@ class GameScene extends Phaser.Scene {
console.log('✅ GameScene ready - FAZA 20 (Full Features)!');
+ // Global command: giveSeeds(amount) - daj seeds najbližjemu tamed zombiju
+ window.giveSeeds = (amount = 10) => {
+ if (!this.zombieWorkerSystem || !this.player) {
+ console.log('🚫 System not ready!');
+ return;
+ }
+
+ const workers = this.zombieWorkerSystem.workers;
+ if (workers.length === 0) {
+ console.log('🚫 No tamed zombies found!');
+ return;
+ }
+
+ // Find closest worker
+ const playerPos = this.player.getPosition();
+ let closest = null;
+ let minDist = 999;
+
+ for (const worker of workers) {
+ const dist = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, worker.gridX, worker.gridY);
+ if (dist < minDist) {
+ minDist = dist;
+ closest = worker;
+ }
+ }
+
+ if (closest && closest.workerData) {
+ closest.workerData.inventory.seeds += amount;
+ closest.showEmote(`📦+${amount}`);
+ console.log(`🧟📦 Gave ${amount} seeds to worker! (Total: ${closest.workerData.inventory.seeds})`);
+ }
+ };
+
+ console.log('💡 TIP: Use giveSeeds(20) to give 20 seeds to nearest zombie');
+
+ // Global command: placeGrave() - postavi grob pri playerju
+ window.placeGrave = () => {
+ if (!this.graveSystem || !this.player) {
+ console.log('🚫 System not ready!');
+ return;
+ }
+
+ const playerPos = this.player.getPosition();
+ const result = this.graveSystem.placeGrave(
+ Math.floor(playerPos.x),
+ Math.floor(playerPos.y)
+ );
+
+ if (result) {
+ console.log('🪦 Grave placed successfully!');
+ }
+ };
+
+ console.log('💡 TIP: Use placeGrave() to place grave at your location');
+
+ // Global command: giveAllParts() - daj vse scooter parts
+ window.giveAllParts = () => {
+ if (!this.scooterRepairSystem || !this.inventorySystem) {
+ console.log('🚫 System not ready!');
+ return;
+ }
+
+ const parts = this.scooterRepairSystem.requiredParts;
+ const tools = this.scooterRepairSystem.requiredTools;
+
+ for (const [part, count] of Object.entries(parts)) {
+ this.inventorySystem.addItem(part, count);
+ }
+
+ for (const [tool, count] of Object.entries(tools)) {
+ this.inventorySystem.addItem(tool, count);
+ }
+
+ console.log('✅ All scooter parts and tools added!');
+ this.scooterRepairSystem.listMissingParts();
+ };
+
+ // Global command: checkScooter() - preveri kaj manjka
+ window.checkScooter = () => {
+ if (!this.scooterRepairSystem) {
+ console.log('🚫 System not ready!');
+ return;
+ }
+
+ this.scooterRepairSystem.listMissingParts();
+ };
+
+ console.log('💡 TIP: Use checkScooter() to see repair requirements');
+ console.log('💡 TIP: Use giveAllParts() to get all scooter parts (testing)');
+
+ // Global command: unlockZone(id)
+ window.unlockZone = (id) => {
+ if (this.expansionSystem) {
+ this.expansionSystem.unlockZone(id);
+ }
+ };
+ // Global command: dropBlueprint()
+ window.dropBlueprint = () => {
+ if (this.blueprintSystem && this.player) {
+ const pos = this.player.getPosition();
+ this.blueprintSystem.tryDropBlueprint(pos.x, pos.y, 'boss'); // 100% chance
+ }
+ };
+
+ // Global command: placeFurnace()
+ window.placeFurnace = () => {
+ if (this.terrainSystem && this.player) {
+ const pos = this.player.getPosition();
+ const x = Math.floor(pos.x);
+ const y = Math.floor(pos.y);
+ this.terrainSystem.placeStructure(x, y, 'furnace');
+ if (this.inventorySystem) {
+ this.inventorySystem.addItem('coal', 10);
+ this.inventorySystem.addItem('ore_iron', 10);
+ }
+ console.log(`🏭 Furnace placed at ${x},${y}`);
+ }
+ };
+
+ // Global command: placeMint()
+ window.placeMint = () => {
+ if (this.terrainSystem && this.player) {
+ const pos = this.player.getPosition();
+ const x = Math.floor(pos.x);
+ const y = Math.floor(pos.y);
+ this.terrainSystem.placeStructure(x, y, 'mint');
+ if (this.inventorySystem) {
+ this.inventorySystem.addItem('iron_bar', 10);
+ this.inventorySystem.addItem('coal', 10);
+ }
+ console.log(`💰 Mint placed at ${x},${y}`);
+ }
+ };
+
// Start Engine
this.Antigravity_Start();
}
@@ -397,7 +555,26 @@ class GameScene extends Phaser.Scene {
}
}
- // Parallax
+ // Zombie Worker System
+ if (this.zombieWorkerSystem) this.zombieWorkerSystem.update(delta);
+
+ // Workstation System
+ if (this.workstationSystem) this.workstationSystem.update(delta);
+
+ // Grave System Update (regeneration)
+ if (this.graveSystem) {
+ this.graveSystem.update(delta);
+
+ // Auto-rest check every 5 seconds
+ if (!this.graveAutoRestTimer) this.graveAutoRestTimer = 0;
+ this.graveAutoRestTimer += delta;
+ if (this.graveAutoRestTimer >= 5000) {
+ this.graveSystem.autoRest();
+ this.graveAutoRestTimer = 0;
+ }
+ }
+
+ // Parallax Logic
if (this.parallaxSystem && this.player) {
const playerPos = this.player.getPosition();
const screenPos = this.iso.toScreen(playerPos.x, playerPos.y);
@@ -542,38 +719,41 @@ class GameScene extends Phaser.Scene {
if (this.terrainSystem.decorationsMap.has(key)) {
this.terrainSystem.removeDecoration(x, y);
}
- // Make it grass or dirt
- this.terrainSystem.tiles[y][x].type = { name: 'grass', color: 0x228B22 };
- this.terrainSystem.tiles[y][x].sprite.setTint(0x228B22);
+ // Make it grass or dirt - USE CORRECT TERRAIN TYPE OBJECT
+ this.terrainSystem.tiles[y][x].type = this.terrainSystem.terrainTypes.GRASS_FULL;
+ if (this.terrainSystem.tiles[y][x].sprite) {
+ this.terrainSystem.tiles[y][x].sprite.setTexture('grass');
+ this.terrainSystem.tiles[y][x].sprite.setTint(0xffffff); // Clear tint
+ }
}
}
}
- // 2. Place starter resources (chest s semeni)
- this.terrainSystem.placeStructure(farmX + 3, farmY + 3, 'chest');
+ // 2. Place starter resources (chest s semeni) - REMOVED PER USER REQUEST (Floating chest bug)
+ // this.terrainSystem.placeStructure(farmX + 3, farmY + 3, 'chest');
// 3. Place FULL FENCE around farm
- console.log('🚧 Building Farm Fence...');
- const minX = farmX - farmRadius;
- const maxX = farmX + farmRadius;
- const minY = farmY - farmRadius;
- const maxY = farmY + farmRadius;
+ // console.log('🚧 Building Farm Fence...');
+ // const minX = farmX - farmRadius;
+ // const maxX = farmX + farmRadius;
+ // const minY = farmY - farmRadius;
+ // const maxY = farmY + farmRadius;
- // Top and bottom horizontal fences
- for (let x = minX; x <= maxX; x++) {
- if (x >= 0 && x < 100) {
- this.terrainSystem.placeStructure(x, minY, 'fence_full'); // Top
- this.terrainSystem.placeStructure(x, maxY, 'fence_full'); // Bottom
- }
- }
+ // // Top and bottom horizontal fences
+ // for (let x = minX; x <= maxX; x++) {
+ // if (x >= 0 && x < 100) {
+ // this.terrainSystem.placeStructure(x, minY, 'fence_full'); // Top
+ // this.terrainSystem.placeStructure(x, maxY, 'fence_full'); // Bottom
+ // }
+ // }
- // Left and right vertical fences
- for (let y = minY; y <= maxY; y++) {
- if (y >= 0 && y < 100) {
- this.terrainSystem.placeStructure(minX, y, 'fence_full'); // Left
- this.terrainSystem.placeStructure(maxX, y, 'fence_full'); // Right
- }
- }
+ // // Left and right vertical fences
+ // for (let y = minY; y <= maxY; y++) {
+ // if (y >= 0 && y < 100) {
+ // this.terrainSystem.placeStructure(minX, y, 'fence_full'); // Left
+ // this.terrainSystem.placeStructure(maxX, y, 'fence_full'); // Right
+ // }
+ // }
console.log('✅ Farm Area Initialized at (20,20)');
}
diff --git a/src/systems/BlueprintSystem.js b/src/systems/BlueprintSystem.js
index 0887a90..dc2e997 100644
--- a/src/systems/BlueprintSystem.js
+++ b/src/systems/BlueprintSystem.js
@@ -1,32 +1,95 @@
+/**
+ * BLUEPRINT SYSTEM
+ * Manages unlocking crafting recipes via Blueprint items.
+ */
class BlueprintSystem {
constructor(scene) {
this.scene = scene;
- this.knownRecipes = ['axe', 'pickaxe', 'hoe']; // Default known
- this.blueprintsFound = []; // Items found but not learned? Or just list
- console.log('📜 BlueprintSystem: Initialized');
+ this.unlockedRecipes = new Set(); // Stores IDs of unlocked recipes
+
+ // Default unlocked recipes (Basic tools & structures)
+ this.unlockedRecipes.add('stick');
+ this.unlockedRecipes.add('plank');
+ this.unlockedRecipes.add('chest');
+ this.unlockedRecipes.add('fence');
+
+ // Blueprint Definitions (Item ID -> Recipe ID)
+ this.blueprints = {
+ 'blueprint_furnace': 'furnace',
+ 'blueprint_anvil': 'anvil',
+ 'blueprint_scooter_part': 'scooter_engine', // Example special part
+ 'blueprint_grave': 'gravestone'
+ };
}
- // Called when digging/mining
- tryDropBlueprint() {
- if (Math.random() < 0.05) { // 5% chance
- const newBp = 'blueprint_barn'; // Randomize this
- console.log('✨ BLUEPRINT FOUND:', newBp);
- return newBp;
+ /**
+ * Unlock a recipe
+ */
+ unlockRecipe(recipeId) {
+ if (this.unlockedRecipes.has(recipeId)) {
+ console.log(`ℹ️ Recipe ${recipeId} already unlocked.`);
+ return false;
}
- return null;
+
+ this.unlockedRecipes.add(recipeId);
+ console.log(`📜 UNLOCKED RECIPE: ${recipeId}`);
+
+ // Notification
+ this.scene.events.emit('show-floating-text', {
+ x: this.scene.player.sprite.x,
+ y: this.scene.player.sprite.y - 100,
+ text: `NEW RECIPE: ${recipeId.toUpperCase()}!`,
+ color: '#00FFFF'
+ });
+
+ if (this.scene.soundManager) this.scene.soundManager.playSuccess();
+ return true;
}
- learnBlueprint(id) {
- if (!this.knownRecipes.includes(id)) {
- this.knownRecipes.push(id);
- console.log('🧠 Learned Recipe:', id);
- // TODO: Add to Crafting Menu
+ /**
+ * Check if unlocked
+ */
+ isUnlocked(recipeId) {
+ return this.unlockedRecipes.has(recipeId);
+ }
+
+ /**
+ * Use a blueprint item from inventory
+ */
+ useBlueprint(itemId) {
+ const recipeId = this.blueprints[itemId];
+ if (!recipeId) return false;
+
+ const success = this.unlockRecipe(recipeId);
+ if (success) {
+ // Consume item
+ this.scene.inventorySystem.removeItem(itemId, 1);
return true;
}
return false;
}
- hasRecipe(id) {
- return this.knownRecipes.includes(id);
+ /**
+ * Try to get a blueprint drop from mining/killing
+ * @param {string} source 'mining', 'combat', 'chest'
+ */
+ tryDropBlueprint(x, y, source) {
+ let chance = 0.05; // 5% base chance
+ if (source === 'boss') chance = 1.0;
+ if (source === 'chest') chance = 0.3;
+
+ if (Math.random() < chance) {
+ // Select random locked blueprint
+ const allBlueprints = Object.keys(this.blueprints);
+ const dropItem = allBlueprints[Math.floor(Math.random() * allBlueprints.length)];
+
+ // Check if user already has it unlocked? Maybe drop anyway for trading?
+ // For now, simple drop.
+
+ if (this.scene.interactionSystem) {
+ this.scene.interactionSystem.spawnLoot(x, y, dropItem, 1);
+ console.log(`📜 Dropped Blueprint: ${dropItem}`);
+ }
+ }
}
}
diff --git a/src/systems/ExpansionSystem.js b/src/systems/ExpansionSystem.js
index 28c05c2..5acb6bf 100644
--- a/src/systems/ExpansionSystem.js
+++ b/src/systems/ExpansionSystem.js
@@ -1,27 +1,163 @@
+/**
+ * EXPANSION SYSTEM
+ * - Manages Unlockable Zones (Farm, Forest, City...)
+ * - Fog of War logic (Visual blocking)
+ * - Unlock costs and requirements
+ */
class ExpansionSystem {
constructor(scene) {
this.scene = scene;
- this.unlockedZones = ['FARM_START']; // List of IDs
- this.islandsDiscovered = [];
- console.log('🌍 ExpansionSystem: Initialized');
+
+ // Define Zones
+ this.zones = {
+ 'farm': {
+ id: 'farm',
+ name: 'Starter Farm',
+ x: 0, y: 0, w: 40, h: 40, // 0-40 coordinates
+ unlocked: true,
+ cost: 0,
+ color: 0x00FF00
+ },
+ 'forest': {
+ id: 'forest',
+ name: 'Dark Forest',
+ x: 40, y: 0, w: 60, h: 40, // Right of farm
+ unlocked: false,
+ cost: 100, // 100 Gold Coins
+ req: 'None',
+ color: 0x006400
+ },
+ 'city': {
+ id: 'city',
+ name: 'Ruined City',
+ x: 0, y: 40, w: 100, h: 60, // Below farm & forest
+ unlocked: false,
+ cost: 500,
+ req: 'Kill Boss',
+ color: 0x808080
+ }
+ };
+
+ // Fog Graphics
+ this.fogGraphics = this.scene.add.graphics();
+ this.fogGraphics.setDepth(9999); // Very high depth
+ this.fogGraphics.setScrollFactor(1); // Moves with camera? No, world coordinates.
+ // wait, graphics draw in world coords? Yes if not scrollFactor 0
+ // We want it to cover the world terrain.
+
+ this.drawFog();
}
- // Preveri, če je igralec v dovoljeni coni
- checkAccess(x, y) {
- // TODO: Map coordinates to Zone ID
+ /**
+ * Unlock a zone
+ */
+ unlockZone(zoneId) {
+ const zone = this.zones[zoneId];
+ if (!zone) return false;
+ if (zone.unlocked) return false; // Already unlocked
+
+ // Check Logic (Payment) would be here or called before.
+ // For now, just unlock logic.
+
+ zone.unlocked = true;
+ console.log(`🗺️ Zone Unlocked: ${zone.name}`);
+
+ // Visual feedback
+ this.scene.events.emit('show-floating-text', {
+ x: this.scene.player.sprite.x,
+ y: this.scene.player.sprite.y - 100,
+ text: `UNLOCKED: ${zone.name}`,
+ color: '#FFFFFF'
+ });
+
+ // Sound
+ if (this.scene.soundManager) this.scene.soundManager.playSuccess(); // Assuming sound exists
+
+ this.drawFog(); // Redraw fog
return true;
}
- unlockZone(zoneId) {
- if (!this.unlockedZones.includes(zoneId)) {
- this.unlockedZones.push(zoneId);
- console.log('🔓 Zone Unlocked:', zoneId);
- // TODO: Remove fog/barrier
+ /**
+ * Check if player is in an unlocked zone
+ * Used for restricting movement or camera
+ */
+ isPointUnlocked(x, y) {
+ for (const key in this.zones) {
+ const z = this.zones[key];
+ if (x >= z.x && x < z.x + z.w && y >= z.y && y < z.y + z.h) {
+ return z.unlocked;
+ }
+ }
+ return false; // Default blocked
+ }
+
+ /**
+ * Draw Fog of War over locked zones
+ */
+ drawFog() {
+ this.fogGraphics.clear();
+
+ // Fill semi-transparent black over locked zones
+ this.fogGraphics.fillStyle(0x000000, 0.7); // 70% opacity black fog
+
+ for (const key in this.zones) {
+ const z = this.zones[key];
+ if (!z.unlocked) {
+ // Convert grid coords to world coords logic
+ // Isometric conversion might be tricky for rectangular fog on isometric grid.
+ // But for simplicity let's assume raw screen overlay or draw huge loose shapes?
+ // Actually, our map coordinates (gridX, gridY) map to iso.
+
+ // Let's try drawing tile-by-tile or chunks? Too slow.
+ // Better approach: Draw a huge rectangle? No, iso is diamond shape.
+
+ // Hack: Just draw simple polygons for now, or use the tile tinting system?
+ // Real Fog of War in Iso is complex.
+ // Let's stick to "Tinting" locked tiles or something simpler first.
+ // Or: Render a shape that covers the locked area logic.
+
+ // Since our world is 100x100 grid.
+ // Zone Forest: x:40, y:0, w:60, h:40.
+
+ // Let's just draw 'clouds' or text over locked areas?
+ // Or simplified: Just don't let player walk there.
+
+ // Let's skip heavy graphics fog for now and just add Floating Text Labels
+ // "LOCKED AREA: FOREST" over the center.
+
+ // Drawing 4 points of the zone in iso:
+ const p1 = this.scene.iso.toScreen(z.x, z.y);
+ const p2 = this.scene.iso.toScreen(z.x + z.w, z.y);
+ const p3 = this.scene.iso.toScreen(z.x + z.w, z.y + z.h);
+ const p4 = this.scene.iso.toScreen(z.x, z.y + z.h);
+
+ const offX = this.scene.terrainOffsetX;
+ const offY = this.scene.terrainOffsetY;
+
+ const poly = new Phaser.Geom.Polygon([
+ p1.x + offX, p1.y + offY,
+ p2.x + offX, p2.y + offY,
+ p3.x + offX, p3.y + offY,
+ p4.x + offX, p4.y + offY
+ ]);
+
+ this.fogGraphics.fillPoints(poly.points, true);
+
+ // Add label? (We'd need to manage text objects separately, simple redraw is easier)
+ }
}
}
- travelToIsland(islandId) {
- console.log('🚤 Traveling to:', islandId);
- // TODO: Load island map / scene
+ update(delta) {
+ // Maybe check if player tries to enter locked zone and push back?
+ if (this.scene.player) {
+ const p = this.scene.player;
+ if (!this.isPointUnlocked(p.gridX, p.gridY)) {
+ // Player is in locked zone! Push back!
+ // Simple bounce back logic
+ // Or just show warning
+ // console.log("Player in locked zone!");
+ }
+ }
}
}
diff --git a/src/systems/GraveSystem.js b/src/systems/GraveSystem.js
new file mode 100644
index 0000000..3952dab
--- /dev/null
+++ b/src/systems/GraveSystem.js
@@ -0,0 +1,205 @@
+/**
+ * GRAVE SYSTEM
+ * Zombi workers lahko počivajo v grobovih za regeneracijo
+ */
+class GraveSystem {
+ constructor(scene) {
+ this.scene = scene;
+ this.graves = []; // Seznam vseh postavljenih grobov
+ }
+
+ /**
+ * Place grave on terrain
+ */
+ placeGrave(gridX, gridY) {
+ const terrain = this.scene.terrainSystem;
+ if (!terrain) return false;
+
+ const tile = terrain.getTile(gridX, gridY);
+ if (!tile) return false;
+
+ // Check if already has grave
+ const key = `${gridX},${gridY}`;
+ if (this.graves.find(g => g.key === key)) {
+ console.log('⚠️ Grave already exists here!');
+ return false;
+ }
+
+ // Create grave object
+ const grave = {
+ gridX,
+ gridY,
+ key,
+ occupiedBy: null, // Zombie currently resting
+ sprite: null
+ };
+
+ // Create visual sprite
+ const screenPos = this.scene.iso.toScreen(gridX, gridY);
+ grave.sprite = this.scene.add.sprite(
+ screenPos.x + this.scene.terrainOffsetX,
+ screenPos.y + this.scene.terrainOffsetY,
+ 'gravestone'
+ );
+ grave.sprite.setOrigin(0.5, 1);
+ grave.sprite.setScale(0.3);
+ grave.sprite.setDepth(this.scene.iso.getDepth(gridX, gridY, this.scene.iso.LAYER_OBJECTS));
+
+ this.graves.push(grave);
+ console.log(`🪦 Grave placed at ${gridX},${gridY}`);
+ return true;
+ }
+
+ /**
+ * Remove grave
+ */
+ removeGrave(gridX, gridY) {
+ const key = `${gridX},${gridY}`;
+ const index = this.graves.findIndex(g => g.key === key);
+
+ if (index === -1) return false;
+
+ const grave = this.graves[index];
+
+ // Kick out occupant
+ if (grave.occupiedBy) {
+ grave.occupiedBy.workerData.isResting = false;
+ grave.occupiedBy = null;
+ }
+
+ if (grave.sprite) grave.sprite.destroy();
+ this.graves.splice(index, 1);
+
+ console.log(`🪦 Grave removed at ${gridX},${gridY}`);
+ return true;
+ }
+
+ /**
+ * Assign zombie to rest in grave
+ */
+ assignZombieToGrave(zombie) {
+ if (!zombie.workerData) return false;
+
+ // Find empty grave
+ const emptyGrave = this.graves.find(g => g.occupiedBy === null);
+
+ if (!emptyGrave) {
+ console.log('🚫 No empty graves available!');
+ return false;
+ }
+
+ // Mark zombie as resting
+ zombie.workerData.isResting = true;
+ zombie.workerData.restingGrave = emptyGrave;
+ emptyGrave.occupiedBy = zombie;
+
+ // Move zombie to grave
+ zombie.gridX = emptyGrave.gridX;
+ zombie.gridY = emptyGrave.gridY;
+ zombie.updatePosition();
+
+ // Visual: Make zombie semi-transparent
+ if (zombie.sprite) {
+ zombie.sprite.setAlpha(0.5);
+ }
+
+ console.log(`🪦 Zombie resting in grave at ${emptyGrave.gridX},${emptyGrave.gridY}`);
+ return true;
+ }
+
+ /**
+ * Wake zombie from grave
+ */
+ wakeZombie(zombie) {
+ if (!zombie.workerData || !zombie.workerData.isResting) return false;
+
+ const grave = zombie.workerData.restingGrave;
+ if (grave) {
+ grave.occupiedBy = null;
+ }
+
+ zombie.workerData.isResting = false;
+ zombie.workerData.restingGrave = null;
+
+ // Restore visual
+ if (zombie.sprite) {
+ zombie.sprite.setAlpha(1.0);
+ }
+
+ console.log(`🪦 Zombie woke up from grave`);
+ return true;
+ }
+
+ /**
+ * Update - regenerate resting zombies
+ */
+ update(delta) {
+ for (const grave of this.graves) {
+ if (grave.occupiedBy && grave.occupiedBy.workerData) {
+ const zombie = grave.occupiedBy;
+ const wd = zombie.workerData;
+
+ // Regenerate energy (slower than normal decay)
+ wd.energy += (0.2 * delta) / 1000; // +0.2 energy/sec
+ wd.energy = Math.min(100, wd.energy);
+
+ // Regenerate HP if energy > 50
+ if (wd.energy > 50) {
+ zombie.hp += (0.1 * delta) / 1000; // +0.1 HP/sec
+ zombie.hp = Math.min(zombie.maxHp, zombie.hp);
+ }
+
+ // Visual feedback - green tint when regenerating
+ if (zombie.sprite) {
+ zombie.sprite.setTint(0x00FF00);
+ }
+ }
+ }
+ }
+
+ /**
+ * Find nearest empty grave
+ */
+ findNearestEmptyGrave(x, y) {
+ let nearest = null;
+ let minDist = 999;
+
+ for (const grave of this.graves) {
+ if (grave.occupiedBy === null) {
+ const dist = Phaser.Math.Distance.Between(x, y, grave.gridX, grave.gridY);
+ if (dist < minDist) {
+ minDist = dist;
+ nearest = grave;
+ }
+ }
+ }
+
+ return nearest;
+ }
+
+ /**
+ * Auto-rest: Send low-energy zombies to graves
+ */
+ autoRest() {
+ if (!this.scene.zombieWorkerSystem) return;
+
+ for (const worker of this.scene.zombieWorkerSystem.workers) {
+ if (!worker.workerData) continue;
+
+ // If energy < 20 and not resting, send to grave
+ if (worker.workerData.energy < 20 && !worker.workerData.isResting) {
+ const grave = this.findNearestEmptyGrave(worker.gridX, worker.gridY);
+ if (grave) {
+ this.assignZombieToGrave(worker);
+ console.log(`🪦 Auto-rest: Zombie sent to grave (Low energy)`);
+ }
+ }
+
+ // If energy > 80 and resting, wake up
+ if (worker.workerData.energy > 80 && worker.workerData.isResting) {
+ this.wakeZombie(worker);
+ console.log(`🪦 Auto-wake: Zombie restored and ready to work`);
+ }
+ }
+ }
+}
diff --git a/src/systems/InteractionSystem.js b/src/systems/InteractionSystem.js
index fc142a0..8fdba7d 100644
--- a/src/systems/InteractionSystem.js
+++ b/src/systems/InteractionSystem.js
@@ -226,6 +226,13 @@ class InteractionSystem {
if (this.scene.terrainSystem.decorationsMap.has(id)) {
const decor = this.scene.terrainSystem.decorationsMap.get(id);
+ // Workstation Interaction
+ if (this.scene.workstationSystem && (decor.type.includes('furnace') || decor.type.includes('mint'))) {
+ const heldItem = activeTool ? { itemId: activeTool } : null;
+ const result = this.scene.workstationSystem.interact(gridX, gridY, heldItem);
+ if (result) return;
+ }
+
// handleTreeHit Logic (User Request)
// Preverimo tip in ustrezno orodje
let damage = 1;
diff --git a/src/systems/ScooterRepairSystem.js b/src/systems/ScooterRepairSystem.js
new file mode 100644
index 0000000..1e3d71d
--- /dev/null
+++ b/src/systems/ScooterRepairSystem.js
@@ -0,0 +1,160 @@
+/**
+ * SCOOTER REPAIR SYSTEM
+ * Broken scooter needs parts + tools to repair before being driveable
+ */
+class ScooterRepairSystem {
+ constructor(scene) {
+ this.scene = scene;
+
+ // Required parts for repair
+ this.requiredParts = {
+ scooter_engine: 1,
+ scooter_wheel: 2,
+ scooter_fuel_tank: 1,
+ scooter_handlebars: 1
+ };
+
+ // Required tools
+ this.requiredTools = {
+ wrench: 1,
+ screwdriver: 1
+ };
+
+ this.isRepaired = false;
+ }
+
+ /**
+ * Check if player has all required parts
+ */
+ hasAllParts() {
+ const inv = this.scene.inventorySystem;
+ if (!inv) return false;
+
+ for (const [part, count] of Object.entries(this.requiredParts)) {
+ if (!inv.hasItem(part) || inv.getItemCount(part) < count) {
+ console.log(`🚫 Missing: ${part} (Need ${count})`);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if player has required tools
+ */
+ hasAllTools() {
+ const inv = this.scene.inventorySystem;
+ if (!inv) return false;
+
+ for (const [tool, count] of Object.entries(this.requiredTools)) {
+ if (!inv.hasItem(tool) || inv.getItemCount(tool) < count) {
+ console.log(`🚫 Missing tool: ${tool} (Need ${count})`);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Attempt to repair scooter
+ */
+ repairScooter() {
+ if (this.isRepaired) {
+ console.log('✅ Scooter already repaired!');
+ return false;
+ }
+
+ if (!this.hasAllParts()) {
+ console.log('🚫 Missing required parts!');
+ this.listMissingParts();
+ return false;
+ }
+
+ if (!this.hasAllTools()) {
+ console.log('🚫 Missing required tools!');
+ return false;
+ }
+
+ // Consume parts (tools are NOT consumed)
+ const inv = this.scene.inventorySystem;
+ for (const [part, count] of Object.entries(this.requiredParts)) {
+ inv.removeItem(part, count);
+ }
+
+ this.isRepaired = true;
+ console.log('✅🛵 SCOOTER REPAIRED! Press V to drive!');
+
+ // Find scooter entity and enable it
+ if (this.scene.vehicles) {
+ for (const vehicle of this.scene.vehicles) {
+ if (vehicle.type === 'scooter') {
+ vehicle.isEnabled = true;
+ vehicle.updateVisual(); // Make it look new
+ }
+ }
+ }
+
+ // Visual feedback
+ this.scene.events.emit('show-floating-text', {
+ x: this.scene.player.sprite.x,
+ y: this.scene.player.sprite.y - 100,
+ text: '🛵 REPAIRED!',
+ color: '#00FF00'
+ });
+
+ return true;
+ }
+
+ /**
+ * List missing parts for repair
+ */
+ listMissingParts() {
+ const inv = this.scene.inventorySystem;
+ if (!inv) return;
+
+ console.log('📋 SCOOTER REPAIR CHECKLIST:');
+ console.log('===========================');
+
+ console.log('PARTS:');
+ for (const [part, needed] of Object.entries(this.requiredParts)) {
+ const has = inv.getItemCount(part) || 0;
+ const status = has >= needed ? '✅' : '🚫';
+ console.log(`${status} ${part}: ${has}/${needed}`);
+ }
+
+ console.log('\nTOOLS:');
+ for (const [tool, needed] of Object.entries(this.requiredTools)) {
+ const has = inv.getItemCount(tool) || 0;
+ const status = has >= needed ? '✅' : '🚫';
+ console.log(`${status} ${tool}: ${has}/${needed}`);
+ }
+ }
+
+ /**
+ * Spawn scooter part as loot
+ */
+ spawnPart(x, y, partType) {
+ if (!this.scene.interactionSystem) return;
+
+ const validParts = Object.keys(this.requiredParts);
+ const part = partType || validParts[Math.floor(Math.random() * validParts.length)];
+
+ this.scene.interactionSystem.spawnLoot(x, y, part, 1);
+ console.log(`🔧 Spawned scooter part: ${part} at ${x},${y}`);
+ }
+
+ /**
+ * Random loot drop chance for parts (from zombies, chests, etc.)
+ */
+ tryDropPart(x, y, dropChance = 0.1) {
+ if (this.isRepaired) return; // Don't drop if already repaired
+
+ if (Math.random() < dropChance) {
+ this.spawnPart(x, y);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/systems/TerrainSystem.js b/src/systems/TerrainSystem.js
index d34123e..ba30dfe 100644
--- a/src/systems/TerrainSystem.js
+++ b/src/systems/TerrainSystem.js
@@ -395,6 +395,12 @@ class TerrainSystem {
if (decor.hp <= 0) {
this.removeDecoration(x, y);
+
+ // Chance to drop Blueprint
+ if (this.scene.blueprintSystem) {
+ this.scene.blueprintSystem.tryDropBlueprint(x, y, 'mining');
+ }
+
return 'destroyed';
}
return 'hit';
@@ -617,7 +623,9 @@ class TerrainSystem {
typeLower.includes('house') ||
typeLower.includes('gravestone') ||
typeLower.includes('bush') ||
- typeLower.includes('fallen_log')
+ typeLower.includes('fallen_log') ||
+ typeLower.includes('furnace') || // WORKSTATION
+ typeLower.includes('mint') // WORKSTATION
);
const decorData = {
@@ -637,6 +645,16 @@ class TerrainSystem {
if (this.tiles[gridY] && this.tiles[gridY][gridX]) {
this.tiles[gridY][gridX].hasDecoration = true;
}
+
+ // Register Workstation
+ if (typeLower.includes('furnace') || typeLower.includes('mint')) {
+ if (this.scene.workstationSystem) {
+ // Determine type exactly
+ let stationType = 'furnace';
+ if (typeLower.includes('mint')) stationType = 'mint';
+ this.scene.workstationSystem.addStation(gridX, gridY, stationType);
+ }
+ }
}
setTileType(x, y, typeName) {
diff --git a/src/systems/WorkstationSystem.js b/src/systems/WorkstationSystem.js
new file mode 100644
index 0000000..85b1d8d
--- /dev/null
+++ b/src/systems/WorkstationSystem.js
@@ -0,0 +1,177 @@
+/**
+ * WORKSTATION SYSTEM
+ * Manages processing stations like Furnace, Mint, Campfire.
+ * Handles: Input -> Process Time -> Output
+ */
+class WorkstationSystem {
+ constructor(scene) {
+ this.scene = scene;
+ this.stations = new Map(); // Key: "x,y", Value: StationData
+
+ // Define Recipes
+ this.recipes = {
+ 'furnace': [
+ { input: 'ore_iron', fuel: 'coal', output: 'iron_bar', time: 5000 },
+ { input: 'ore_stone', fuel: 'coal', output: 'stone_brick', time: 3000 },
+ { input: 'sand', fuel: 'coal', output: 'glass', time: 3000 },
+ { input: 'log', fuel: 'log', output: 'charcoal', time: 4000 } // Wood to charcoal
+ ],
+ 'mint': [
+ { input: 'iron_bar', fuel: 'coal', output: 'coin_gold', time: 2000, outputCount: 10 }
+ ],
+ 'campfire': [
+ { input: 'raw_meat', fuel: 'stick', output: 'cooked_meat', time: 3000 }
+ ]
+ };
+ }
+
+ /**
+ * Register a new workstation at coordinates
+ */
+ addStation(x, y, type) {
+ const key = `${x},${y}`;
+ if (this.stations.has(key)) return;
+
+ this.stations.set(key, {
+ x, y, type,
+ input: null, // { itemId, count }
+ fuel: null, // { itemId, count }
+ output: null, // { itemId, count }
+ isProcessing: false,
+ timer: 0,
+ maxTime: 0,
+ currentRecipe: null
+ });
+ console.log(`🏭 Created ${type} at ${x},${y}`);
+ }
+
+ /**
+ * Player interacts with station
+ * (Simple version: Click with item to insert, Click empty to take output)
+ */
+ interact(x, y, playerItem) {
+ const key = `${x},${y}`;
+ const station = this.stations.get(key);
+ if (!station) return false;
+
+ // 1. If Output exists, take it (Prioritize taking output)
+ if (station.output) {
+ this.scene.inventorySystem.addItem(station.output.itemId, station.output.count);
+ this.showFloatingText(x, y, `+${station.output.count} ${station.output.itemId}`, '#00FF00');
+ station.output = null;
+ return true;
+ }
+
+ // 2. If holding item, try to insert (Input or Fuel)
+ if (playerItem) {
+ // Check if it's Valid Input for this station type
+ const recipe = this.findRecipe(station.type, playerItem.itemId);
+
+ // Is it Fuel? (Assume 'coal', 'log', 'stick' are fuels)
+ const isFuel = ['coal', 'log', 'stick', 'charcoal'].includes(playerItem.itemId);
+
+ if (recipe && !station.input) {
+ // Insert Input
+ station.input = { itemId: playerItem.itemId, count: 1 };
+ this.scene.inventorySystem.removeItem(playerItem.itemId, 1);
+ this.showFloatingText(x, y, `Inserted ${playerItem.itemId}`, '#FFFFFF');
+ this.tryStartProcess(station);
+ return true;
+ } else if (isFuel && !station.fuel) {
+ // Insert Fuel
+ station.fuel = { itemId: playerItem.itemId, count: 1 };
+ this.scene.inventorySystem.removeItem(playerItem.itemId, 1);
+ this.showFloatingText(x, y, `Fueled with ${playerItem.itemId}`, '#FFA500');
+ this.tryStartProcess(station);
+ return true;
+ }
+ }
+
+ // 3. Status Check
+ if (station.isProcessing) {
+ const progress = Math.floor((station.timer / station.maxTime) * 100);
+ this.showFloatingText(x, y, `Processing... ${progress}%`, '#FFFF00');
+ } else {
+ this.showFloatingText(x, y, 'Missing Input or Fuel', '#FF0000');
+ }
+
+ return true;
+ }
+
+ findRecipe(stationType, inputItem) {
+ const stationRecipes = this.recipes[stationType];
+ if (!stationRecipes) return null;
+ return stationRecipes.find(r => r.input === inputItem);
+ }
+
+ tryStartProcess(station) {
+ if (station.isProcessing) return;
+ if (!station.input || !station.fuel) return;
+
+ // Find matching recipe
+ const recipe = this.recipes[station.type].find(r =>
+ r.input === station.input.itemId &&
+ (r.fuel === 'any' || r.fuel === station.fuel.itemId)
+ );
+
+ // Specific fuel check can be relaxed later
+ if (recipe) {
+ station.isProcessing = true;
+ station.timer = 0;
+ station.maxTime = recipe.time;
+ station.currentRecipe = recipe;
+ console.log(`🔥 Started processing ${recipe.input} -> ${recipe.output}`);
+
+ // Visual feedback (Particles?)
+ // this.scene.addParticles...
+ }
+ }
+
+ update(delta) {
+ for (const station of this.stations.values()) {
+ if (station.isProcessing) {
+ station.timer += delta;
+
+ // Visual: Float smoke occasionally?
+ if (Math.random() < 0.05) {
+ // emit smoke particle
+ }
+
+ if (station.timer >= station.maxTime) {
+ // COMPLETE
+ const recipe = station.currentRecipe;
+ const outCount = recipe.outputCount || 1;
+
+ // Consume inputs
+ // (Actually we keep them in slots until done? Or consume at start?
+ // Let's say inputs are consumed at start visually, but logically here we swap to output)
+
+ station.output = { itemId: recipe.output, count: outCount };
+ station.input = null;
+ station.fuel = null; // Consume 1 fuel per operation for now (Simple)
+
+ station.isProcessing = false;
+ station.timer = 0;
+ station.currentRecipe = null;
+
+ this.showFloatingText(station.x, station.y, 'DONE!', '#00FF00');
+ if (this.scene.soundManager) this.scene.soundManager.playSuccess(); // Placeholder sound
+ }
+ }
+ }
+ }
+
+ showFloatingText(x, y, text, color) {
+ // Convert grid to screen
+ // Assuming interact passes Grid X,Y
+ if (this.scene.events) {
+ const screen = this.scene.iso.toScreen(x, y);
+ this.scene.events.emit('show-floating-text', {
+ x: screen.x + this.scene.terrainOffsetX,
+ y: screen.y + this.scene.terrainOffsetY - 30,
+ text: text,
+ color: color
+ });
+ }
+ }
+}
diff --git a/src/systems/ZombieWorkerSystem.js b/src/systems/ZombieWorkerSystem.js
index 0003c52..ee361a2 100644
--- a/src/systems/ZombieWorkerSystem.js
+++ b/src/systems/ZombieWorkerSystem.js
@@ -1,50 +1,202 @@
+/**
+ * ZOMBIE WORKER AI SYSTEM
+ * Tamed zombies lahko opravljajo delo (farming, mining)
+ */
class ZombieWorkerSystem {
constructor(scene) {
this.scene = scene;
- this.workers = []; // Array of tamed zombies (NPC objects)
-
- console.log('🧟 ZombieWorkerSystem: Initialized');
+ this.workers = [];
}
- registerWorker(npc) {
- if (!this.workers.includes(npc)) {
- this.workers.push(npc);
- npc.workerStats = {
- energy: 100, // Energija (pada ob delu)
- decay: 0, // Razpadanje (0-100%, 100% = smrt)
- xp: 0, // Izkušnje zombija
- level: 1,
- task: 'IDLE' // IDLE, FARM, MINE, GUARD, FOLLOW
- };
- console.log(`🧟 Zombie ${this.workers.length} registered as Worker!`);
+ assignWork(zombie, workType, workRadius = 5) {
+ if (!zombie.isTamed) {
+ console.warn('⚠️ Cannot assign work to untamed zombie!');
+ return false;
+ }
- // UI Feedback
- this.scene.events.emit('show-floating-text', {
- x: npc.sprite.x,
- y: npc.sprite.y - 50,
- text: "New Worker!",
- color: "#00FF00"
- });
+ zombie.workerData = {
+ type: workType,
+ radius: workRadius,
+ centerX: zombie.gridX,
+ centerY: zombie.gridY,
+ energy: 100,
+ decayRate: 0.5,
+ workTimer: 0,
+ workInterval: 5000,
+ status: 'IDLE',
+ inventory: {
+ seeds: 0,
+ hoe: 0,
+ watering_can: 0,
+ pickaxe: 0
+ }
+ };
+
+ this.workers.push(zombie);
+ console.log(`🧟 Assigned ${zombie.type} as ${workType} worker`);
+ return true;
+ }
+
+ removeWorker(zombie) {
+ const index = this.workers.indexOf(zombie);
+ if (index > -1) {
+ this.workers.splice(index, 1);
+ zombie.workerData = null;
}
}
- unregisterWorker(npc) {
- const idx = this.workers.indexOf(npc);
- if (idx > -1) {
- this.workers.splice(idx, 1);
- console.log('🧟 Zombie Worker removed.');
+ update(delta) {
+ this.workers = this.workers.filter(w =>
+ this.scene.npcs.includes(w) && w.hp > 0
+ );
+
+ for (const worker of this.workers) {
+ if (!worker.workerData) continue;
+
+ this.applyDecay(worker, delta);
+ worker.workerData.workTimer += delta;
+
+ if (worker.workerData.workTimer >= worker.workerData.workInterval) {
+ worker.workerData.workTimer = 0;
+
+ if (worker.workerData.type === 'FARM') {
+ this.performFarmWork(worker);
+ } else if (worker.workerData.type === 'MINE') {
+ this.performMineWork(worker);
+ }
+ }
}
}
- update(time, delta) {
- // Update logic for all workers (e.g. decay, energy regen if sleeping)
- // This is called every frame, so keep it light.
+ applyDecay(zombie, delta) {
+ const wd = zombie.workerData;
+ wd.energy -= (wd.decayRate * delta) / 1000;
- // Example: Decay tick every 10 seconds (handled by timer, or simplified here)
+ if (wd.energy <= 0) {
+ wd.energy = 0;
+ zombie.hp -= (wd.decayRate * delta) / 1000;
+ if (zombie.sprite) zombie.sprite.setTint(0x666666);
+ }
+
+ if (zombie.hp <= 0) this.onWorkerDeath(zombie);
}
- // Assign a task to all idle workers or specific one
- assignTask(taskName, targetPos) {
- // TODO: Poišči prostega delavca in mu daj nalogo
+ performFarmWork(zombie) {
+ const wd = zombie.workerData;
+ const terrain = this.scene.terrainSystem;
+ const farming = this.scene.farmingSystem;
+ if (!terrain || !farming) return;
+
+ // 1. Water/Harvest existing crops
+ if (terrain.cropsMap) {
+ 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.cropsMap.has(key)) {
+ const crop = terrain.cropsMap.get(key);
+
+ if (!crop.isWatered) {
+ farming.waterCrop(wd.centerX + dx, wd.centerY + dy);
+ console.log(`🧟💧 Worker watered`);
+ wd.status = 'WORKING';
+ return;
+ }
+
+ if (crop.stage >= 4) {
+ farming.harvest(wd.centerX + dx, wd.centerY + dy);
+ console.log(`🧟🌾 Worker harvested`);
+ wd.status = 'WORKING';
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // 2. Plant on empty tilled soil (if has seeds)
+ if (wd.inventory.seeds > 0) {
+ for (let dx = -wd.radius; dx <= wd.radius; dx++) {
+ for (let dy = -wd.radius; dy <= wd.radius; dy++) {
+ const tile = terrain.getTile(wd.centerX + dx, wd.centerY + dy);
+ if (tile && tile.isTilled && !tile.hasCrop) {
+ farming.plant(wd.centerX + dx, wd.centerY + dy, 'wheat');
+ wd.inventory.seeds--;
+ console.log(`🧟🌱 Worker planted (Seeds: ${wd.inventory.seeds})`);
+ wd.status = 'WORKING';
+ return;
+ }
+ }
+ }
+ }
+
+ // 3. Till grass/dirt
+ for (let dx = -wd.radius; dx <= wd.radius; dx++) {
+ for (let dy = -wd.radius; dy <= wd.radius; dy++) {
+ const tile = terrain.getTile(wd.centerX + dx, wd.centerY + dy);
+ const typeName = tile?.type?.name || tile?.type;
+
+ if ((typeName === 'grass' || typeName === 'dirt') && !tile.isTilled) {
+ tile.isTilled = true;
+ if (tile.sprite) tile.sprite.setTint(0x8B4513);
+ console.log(`🧟🔨 Worker tilled soil`);
+ wd.status = 'WORKING';
+ return;
+ }
+ }
+ }
+
+ wd.status = 'IDLE';
+ }
+
+ performMineWork(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);
+ if (decor.type === 'stone' || decor.type.includes('rock')) {
+ terrain.removeDecoration(wd.centerX + dx, wd.centerY + dy);
+
+ if (this.scene.inventorySystem) {
+ this.scene.inventorySystem.addItem('stone', 1);
+ }
+
+ console.log(`🧟⛏️ Worker mined stone`);
+ wd.status = 'WORKING';
+ return;
+ }
+ }
+ }
+ }
+
+ wd.status = 'IDLE';
+ }
+
+ onWorkerDeath(zombie) {
+ console.log(`💀 Worker died at ${zombie.gridX},${zombie.gridY}`);
+
+ if (this.scene.interactionSystem && this.scene.interactionSystem.spawnLoot) {
+ this.scene.interactionSystem.spawnLoot(zombie.gridX, zombie.gridY, 'fertilizer', 1);
+ }
+
+ if (this.scene.statsSystem) {
+ this.scene.statsSystem.addXP(10);
+ }
+
+ this.removeWorker(zombie);
+ }
+
+ feedWorker(zombie, amount = 50) {
+ if (!zombie.workerData) return false;
+
+ zombie.workerData.energy = Math.min(100, zombie.workerData.energy + amount);
+ if (zombie.sprite) zombie.sprite.clearTint();
+
+ console.log(`🍖 Fed worker`);
+ return true;
}
}
diff --git a/src/utils/TextureGenerator.js b/src/utils/TextureGenerator.js
index 99f6b3f..2d51a1e 100644
--- a/src/utils/TextureGenerator.js
+++ b/src/utils/TextureGenerator.js
@@ -320,12 +320,102 @@ class TextureGenerator {
static createGravestoneSprite(scene, key = 'gravestone') {
if (scene.textures.exists(key)) return;
- const canvas = scene.textures.createCanvas(key, 32, 32);
+ const canvas = scene.textures.createCanvas(key, 48, 64);
const ctx = canvas.getContext();
- ctx.clearRect(0, 0, 32, 32);
- ctx.fillStyle = '#808080';
- ctx.fillRect(8, 8, 16, 24);
- ctx.beginPath(); ctx.arc(16, 8, 8, Math.PI, 0); ctx.fill();
+
+ // Clear with transparency
+ ctx.clearRect(0, 0, 48, 64);
+
+ // Stone base (darker gray)
+ ctx.fillStyle = '#4A4A4A';
+ ctx.fillRect(14, 52, 20, 8); // Base platform
+
+ // Main gravestone body (gray stone)
+ ctx.fillStyle = '#707070';
+ ctx.fillRect(16, 24, 16, 28); // Tall rectangle
+
+ // Rounded top
+ ctx.beginPath();
+ ctx.arc(24, 24, 8, Math.PI, 0, false);
+ ctx.fill();
+
+ // Moss/weathering effect (dark green patches)
+ ctx.fillStyle = '#2D5016';
+ ctx.fillRect(17, 30, 3, 4);
+ ctx.fillRect(28, 35, 2, 3);
+ ctx.fillRect(19, 45, 4, 3);
+
+ // Cracks (dark lines)
+ ctx.fillStyle = '#3A3A3A';
+ ctx.fillRect(20, 28, 1, 10); // Vertical crack
+ ctx.fillRect(27, 32, 1, 8);
+
+ // Cross/RIP symbol (lighter gray)
+ ctx.fillStyle = '#909090';
+ // Vertical bar
+ ctx.fillRect(23, 32, 2, 8);
+ // Horizontal bar
+ ctx.fillRect(21, 34, 6, 2);
+
+ // Shadow (semi-transparent black)
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
+ ctx.ellipse(24, 60, 10, 3, 0, 0, Math.PI * 2);
+ ctx.fill();
+
+ canvas.refresh();
+ }
+
+ static createFenceSprite(scene, key = 'fence_full') {
+ if (scene.textures.exists(key)) return;
+ const canvas = scene.textures.createCanvas(key, 48, 48);
+ const ctx = canvas.getContext();
+
+ // Clear with transparency
+ ctx.clearRect(0, 0, 48, 48);
+
+ // Wood color (brown)
+ const woodDark = '#6B4423';
+ const woodLight = '#8B5A3C';
+
+ // Vertical posts (3 posts)
+ for (let i = 0; i < 3; i++) {
+ const x = 8 + i * 16;
+
+ // Main post
+ ctx.fillStyle = woodLight;
+ ctx.fillRect(x, 8, 6, 32);
+
+ // Dark side (shading)
+ ctx.fillStyle = woodDark;
+ ctx.fillRect(x + 5, 8, 1, 32);
+
+ // Top point (fence picket style)
+ ctx.fillStyle = woodLight;
+ ctx.beginPath();
+ ctx.moveTo(x, 8);
+ ctx.lineTo(x + 3, 4);
+ ctx.lineTo(x + 6, 8);
+ ctx.fill();
+ }
+
+ // Horizontal planks (2 planks)
+ ctx.fillStyle = woodLight;
+ ctx.fillRect(4, 16, 40, 4); // Top plank
+ ctx.fillRect(4, 28, 40, 4); // Bottom plank
+
+ // Plank shading
+ ctx.fillStyle = woodDark;
+ ctx.fillRect(4, 19, 40, 1); // Top plank shadow
+ ctx.fillRect(4, 31, 40, 1); // Bottom plank shadow
+
+ // Wood grain (subtle lines)
+ ctx.fillStyle = 'rgba(107, 68, 35, 0.3)';
+ for (let i = 0; i < 3; i++) {
+ const x = 8 + i * 16;
+ ctx.fillRect(x + 1, 10, 1, 25);
+ ctx.fillRect(x + 3, 12, 1, 20);
+ }
+
canvas.refresh();
}
@@ -646,6 +736,7 @@ class TextureGenerator {
TextureGenerator.createCornSprites(this.scene); // Added Corn
TextureGenerator.createGravestoneSprite(this.scene);
+ TextureGenerator.createFenceSprite(this.scene); // Procedural fence with transparency
TextureGenerator.createToolSprites(this.scene);
TextureGenerator.createItemSprites(this.scene);
TextureGenerator.createScooterSprite(this.scene, 'scooter', false);
@@ -661,6 +752,8 @@ class TextureGenerator {
TextureGenerator.createMushroomSprite(this.scene, 'mushroom_brown', '#8B4513', '#FFE4C4');
TextureGenerator.createFallenLogSprite(this.scene, 'fallen_log');
TextureGenerator.createPuddleSprite(this.scene, 'puddle');
+ TextureGenerator.createFurnaceSprite(this.scene, 'furnace');
+ TextureGenerator.createMintSprite(this.scene, 'mint');
}
static createPathStoneSprite(scene, key = 'path_stone') {
@@ -967,6 +1060,29 @@ class TextureGenerator {
}
}
+ static createFurnaceSprite(scene, key = 'furnace') {
+ if (scene.textures.exists(key)) return;
+ const graphics = scene.make.graphics({ x: 0, y: 0, add: false });
+ // Furnace block (gray)
+ graphics.fillStyle(0x555555, 1);
+ graphics.fillRect(8, 14, 16, 18);
+ graphics.fillStyle(0x333333, 1);
+ graphics.fillRect(12, 22, 8, 6); // Hole
+ graphics.fillStyle(0xffaa00, 1);
+ graphics.fillRect(14, 24, 4, 3); // Embers
+ graphics.generateTexture(key, 32, 32);
+ }
+
+ static createMintSprite(scene, key = 'mint') {
+ if (scene.textures.exists(key)) return;
+ const graphics = scene.make.graphics({ x: 0, y: 0, add: false });
+ graphics.fillStyle(0x777799, 1); // Steel blue
+ graphics.fillRect(6, 12, 20, 20);
+ graphics.fillStyle(0xffd700, 1); // Gold coin icon
+ graphics.fillCircle(16, 18, 5);
+ graphics.generateTexture(key, 32, 32);
+ }
+
constructor(scene) {
this.scene = scene;
}