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; }