phase 11 part1

This commit is contained in:
2025-12-08 12:30:15 +01:00
parent 3336b59e7d
commit 07f0752d81
15 changed files with 1383 additions and 133 deletions

View File

@@ -169,3 +169,25 @@ Načrt za razvoj in zahteve.
--- ---
*Zadnja posodobitev koncepta: 8. December 2025 (Mega Update + Tech Specs)* *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.

View File

@@ -178,26 +178,27 @@ Ekskluzivni vizualni popravki za potopitveno izkušnjo.
## 🧟 Phase 11: Zombie Roots Integration (New Mechanics) ## 🧟 Phase 11: Zombie Roots Integration (New Mechanics)
Implementacija jedrnih mehanik iz novega koncepta "Krvava Žetev". Implementacija jedrnih mehanik iz novega koncepta "Krvava Žetev".
- [ ] **Zombie Worker AI** - [x] **Zombie Worker AI**
- [ ] `WORK_FARM`: Zombi avtomatsko zaliva/žanje v določenem radiusu. - [x] `WORK_FARM`: Zombi avtomatsko zaliva/žanje v določenem radiusu.
- [ ] `WORK_MINE`: Zombi koplje kamenje/rudo. - [x] `WORK_MINE`: Zombi koplje kamenje/rudo.
- [ ] **Decay System**: Zombi s časom izgublja energijo/HP. - [x] **Decay System**: Zombi s časom izgublja energijo/HP.
- [ ] **Death Drop**: Gnojilo (Fertilizer) + XP ob smrti. - [x] **Death Drop**: Gnojilo (Fertilizer) + XP ob smrti.
- [ ] **Grave System** - [x] **Grave System**
- [ ] Izdelava Groba (Crafting). - [x] Izdelava Groba (Crafting) (via command).
- [ ] Počitek: Zombi v grobu se regenerira (počasneje razpada). - [x] Počitek: Zombi v grobu se regenerira (počasneje razpada).
- [ ] **Expansion System (Micro Farm start)** - [x] **Expansion System (Micro Farm start)**
- [ ] Zaklepanje con (megla/neprehodno). - [x] Zaklepanje con (megla/neprehodno).
- [ ] Naloga: "Pošlji zombije očistit cono". - [ ] Naloga: "Pošlji zombije očistit cono".
- [ ] **Hybrid Skill & Language** - [ ] **Hybrid Skill & Language**
- [ ] Skill Tree UI za Hibrida. - [ ] Skill Tree UI za Hibrida.
- [ ] Prevajalnik dialogov (Level 1: "...hggh", Level 10: "Nevarnost!"). - [ ] Prevajalnik dialogov (Level 1: "...hggh", Level 10: "Nevarnost!").
- [ ] **Economy: Minting & Crafting** - [ ] **Economy: Minting & Crafting**
- [ ] **Blueprint System**: Drop chance pri kopanju (`unlockRecipe(id)`). - [x] **Blueprint System**: Drop chance pri kopanju (`unlockRecipe(id)`).
- [ ] **Workstation Logic**: - [ ] **Workstation Logic**:
- [ ] Workbench (Crafting UI v2.0). - [ ] Workbench (Crafting UI v2.0).
- [ ] Furnace (Input slot -> Fuel -> Output slot timer). - [x] Furnace (Input slot -> Fuel -> Output slot timer).
- [ ] Talilnica (Furnace) za rudo -> palice. - [x] Talilnica (Furnace) za rudo -> palice.
- [x] Kovnica (Mint) za palice -> kovanci.
- [ ] Kovnica (Mint) za palice -> zlatniki. - [ ] Kovnica (Mint) za palice -> zlatniki.
- [ ] **Building Expansion** - [ ] **Building Expansion**
- [ ] **Barn**: Objekt za shranjevanje živali. - [ ] **Barn**: Objekt za shranjevanje živali.

View File

@@ -99,6 +99,12 @@
<!-- Multiplayer --> <!-- Multiplayer -->
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script> <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<script src="src/systems/MultiplayerSystem.js"></script> <script src="src/systems/MultiplayerSystem.js"></script>
<script src="src/systems/ZombieWorkerSystem.js"></script> <!-- Zombie Worker AI -->
<script src="src/systems/GraveSystem.js"></script> <!-- Grave/Rest System -->
<script src="src/systems/ScooterRepairSystem.js"></script> <!-- Scooter Repair -->
<script src="src/systems/ExpansionSystem.js"></script> <!-- Zone Expansion -->
<script src="src/systems/BlueprintSystem.js"></script> <!-- Blueprints -->
<script src="src/systems/WorkstationSystem.js"></script> <!-- Furnaces & Machines -->
<!-- Entities --> <!-- Entities -->
<script src="src/entities/Player.js"></script> <script src="src/entities/Player.js"></script>

View File

@@ -630,10 +630,11 @@ class NPC {
this.state = 'TAMED'; this.state = 'TAMED';
console.log('🧟❤️ Zombie TAMED!'); console.log('🧟❤️ Zombie TAMED!');
this.isTamed = true; // Mark as tamed
// Register to ZombieWorkerSystem // Register to ZombieWorkerSystem (assign FARM work by default)
if (this.scene.zombieSystem) { if (this.scene.zombieWorkerSystem) {
this.scene.zombieSystem.registerWorker(this); this.scene.zombieWorkerSystem.assignWork(this, 'FARM', 5);
} }
// Visual Feedback // Visual Feedback

View File

@@ -44,35 +44,41 @@ class Scooter {
} }
tryFix(player) { tryFix(player) {
// Logic: Check if player has tools? // Use ScooterRepairSystem to check parts/tools
// User said: "ga more popraviti" (needs to fix it). if (!this.scene.scooterRepairSystem) {
// Let's just require a short delay or check for 'wrench' if we had one. console.log('🚫 Repair system not available!');
// For easter egg, let's say hitting it with a hammer works, or just interacting. return;
// Let's make it simple: "Fixing Scooter..." progress. }
console.log('🔧 Fixing Scooter...'); console.log('🔧 Attempting to repair Scooter...');
this.scene.events.emit('show-floating-text', {
x: this.sprite.x,
y: this.sprite.y - 50,
text: "Fixing...",
color: '#FFFF00'
});
// Plays sound const success = this.scene.scooterRepairSystem.repairScooter();
if (this.scene.soundManager) this.scene.soundManager.playHit(); // Clank sounds
// Delay 2 seconds then fix if (success) {
this.scene.time.delayedCall(2000, () => {
this.isBroken = false; this.isBroken = false;
this.createSprite(); // Update texture to shiny this.createSprite(); // Update to fixed texture
this.scene.events.emit('show-floating-text', { this.scene.events.emit('show-floating-text', {
x: this.sprite.x, x: this.sprite.x,
y: this.sprite.y - 50, y: this.sprite.y - 50,
text: "Scooter Fixed!", text: "🛵 REPAIRED!",
color: '#00FF00' 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) { toggleRide(player) {

View File

@@ -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) // ELITE ZOMBIE v City območju (samo 1 za testiranje)
console.log('👹 Spawning ELITE ZOMBIE in City...'); console.log('👹 Spawning ELITE ZOMBIE in City...');
const eliteX = Phaser.Math.Between(50, 80); // City area const eliteX = Phaser.Math.Between(50, 80); // City area
@@ -169,8 +193,8 @@ class GameScene extends Phaser.Scene {
// Easter Egg: Broken Scooter // Easter Egg: Broken Scooter
console.log('🛵 Spawning Scooter Easter Egg...'); console.log('🛵 Spawning Scooter Easter Egg...');
this.vehicles = []; this.vehicles = [];
const scooter = new Scooter(this, 25, 25); // const scooter = new Scooter(this, 25, 25);
this.vehicles.push(scooter); // this.vehicles.push(scooter);
// ZOMBIE SPAWNERS (City area) // ZOMBIE SPAWNERS (City area)
console.log('👹 Creating Zombie Spawners...'); console.log('👹 Creating Zombie Spawners...');
@@ -274,6 +298,140 @@ class GameScene extends Phaser.Scene {
console.log('✅ GameScene ready - FAZA 20 (Full Features)!'); 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 // Start Engine
this.Antigravity_Start(); 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) { if (this.parallaxSystem && this.player) {
const playerPos = this.player.getPosition(); const playerPos = this.player.getPosition();
const screenPos = this.iso.toScreen(playerPos.x, playerPos.y); const screenPos = this.iso.toScreen(playerPos.x, playerPos.y);
@@ -542,38 +719,41 @@ class GameScene extends Phaser.Scene {
if (this.terrainSystem.decorationsMap.has(key)) { if (this.terrainSystem.decorationsMap.has(key)) {
this.terrainSystem.removeDecoration(x, y); this.terrainSystem.removeDecoration(x, y);
} }
// Make it grass or dirt // Make it grass or dirt - USE CORRECT TERRAIN TYPE OBJECT
this.terrainSystem.tiles[y][x].type = { name: 'grass', color: 0x228B22 }; this.terrainSystem.tiles[y][x].type = this.terrainSystem.terrainTypes.GRASS_FULL;
this.terrainSystem.tiles[y][x].sprite.setTint(0x228B22); 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) // 2. Place starter resources (chest s semeni) - REMOVED PER USER REQUEST (Floating chest bug)
this.terrainSystem.placeStructure(farmX + 3, farmY + 3, 'chest'); // this.terrainSystem.placeStructure(farmX + 3, farmY + 3, 'chest');
// 3. Place FULL FENCE around farm // 3. Place FULL FENCE around farm
console.log('🚧 Building Farm Fence...'); // console.log('🚧 Building Farm Fence...');
const minX = farmX - farmRadius; // const minX = farmX - farmRadius;
const maxX = farmX + farmRadius; // const maxX = farmX + farmRadius;
const minY = farmY - farmRadius; // const minY = farmY - farmRadius;
const maxY = farmY + farmRadius; // const maxY = farmY + farmRadius;
// Top and bottom horizontal fences // // Top and bottom horizontal fences
for (let x = minX; x <= maxX; x++) { // for (let x = minX; x <= maxX; x++) {
if (x >= 0 && x < 100) { // if (x >= 0 && x < 100) {
this.terrainSystem.placeStructure(x, minY, 'fence_full'); // Top // this.terrainSystem.placeStructure(x, minY, 'fence_full'); // Top
this.terrainSystem.placeStructure(x, maxY, 'fence_full'); // Bottom // this.terrainSystem.placeStructure(x, maxY, 'fence_full'); // Bottom
} // }
} // }
// Left and right vertical fences // // Left and right vertical fences
for (let y = minY; y <= maxY; y++) { // for (let y = minY; y <= maxY; y++) {
if (y >= 0 && y < 100) { // if (y >= 0 && y < 100) {
this.terrainSystem.placeStructure(minX, y, 'fence_full'); // Left // this.terrainSystem.placeStructure(minX, y, 'fence_full'); // Left
this.terrainSystem.placeStructure(maxX, y, 'fence_full'); // Right // this.terrainSystem.placeStructure(maxX, y, 'fence_full'); // Right
} // }
} // }
console.log('✅ Farm Area Initialized at (20,20)'); console.log('✅ Farm Area Initialized at (20,20)');
} }

View File

@@ -1,32 +1,95 @@
/**
* BLUEPRINT SYSTEM
* Manages unlocking crafting recipes via Blueprint items.
*/
class BlueprintSystem { class BlueprintSystem {
constructor(scene) { constructor(scene) {
this.scene = scene; this.scene = scene;
this.knownRecipes = ['axe', 'pickaxe', 'hoe']; // Default known this.unlockedRecipes = new Set(); // Stores IDs of unlocked recipes
this.blueprintsFound = []; // Items found but not learned? Or just list
console.log('📜 BlueprintSystem: Initialized'); // 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() { * Unlock a recipe
if (Math.random() < 0.05) { // 5% chance */
const newBp = 'blueprint_barn'; // Randomize this unlockRecipe(recipeId) {
console.log('✨ BLUEPRINT FOUND:', newBp); if (this.unlockedRecipes.has(recipeId)) {
return newBp; 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)) { * Check if unlocked
this.knownRecipes.push(id); */
console.log('🧠 Learned Recipe:', id); isUnlocked(recipeId) {
// TODO: Add to Crafting Menu 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 true;
} }
return false; 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}`);
}
}
} }
} }

View File

@@ -1,27 +1,163 @@
/**
* EXPANSION SYSTEM
* - Manages Unlockable Zones (Farm, Forest, City...)
* - Fog of War logic (Visual blocking)
* - Unlock costs and requirements
*/
class ExpansionSystem { class ExpansionSystem {
constructor(scene) { constructor(scene) {
this.scene = scene; this.scene = scene;
this.unlockedZones = ['FARM_START']; // List of IDs
this.islandsDiscovered = []; // Define Zones
console.log('🌍 ExpansionSystem: Initialized'); 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) { * Unlock a zone
// TODO: Map coordinates to Zone ID */
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; return true;
} }
unlockZone(zoneId) { /**
if (!this.unlockedZones.includes(zoneId)) { * Check if player is in an unlocked zone
this.unlockedZones.push(zoneId); * Used for restricting movement or camera
console.log('🔓 Zone Unlocked:', zoneId); */
// TODO: Remove fog/barrier 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) { update(delta) {
console.log('🚤 Traveling to:', islandId); // Maybe check if player tries to enter locked zone and push back?
// TODO: Load island map / scene 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!");
}
}
} }
} }

205
src/systems/GraveSystem.js Normal file
View File

@@ -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`);
}
}
}
}

View File

@@ -226,6 +226,13 @@ class InteractionSystem {
if (this.scene.terrainSystem.decorationsMap.has(id)) { if (this.scene.terrainSystem.decorationsMap.has(id)) {
const decor = this.scene.terrainSystem.decorationsMap.get(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) // handleTreeHit Logic (User Request)
// Preverimo tip in ustrezno orodje // Preverimo tip in ustrezno orodje
let damage = 1; let damage = 1;

View File

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

View File

@@ -395,6 +395,12 @@ class TerrainSystem {
if (decor.hp <= 0) { if (decor.hp <= 0) {
this.removeDecoration(x, y); this.removeDecoration(x, y);
// Chance to drop Blueprint
if (this.scene.blueprintSystem) {
this.scene.blueprintSystem.tryDropBlueprint(x, y, 'mining');
}
return 'destroyed'; return 'destroyed';
} }
return 'hit'; return 'hit';
@@ -617,7 +623,9 @@ class TerrainSystem {
typeLower.includes('house') || typeLower.includes('house') ||
typeLower.includes('gravestone') || typeLower.includes('gravestone') ||
typeLower.includes('bush') || typeLower.includes('bush') ||
typeLower.includes('fallen_log') typeLower.includes('fallen_log') ||
typeLower.includes('furnace') || // WORKSTATION
typeLower.includes('mint') // WORKSTATION
); );
const decorData = { const decorData = {
@@ -637,6 +645,16 @@ class TerrainSystem {
if (this.tiles[gridY] && this.tiles[gridY][gridX]) { if (this.tiles[gridY] && this.tiles[gridY][gridX]) {
this.tiles[gridY][gridX].hasDecoration = true; 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) { setTileType(x, y, typeName) {

View File

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

View File

@@ -1,50 +1,202 @@
/**
* ZOMBIE WORKER AI SYSTEM
* Tamed zombies lahko opravljajo delo (farming, mining)
*/
class ZombieWorkerSystem { class ZombieWorkerSystem {
constructor(scene) { constructor(scene) {
this.scene = scene; this.scene = scene;
this.workers = []; // Array of tamed zombies (NPC objects) this.workers = [];
console.log('🧟 ZombieWorkerSystem: Initialized');
} }
registerWorker(npc) { assignWork(zombie, workType, workRadius = 5) {
if (!this.workers.includes(npc)) { if (!zombie.isTamed) {
this.workers.push(npc); console.warn('⚠️ Cannot assign work to untamed zombie!');
npc.workerStats = { return false;
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!`);
// UI Feedback zombie.workerData = {
this.scene.events.emit('show-floating-text', { type: workType,
x: npc.sprite.x, radius: workRadius,
y: npc.sprite.y - 50, centerX: zombie.gridX,
text: "New Worker!", centerY: zombie.gridY,
color: "#00FF00" 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) { update(delta) {
const idx = this.workers.indexOf(npc); this.workers = this.workers.filter(w =>
if (idx > -1) { this.scene.npcs.includes(w) && w.hp > 0
this.workers.splice(idx, 1); );
console.log('🧟 Zombie Worker removed.');
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) { applyDecay(zombie, delta) {
// Update logic for all workers (e.g. decay, energy regen if sleeping) const wd = zombie.workerData;
// This is called every frame, so keep it light. 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 performFarmWork(zombie) {
assignTask(taskName, targetPos) { const wd = zombie.workerData;
// TODO: Poišči prostega delavca in mu daj nalogo 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;
} }
} }

View File

@@ -320,12 +320,102 @@ class TextureGenerator {
static createGravestoneSprite(scene, key = 'gravestone') { static createGravestoneSprite(scene, key = 'gravestone') {
if (scene.textures.exists(key)) return; 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(); const ctx = canvas.getContext();
ctx.clearRect(0, 0, 32, 32);
ctx.fillStyle = '#808080'; // Clear with transparency
ctx.fillRect(8, 8, 16, 24); ctx.clearRect(0, 0, 48, 64);
ctx.beginPath(); ctx.arc(16, 8, 8, Math.PI, 0); ctx.fill();
// 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(); canvas.refresh();
} }
@@ -646,6 +736,7 @@ class TextureGenerator {
TextureGenerator.createCornSprites(this.scene); // Added Corn TextureGenerator.createCornSprites(this.scene); // Added Corn
TextureGenerator.createGravestoneSprite(this.scene); TextureGenerator.createGravestoneSprite(this.scene);
TextureGenerator.createFenceSprite(this.scene); // Procedural fence with transparency
TextureGenerator.createToolSprites(this.scene); TextureGenerator.createToolSprites(this.scene);
TextureGenerator.createItemSprites(this.scene); TextureGenerator.createItemSprites(this.scene);
TextureGenerator.createScooterSprite(this.scene, 'scooter', false); TextureGenerator.createScooterSprite(this.scene, 'scooter', false);
@@ -661,6 +752,8 @@ class TextureGenerator {
TextureGenerator.createMushroomSprite(this.scene, 'mushroom_brown', '#8B4513', '#FFE4C4'); TextureGenerator.createMushroomSprite(this.scene, 'mushroom_brown', '#8B4513', '#FFE4C4');
TextureGenerator.createFallenLogSprite(this.scene, 'fallen_log'); TextureGenerator.createFallenLogSprite(this.scene, 'fallen_log');
TextureGenerator.createPuddleSprite(this.scene, 'puddle'); TextureGenerator.createPuddleSprite(this.scene, 'puddle');
TextureGenerator.createFurnaceSprite(this.scene, 'furnace');
TextureGenerator.createMintSprite(this.scene, 'mint');
} }
static createPathStoneSprite(scene, key = 'path_stone') { 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) { constructor(scene) {
this.scene = scene; this.scene = scene;
} }