phase 11 part1
This commit is contained in:
22
DNEVNIK.md
22
DNEVNIK.md
@@ -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.
|
||||||
|
|||||||
27
TASKS.md
27
TASKS.md
@@ -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.
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -44,36 +44,42 @@ 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) {
|
||||||
if (this.isMounted) {
|
if (this.isMounted) {
|
||||||
|
|||||||
@@ -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)');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
learnBlueprint(id) {
|
this.unlockedRecipes.add(recipeId);
|
||||||
if (!this.knownRecipes.includes(id)) {
|
console.log(`📜 UNLOCKED RECIPE: ${recipeId}`);
|
||||||
this.knownRecipes.push(id);
|
|
||||||
console.log('🧠 Learned Recipe:', id);
|
// Notification
|
||||||
// TODO: Add to Crafting Menu
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
205
src/systems/GraveSystem.js
Normal 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`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
160
src/systems/ScooterRepairSystem.js
Normal file
160
src/systems/ScooterRepairSystem.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
177
src/systems/WorkstationSystem.js
Normal file
177
src/systems/WorkstationSystem.js
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
zombie.workerData = {
|
||||||
level: 1,
|
type: workType,
|
||||||
task: 'IDLE' // IDLE, FARM, MINE, GUARD, FOLLOW
|
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
|
||||||
|
}
|
||||||
};
|
};
|
||||||
console.log(`🧟 Zombie ${this.workers.length} registered as Worker!`);
|
|
||||||
|
|
||||||
// UI Feedback
|
this.workers.push(zombie);
|
||||||
this.scene.events.emit('show-floating-text', {
|
console.log(`🧟 Assigned ${zombie.type} as ${workType} worker`);
|
||||||
x: npc.sprite.x,
|
return true;
|
||||||
y: npc.sprite.y - 50,
|
}
|
||||||
text: "New Worker!",
|
|
||||||
color: "#00FF00"
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign a task to all idle workers or specific one
|
if (zombie.hp <= 0) this.onWorkerDeath(zombie);
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user