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

@@ -1,32 +1,95 @@
/**
* BLUEPRINT SYSTEM
* Manages unlocking crafting recipes via Blueprint items.
*/
class BlueprintSystem {
constructor(scene) {
this.scene = scene;
this.knownRecipes = ['axe', 'pickaxe', 'hoe']; // Default known
this.blueprintsFound = []; // Items found but not learned? Or just list
console.log('📜 BlueprintSystem: Initialized');
this.unlockedRecipes = new Set(); // Stores IDs of unlocked recipes
// Default unlocked recipes (Basic tools & structures)
this.unlockedRecipes.add('stick');
this.unlockedRecipes.add('plank');
this.unlockedRecipes.add('chest');
this.unlockedRecipes.add('fence');
// Blueprint Definitions (Item ID -> Recipe ID)
this.blueprints = {
'blueprint_furnace': 'furnace',
'blueprint_anvil': 'anvil',
'blueprint_scooter_part': 'scooter_engine', // Example special part
'blueprint_grave': 'gravestone'
};
}
// Called when digging/mining
tryDropBlueprint() {
if (Math.random() < 0.05) { // 5% chance
const newBp = 'blueprint_barn'; // Randomize this
console.log('✨ BLUEPRINT FOUND:', newBp);
return newBp;
/**
* Unlock a recipe
*/
unlockRecipe(recipeId) {
if (this.unlockedRecipes.has(recipeId)) {
console.log(` Recipe ${recipeId} already unlocked.`);
return false;
}
return null;
this.unlockedRecipes.add(recipeId);
console.log(`📜 UNLOCKED RECIPE: ${recipeId}`);
// Notification
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 100,
text: `NEW RECIPE: ${recipeId.toUpperCase()}!`,
color: '#00FFFF'
});
if (this.scene.soundManager) this.scene.soundManager.playSuccess();
return true;
}
learnBlueprint(id) {
if (!this.knownRecipes.includes(id)) {
this.knownRecipes.push(id);
console.log('🧠 Learned Recipe:', id);
// TODO: Add to Crafting Menu
/**
* Check if unlocked
*/
isUnlocked(recipeId) {
return this.unlockedRecipes.has(recipeId);
}
/**
* Use a blueprint item from inventory
*/
useBlueprint(itemId) {
const recipeId = this.blueprints[itemId];
if (!recipeId) return false;
const success = this.unlockRecipe(recipeId);
if (success) {
// Consume item
this.scene.inventorySystem.removeItem(itemId, 1);
return true;
}
return false;
}
hasRecipe(id) {
return this.knownRecipes.includes(id);
/**
* Try to get a blueprint drop from mining/killing
* @param {string} source 'mining', 'combat', 'chest'
*/
tryDropBlueprint(x, y, source) {
let chance = 0.05; // 5% base chance
if (source === 'boss') chance = 1.0;
if (source === 'chest') chance = 0.3;
if (Math.random() < chance) {
// Select random locked blueprint
const allBlueprints = Object.keys(this.blueprints);
const dropItem = allBlueprints[Math.floor(Math.random() * allBlueprints.length)];
// Check if user already has it unlocked? Maybe drop anyway for trading?
// For now, simple drop.
if (this.scene.interactionSystem) {
this.scene.interactionSystem.spawnLoot(x, y, dropItem, 1);
console.log(`📜 Dropped Blueprint: ${dropItem}`);
}
}
}
}

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 {
constructor(scene) {
this.scene = scene;
this.unlockedZones = ['FARM_START']; // List of IDs
this.islandsDiscovered = [];
console.log('🌍 ExpansionSystem: Initialized');
// Define Zones
this.zones = {
'farm': {
id: 'farm',
name: 'Starter Farm',
x: 0, y: 0, w: 40, h: 40, // 0-40 coordinates
unlocked: true,
cost: 0,
color: 0x00FF00
},
'forest': {
id: 'forest',
name: 'Dark Forest',
x: 40, y: 0, w: 60, h: 40, // Right of farm
unlocked: false,
cost: 100, // 100 Gold Coins
req: 'None',
color: 0x006400
},
'city': {
id: 'city',
name: 'Ruined City',
x: 0, y: 40, w: 100, h: 60, // Below farm & forest
unlocked: false,
cost: 500,
req: 'Kill Boss',
color: 0x808080
}
};
// Fog Graphics
this.fogGraphics = this.scene.add.graphics();
this.fogGraphics.setDepth(9999); // Very high depth
this.fogGraphics.setScrollFactor(1); // Moves with camera? No, world coordinates.
// wait, graphics draw in world coords? Yes if not scrollFactor 0
// We want it to cover the world terrain.
this.drawFog();
}
// Preveri, če je igralec v dovoljeni coni
checkAccess(x, y) {
// TODO: Map coordinates to Zone ID
/**
* Unlock a zone
*/
unlockZone(zoneId) {
const zone = this.zones[zoneId];
if (!zone) return false;
if (zone.unlocked) return false; // Already unlocked
// Check Logic (Payment) would be here or called before.
// For now, just unlock logic.
zone.unlocked = true;
console.log(`🗺️ Zone Unlocked: ${zone.name}`);
// Visual feedback
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 100,
text: `UNLOCKED: ${zone.name}`,
color: '#FFFFFF'
});
// Sound
if (this.scene.soundManager) this.scene.soundManager.playSuccess(); // Assuming sound exists
this.drawFog(); // Redraw fog
return true;
}
unlockZone(zoneId) {
if (!this.unlockedZones.includes(zoneId)) {
this.unlockedZones.push(zoneId);
console.log('🔓 Zone Unlocked:', zoneId);
// TODO: Remove fog/barrier
/**
* Check if player is in an unlocked zone
* Used for restricting movement or camera
*/
isPointUnlocked(x, y) {
for (const key in this.zones) {
const z = this.zones[key];
if (x >= z.x && x < z.x + z.w && y >= z.y && y < z.y + z.h) {
return z.unlocked;
}
}
return false; // Default blocked
}
/**
* Draw Fog of War over locked zones
*/
drawFog() {
this.fogGraphics.clear();
// Fill semi-transparent black over locked zones
this.fogGraphics.fillStyle(0x000000, 0.7); // 70% opacity black fog
for (const key in this.zones) {
const z = this.zones[key];
if (!z.unlocked) {
// Convert grid coords to world coords logic
// Isometric conversion might be tricky for rectangular fog on isometric grid.
// But for simplicity let's assume raw screen overlay or draw huge loose shapes?
// Actually, our map coordinates (gridX, gridY) map to iso.
// Let's try drawing tile-by-tile or chunks? Too slow.
// Better approach: Draw a huge rectangle? No, iso is diamond shape.
// Hack: Just draw simple polygons for now, or use the tile tinting system?
// Real Fog of War in Iso is complex.
// Let's stick to "Tinting" locked tiles or something simpler first.
// Or: Render a shape that covers the locked area logic.
// Since our world is 100x100 grid.
// Zone Forest: x:40, y:0, w:60, h:40.
// Let's just draw 'clouds' or text over locked areas?
// Or simplified: Just don't let player walk there.
// Let's skip heavy graphics fog for now and just add Floating Text Labels
// "LOCKED AREA: FOREST" over the center.
// Drawing 4 points of the zone in iso:
const p1 = this.scene.iso.toScreen(z.x, z.y);
const p2 = this.scene.iso.toScreen(z.x + z.w, z.y);
const p3 = this.scene.iso.toScreen(z.x + z.w, z.y + z.h);
const p4 = this.scene.iso.toScreen(z.x, z.y + z.h);
const offX = this.scene.terrainOffsetX;
const offY = this.scene.terrainOffsetY;
const poly = new Phaser.Geom.Polygon([
p1.x + offX, p1.y + offY,
p2.x + offX, p2.y + offY,
p3.x + offX, p3.y + offY,
p4.x + offX, p4.y + offY
]);
this.fogGraphics.fillPoints(poly.points, true);
// Add label? (We'd need to manage text objects separately, simple redraw is easier)
}
}
}
travelToIsland(islandId) {
console.log('🚤 Traveling to:', islandId);
// TODO: Load island map / scene
update(delta) {
// Maybe check if player tries to enter locked zone and push back?
if (this.scene.player) {
const p = this.scene.player;
if (!this.isPointUnlocked(p.gridX, p.gridY)) {
// Player is in locked zone! Push back!
// Simple bounce back logic
// Or just show warning
// console.log("Player in locked zone!");
}
}
}
}

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)) {
const decor = this.scene.terrainSystem.decorationsMap.get(id);
// Workstation Interaction
if (this.scene.workstationSystem && (decor.type.includes('furnace') || decor.type.includes('mint'))) {
const heldItem = activeTool ? { itemId: activeTool } : null;
const result = this.scene.workstationSystem.interact(gridX, gridY, heldItem);
if (result) return;
}
// handleTreeHit Logic (User Request)
// Preverimo tip in ustrezno orodje
let damage = 1;

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

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 {
constructor(scene) {
this.scene = scene;
this.workers = []; // Array of tamed zombies (NPC objects)
console.log('🧟 ZombieWorkerSystem: Initialized');
this.workers = [];
}
registerWorker(npc) {
if (!this.workers.includes(npc)) {
this.workers.push(npc);
npc.workerStats = {
energy: 100, // Energija (pada ob delu)
decay: 0, // Razpadanje (0-100%, 100% = smrt)
xp: 0, // Izkušnje zombija
level: 1,
task: 'IDLE' // IDLE, FARM, MINE, GUARD, FOLLOW
};
console.log(`🧟 Zombie ${this.workers.length} registered as Worker!`);
assignWork(zombie, workType, workRadius = 5) {
if (!zombie.isTamed) {
console.warn('⚠️ Cannot assign work to untamed zombie!');
return false;
}
// UI Feedback
this.scene.events.emit('show-floating-text', {
x: npc.sprite.x,
y: npc.sprite.y - 50,
text: "New Worker!",
color: "#00FF00"
});
zombie.workerData = {
type: workType,
radius: workRadius,
centerX: zombie.gridX,
centerY: zombie.gridY,
energy: 100,
decayRate: 0.5,
workTimer: 0,
workInterval: 5000,
status: 'IDLE',
inventory: {
seeds: 0,
hoe: 0,
watering_can: 0,
pickaxe: 0
}
};
this.workers.push(zombie);
console.log(`🧟 Assigned ${zombie.type} as ${workType} worker`);
return true;
}
removeWorker(zombie) {
const index = this.workers.indexOf(zombie);
if (index > -1) {
this.workers.splice(index, 1);
zombie.workerData = null;
}
}
unregisterWorker(npc) {
const idx = this.workers.indexOf(npc);
if (idx > -1) {
this.workers.splice(idx, 1);
console.log('🧟 Zombie Worker removed.');
update(delta) {
this.workers = this.workers.filter(w =>
this.scene.npcs.includes(w) && w.hp > 0
);
for (const worker of this.workers) {
if (!worker.workerData) continue;
this.applyDecay(worker, delta);
worker.workerData.workTimer += delta;
if (worker.workerData.workTimer >= worker.workerData.workInterval) {
worker.workerData.workTimer = 0;
if (worker.workerData.type === 'FARM') {
this.performFarmWork(worker);
} else if (worker.workerData.type === 'MINE') {
this.performMineWork(worker);
}
}
}
}
update(time, delta) {
// Update logic for all workers (e.g. decay, energy regen if sleeping)
// This is called every frame, so keep it light.
applyDecay(zombie, delta) {
const wd = zombie.workerData;
wd.energy -= (wd.decayRate * delta) / 1000;
// Example: Decay tick every 10 seconds (handled by timer, or simplified here)
if (wd.energy <= 0) {
wd.energy = 0;
zombie.hp -= (wd.decayRate * delta) / 1000;
if (zombie.sprite) zombie.sprite.setTint(0x666666);
}
if (zombie.hp <= 0) this.onWorkerDeath(zombie);
}
// Assign a task to all idle workers or specific one
assignTask(taskName, targetPos) {
// TODO: Poišči prostega delavca in mu daj nalogo
performFarmWork(zombie) {
const wd = zombie.workerData;
const terrain = this.scene.terrainSystem;
const farming = this.scene.farmingSystem;
if (!terrain || !farming) return;
// 1. Water/Harvest existing crops
if (terrain.cropsMap) {
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const key = `${wd.centerX + dx},${wd.centerY + dy}`;
if (terrain.cropsMap.has(key)) {
const crop = terrain.cropsMap.get(key);
if (!crop.isWatered) {
farming.waterCrop(wd.centerX + dx, wd.centerY + dy);
console.log(`🧟💧 Worker watered`);
wd.status = 'WORKING';
return;
}
if (crop.stage >= 4) {
farming.harvest(wd.centerX + dx, wd.centerY + dy);
console.log(`🧟🌾 Worker harvested`);
wd.status = 'WORKING';
return;
}
}
}
}
}
// 2. Plant on empty tilled soil (if has seeds)
if (wd.inventory.seeds > 0) {
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const tile = terrain.getTile(wd.centerX + dx, wd.centerY + dy);
if (tile && tile.isTilled && !tile.hasCrop) {
farming.plant(wd.centerX + dx, wd.centerY + dy, 'wheat');
wd.inventory.seeds--;
console.log(`🧟🌱 Worker planted (Seeds: ${wd.inventory.seeds})`);
wd.status = 'WORKING';
return;
}
}
}
}
// 3. Till grass/dirt
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const tile = terrain.getTile(wd.centerX + dx, wd.centerY + dy);
const typeName = tile?.type?.name || tile?.type;
if ((typeName === 'grass' || typeName === 'dirt') && !tile.isTilled) {
tile.isTilled = true;
if (tile.sprite) tile.sprite.setTint(0x8B4513);
console.log(`🧟🔨 Worker tilled soil`);
wd.status = 'WORKING';
return;
}
}
}
wd.status = 'IDLE';
}
performMineWork(zombie) {
const wd = zombie.workerData;
const terrain = this.scene.terrainSystem;
if (!terrain || !terrain.decorationsMap) return;
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const key = `${wd.centerX + dx},${wd.centerY + dy}`;
if (terrain.decorationsMap.has(key)) {
const decor = terrain.decorationsMap.get(key);
if (decor.type === 'stone' || decor.type.includes('rock')) {
terrain.removeDecoration(wd.centerX + dx, wd.centerY + dy);
if (this.scene.inventorySystem) {
this.scene.inventorySystem.addItem('stone', 1);
}
console.log(`🧟⛏️ Worker mined stone`);
wd.status = 'WORKING';
return;
}
}
}
}
wd.status = 'IDLE';
}
onWorkerDeath(zombie) {
console.log(`💀 Worker died at ${zombie.gridX},${zombie.gridY}`);
if (this.scene.interactionSystem && this.scene.interactionSystem.spawnLoot) {
this.scene.interactionSystem.spawnLoot(zombie.gridX, zombie.gridY, 'fertilizer', 1);
}
if (this.scene.statsSystem) {
this.scene.statsSystem.addXP(10);
}
this.removeWorker(zombie);
}
feedWorker(zombie, amount = 50) {
if (!zombie.workerData) return false;
zombie.workerData.energy = Math.min(100, zombie.workerData.energy + amount);
if (zombie.sprite) zombie.sprite.clearTint();
console.log(`🍖 Fed worker`);
return true;
}
}