phase 11 part1
This commit is contained in:
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
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)) {
|
||||
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;
|
||||
|
||||
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) {
|
||||
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) {
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user