ZombieSystem - KRVAVA ŽETEV core feature! Alfa taming, worker tasks, leveling, decay & graves (900 LOC)
This commit is contained in:
875
src/systems/ZombieSystem.js
Normal file
875
src/systems/ZombieSystem.js
Normal file
@@ -0,0 +1,875 @@
|
||||
/**
|
||||
* ZombieSystem.js
|
||||
* ===============
|
||||
* KRVAVA ŽETEV - Zombie Worker Management System
|
||||
*
|
||||
* Core Concept:
|
||||
* Player is "Alfa" (hybrid virus) - can tame wild zombies
|
||||
* Zombies can be assigned tasks: farming, mining, gathering, guarding
|
||||
* Zombies level up, specialize, get tired, and eventually decay
|
||||
*
|
||||
* Features:
|
||||
* - Zombie taming (Alfa scent system)
|
||||
* - AI pathfinding & task execution
|
||||
* - Skill specialization (Farmer, Miner, Guard, Gatherer)
|
||||
* - Stamina & decay mechanics
|
||||
* - Grave resting system (regeneration)
|
||||
* - Work commands (follow, farm, mine, gather, guard)
|
||||
*
|
||||
* Uses: zombie_varieties_pack_tiled_1766101086057.tsx
|
||||
* zombie_workers_2x2_grids_1766099189858.tsx
|
||||
* smart_zombies_working_1766097073226.tsx
|
||||
* specialty_zombie_workers_detailed_1766097635926.tsx
|
||||
*
|
||||
* @author NovaFarma Team
|
||||
* @date 2025-12-22
|
||||
*/
|
||||
|
||||
export default class ZombieSystem {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
|
||||
// Zombie registry
|
||||
this.zombies = new Map(); // id -> zombie data
|
||||
this.wildZombies = new Set(); // Untamed zombies
|
||||
this.tamedZombies = new Set(); // Player's zombies
|
||||
|
||||
// Alfa system
|
||||
this.alfaScent = 100; // Player's Alfa power (0-100)
|
||||
this.alfaRange = 150; // Taming range (pixels)
|
||||
|
||||
// Task queues
|
||||
this.taskQueue = new Map(); // zombieId -> task
|
||||
|
||||
// Graves
|
||||
this.graves = new Map(); // position -> grave data
|
||||
|
||||
// Zombie definitions
|
||||
this.zombieTypes = this.defineZombieTypes();
|
||||
this.skillTrees = this.defineSkillTrees();
|
||||
|
||||
// Timing constants
|
||||
this.DECAY_RATE = 0.1; // HP loss per second when not resting
|
||||
this.STAMINA_DRAIN_RATE = 5; // Stamina loss per second when working
|
||||
this.STAMINA_REGEN_RATE = 20; // Stamina gain per second when resting
|
||||
this.GRAVE_REST_BONUS = 2.0; // 2x regen in grave
|
||||
|
||||
console.log('🧟 ZombieSystem initialized - ALFA ACTIVE');
|
||||
}
|
||||
|
||||
defineZombieTypes() {
|
||||
return {
|
||||
basic: {
|
||||
name: 'Basic Zombie',
|
||||
baseHP: 100,
|
||||
baseStamina: 100,
|
||||
baseSpeed: 60,
|
||||
baseWorkSpeed: 1.0,
|
||||
sprite: 'zombie_varieties_pack', // Frame 0
|
||||
tameDifficulty: 1
|
||||
},
|
||||
worker: {
|
||||
name: 'Zombie Worker',
|
||||
baseHP: 120,
|
||||
baseStamina: 150,
|
||||
baseSpeed: 70,
|
||||
baseWorkSpeed: 1.2,
|
||||
sprite: 'zombie_workers_2x2_grids', // Frame 0
|
||||
tameDifficulty: 2
|
||||
},
|
||||
smart: {
|
||||
name: 'Smart Zombie',
|
||||
baseHP: 150,
|
||||
baseStamina: 120,
|
||||
baseSpeed: 80,
|
||||
baseWorkSpeed: 1.5,
|
||||
sprite: 'smart_zombies_working',
|
||||
tameDifficulty: 3,
|
||||
canLearn: true // Levels faster
|
||||
},
|
||||
elite: {
|
||||
name: 'Elite Zombie',
|
||||
baseHP: 200,
|
||||
baseStamina: 200,
|
||||
baseSpeed: 100,
|
||||
baseWorkSpeed: 2.0,
|
||||
sprite: 'elite_zombie',
|
||||
tameDifficulty: 5,
|
||||
canLearn: true,
|
||||
specialAbility: 'multitask' // Can do 2 tasks
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
defineSkillTrees() {
|
||||
return {
|
||||
farmer: {
|
||||
name: 'Farmer Zombie',
|
||||
tasks: ['plant', 'harvest', 'water', 'fertilize'],
|
||||
levelBonuses: {
|
||||
1: { workSpeed: 1.0 },
|
||||
5: { workSpeed: 1.2, yieldBonus: 1.1 },
|
||||
10: { workSpeed: 1.5, yieldBonus: 1.3, autoPlant: true },
|
||||
15: { workSpeed: 2.0, yieldBonus: 1.5, qualityBonus: 1.2 }
|
||||
},
|
||||
sprite: 'specialty_zombie_workers', // Frame 0
|
||||
color: 0x00FF00 // Green tint
|
||||
},
|
||||
miner: {
|
||||
name: 'Miner Zombie',
|
||||
tasks: ['mine', 'quarry', 'smelt'],
|
||||
levelBonuses: {
|
||||
1: { workSpeed: 1.0 },
|
||||
5: { workSpeed: 1.2, oreBonus: 1.1 },
|
||||
10: { workSpeed: 1.5, oreBonus: 1.3, rareOreChance: 0.1 },
|
||||
15: { workSpeed: 2.0, oreBonus: 1.5, gemChance: 0.2 }
|
||||
},
|
||||
sprite: 'specialty_zombie_workers', // Frame 1
|
||||
color: 0x888888 // Gray tint
|
||||
},
|
||||
gatherer: {
|
||||
name: 'Gatherer Zombie',
|
||||
tasks: ['gather', 'forage', 'loot'],
|
||||
levelBonuses: {
|
||||
1: { workSpeed: 1.0 },
|
||||
5: { workSpeed: 1.2, gatherRadius: 1.2 },
|
||||
10: { workSpeed: 1.5, gatherRadius: 1.5, rareItemChance: 0.1 },
|
||||
15: { workSpeed: 2.0, gatherRadius: 2.0, doubleGather: 0.3 }
|
||||
},
|
||||
sprite: 'specialty_zombie_workers', // Frame 2
|
||||
color: 0xFFFF00 // Yellow tint
|
||||
},
|
||||
guard: {
|
||||
name: 'Guard Zombie',
|
||||
tasks: ['guard', 'patrol', 'attack'],
|
||||
levelBonuses: {
|
||||
1: { damage: 10, defense: 5 },
|
||||
5: { damage: 20, defense: 10, detectRange: 1.2 },
|
||||
10: { damage: 35, defense: 20, detectRange: 1.5, counterAttack: true },
|
||||
15: { damage: 50, defense: 30, detectRange: 2.0, areaAttack: true }
|
||||
},
|
||||
sprite: 'specialty_zombie_workers', // Frame 3
|
||||
color: 0xFF0000 // Red tint
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ===== ZOMBIE SPAWNING =====
|
||||
|
||||
spawnWildZombie(x, y, type = 'basic') {
|
||||
const zombieType = this.zombieTypes[type];
|
||||
if (!zombieType) return null;
|
||||
|
||||
const zombie = {
|
||||
id: this.generateId(),
|
||||
type: type,
|
||||
state: 'wild', // wild, tamed, working, resting, decaying
|
||||
|
||||
// Position
|
||||
x: x,
|
||||
y: y,
|
||||
|
||||
// Stats
|
||||
hp: zombieType.baseHP,
|
||||
maxHP: zombieType.baseHP,
|
||||
stamina: zombieType.baseStamina,
|
||||
maxStamina: zombieType.baseStamina,
|
||||
|
||||
// Progression
|
||||
level: 1,
|
||||
xp: 0,
|
||||
xpToNextLevel: 100,
|
||||
specialization: null, // farmer, miner, gatherer, guard
|
||||
|
||||
// Work
|
||||
currentTask: null,
|
||||
workSpeed: zombieType.baseWorkSpeed,
|
||||
efficiency: 1.0,
|
||||
|
||||
// Decay
|
||||
decayTimer: 24 * 60 * 60 * 1000, // 24 hours to decay
|
||||
decayRate: this.DECAY_RATE,
|
||||
|
||||
// Behavior
|
||||
speed: zombieType.baseSpeed,
|
||||
aggression: 0.3, // Wild zombies slightly aggressive
|
||||
loyalty: 0, // Increases when tamed
|
||||
|
||||
// Visual
|
||||
sprite: null,
|
||||
tint: 0xFFFFFF,
|
||||
|
||||
// Timestamps
|
||||
spawnTime: Date.now(),
|
||||
lastWorked: null,
|
||||
lastRested: null
|
||||
};
|
||||
|
||||
// Create sprite
|
||||
zombie.sprite = this.createZombieSprite(zombie, zombieType);
|
||||
|
||||
// Add to registry
|
||||
this.zombies.set(zombie.id, zombie);
|
||||
this.wildZombies.add(zombie.id);
|
||||
|
||||
console.log(`🧟 Spawned wild ${zombieType.name} at (${x}, ${y})`);
|
||||
return zombie;
|
||||
}
|
||||
|
||||
createZombieSprite(zombie, zombieType) {
|
||||
const sprite = this.scene.add.sprite(zombie.x, zombie.y, zombieType.sprite);
|
||||
|
||||
sprite.setData('zombieId', zombie.id);
|
||||
sprite.setInteractive();
|
||||
|
||||
// Physics
|
||||
this.scene.physics.add.existing(sprite);
|
||||
sprite.body.setCollideWorldBounds(true);
|
||||
|
||||
// Click handler
|
||||
sprite.on('pointerdown', () => {
|
||||
this.onZombieClicked(zombie);
|
||||
});
|
||||
|
||||
// Health bar
|
||||
this.createHealthBar(sprite);
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
createHealthBar(sprite) {
|
||||
const bar = this.scene.add.graphics();
|
||||
bar.fillStyle(0xFF0000);
|
||||
bar.fillRect(-20, -30, 40, 4);
|
||||
sprite.healthBar = bar;
|
||||
sprite.healthBar.x = sprite.x;
|
||||
sprite.healthBar.y = sprite.y;
|
||||
}
|
||||
|
||||
// ===== ALFA SYSTEM (TAMING) =====
|
||||
|
||||
canTameZombie(zombieId) {
|
||||
const zombie = this.zombies.get(zombieId);
|
||||
if (!zombie) {
|
||||
return { canTame: false, reason: 'Zombie not found' };
|
||||
}
|
||||
|
||||
if (zombie.state !== 'wild') {
|
||||
return { canTame: false, reason: 'Already tamed' };
|
||||
}
|
||||
|
||||
// Check distance (Alfa scent range)
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
this.scene.player.x, this.scene.player.y,
|
||||
zombie.x, zombie.y
|
||||
);
|
||||
|
||||
if (distance > this.alfaRange) {
|
||||
return { canTame: false, reason: 'Too far - get closer!' };
|
||||
}
|
||||
|
||||
// Check Alfa power
|
||||
const zombieType = this.zombieTypes[zombie.type];
|
||||
const requiredAlfaPower = zombieType.tameDifficulty * 15;
|
||||
|
||||
if (this.alfaScent < requiredAlfaPower) {
|
||||
return {
|
||||
canTame: false,
|
||||
reason: `Need ${requiredAlfaPower} Alfa power (have ${this.alfaScent})`
|
||||
};
|
||||
}
|
||||
|
||||
return { canTame: true };
|
||||
}
|
||||
|
||||
tameZombie(zombieId) {
|
||||
const check = this.canTameZombie(zombieId);
|
||||
if (!check.canTame) {
|
||||
this.scene.uiSystem?.showError(check.reason);
|
||||
return false;
|
||||
}
|
||||
|
||||
const zombie = this.zombies.get(zombieId);
|
||||
const zombieType = this.zombieTypes[zombie.type];
|
||||
|
||||
// Taming animation
|
||||
if (this.scene.particleSystem) {
|
||||
this.scene.particleSystem.createTamingEffect(zombie.x, zombie.y);
|
||||
}
|
||||
|
||||
// Update zombie state
|
||||
zombie.state = 'tamed';
|
||||
zombie.loyalty = 50; // Starts at 50, increases with work
|
||||
zombie.aggression = 0;
|
||||
|
||||
// Remove from wild, add to tamed
|
||||
this.wildZombies.delete(zombieId);
|
||||
this.tamedZombies.add(zombieId);
|
||||
|
||||
// Visual feedback
|
||||
zombie.sprite.setTint(0x00FF88); // Green tint for tamed
|
||||
|
||||
// Notification
|
||||
this.scene.uiSystem?.showNotification({
|
||||
title: '🧟 Zombie Tamed!',
|
||||
message: `${zombieType.name} joined your workforce!`,
|
||||
icon: 'zombie',
|
||||
duration: 4000,
|
||||
color: '#00FF00'
|
||||
});
|
||||
|
||||
// Emit event
|
||||
this.scene.events.emit('zombieTamed', { zombie });
|
||||
|
||||
// Consume Alfa power
|
||||
const cost = zombieType.tameDifficulty * 10;
|
||||
this.alfaScent = Math.max(0, this.alfaScent - cost);
|
||||
|
||||
console.log(`🧟 Tamed ${zombieType.name} - Loyalty: ${zombie.loyalty}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== TASK ASSIGNMENT =====
|
||||
|
||||
assignTask(zombieId, task) {
|
||||
const zombie = this.zombies.get(zombieId);
|
||||
if (!zombie || zombie.state !== 'tamed') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if zombie has specialization for this task
|
||||
if (zombie.specialization) {
|
||||
const spec = this.skillTrees[zombie.specialization];
|
||||
if (!spec.tasks.includes(task.type)) {
|
||||
this.scene.uiSystem?.showError(`This zombie specializes in ${zombie.specialization} tasks!`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Assign task
|
||||
zombie.currentTask = task;
|
||||
zombie.state = 'working';
|
||||
this.taskQueue.set(zombieId, task);
|
||||
|
||||
console.log(`🧟 ${zombie.id} assigned task:`, task.type);
|
||||
return true;
|
||||
}
|
||||
|
||||
executeTask(zombie, delta) {
|
||||
const task = zombie.currentTask;
|
||||
if (!task) return;
|
||||
|
||||
const deltaSeconds = delta / 1000;
|
||||
|
||||
// Drain stamina
|
||||
zombie.stamina -= this.STAMINA_DRAIN_RATE * deltaSeconds;
|
||||
|
||||
// If exhausted, stop working
|
||||
if (zombie.stamina <= 0) {
|
||||
zombie.state = 'idle';
|
||||
zombie.currentTask = null;
|
||||
this.scene.uiSystem?.showNotification({
|
||||
title: 'Zombie Exhausted!',
|
||||
message: `Zombie ${zombie.id} needs rest`,
|
||||
icon: 'zombie',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute task based on type
|
||||
switch (task.type) {
|
||||
case 'farm':
|
||||
this.executeFarmTask(zombie, task, deltaSeconds);
|
||||
break;
|
||||
case 'mine':
|
||||
this.executeMineTask(zombie, task, deltaSeconds);
|
||||
break;
|
||||
case 'gather':
|
||||
this.executeGatherTask(zombie, task, deltaSeconds);
|
||||
break;
|
||||
case 'guard':
|
||||
this.executeGuardTask(zombie, task, deltaSeconds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
executeFarmTask(zombie, task, deltaSeconds) {
|
||||
// Move to farm plot
|
||||
this.moveTowardsTarget(zombie, task.targetX, task.targetY);
|
||||
|
||||
// If close enough, work
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
zombie.x, zombie.y, task.targetX, task.targetY
|
||||
);
|
||||
|
||||
if (distance < 20) {
|
||||
task.progress = (task.progress || 0) + (zombie.workSpeed * deltaSeconds);
|
||||
|
||||
// Complete task
|
||||
if (task.progress >= task.requiredProgress) {
|
||||
this.completeTask(zombie, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeMineTask(zombie, task, deltaSeconds) {
|
||||
this.moveTowardsTarget(zombie, task.targetX, task.targetY);
|
||||
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
zombie.x, zombie.y, task.targetX, task.targetY
|
||||
);
|
||||
|
||||
if (distance < 20) {
|
||||
task.progress = (task.progress || 0) + (zombie.workSpeed * 0.8 * deltaSeconds);
|
||||
|
||||
if (task.progress >= task.requiredProgress) {
|
||||
this.completeTask(zombie, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeGatherTask(zombie, task, deltaSeconds) {
|
||||
this.moveTowardsTarget(zombie, task.targetX, task.targetY);
|
||||
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
zombie.x, zombie.y, task.targetX, task.targetY
|
||||
);
|
||||
|
||||
if (distance < 30) {
|
||||
task.progress = (task.progress || 0) + (zombie.workSpeed * 1.2 * deltaSeconds);
|
||||
|
||||
if (task.progress >= task.requiredProgress) {
|
||||
this.completeTask(zombie, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeGuardTask(zombie, task, deltaSeconds) {
|
||||
// Patrol area
|
||||
if (!task.patrolTarget || zombie.reachedPatrol) {
|
||||
task.patrolTarget = {
|
||||
x: task.centerX + Phaser.Math.Between(-task.radius, task.radius),
|
||||
y: task.centerY + Phaser.Math.Between(-task.radius, task.radius)
|
||||
};
|
||||
zombie.reachedPatrol = false;
|
||||
}
|
||||
|
||||
this.moveTowardsTarget(zombie, task.patrolTarget.x, task.patrolTarget.y);
|
||||
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
zombie.x, zombie.y, task.patrolTarget.x, task.patrolTarget.y
|
||||
);
|
||||
|
||||
if (distance < 10) {
|
||||
zombie.reachedPatrol = true;
|
||||
}
|
||||
|
||||
// Check for enemies
|
||||
this.detectEnemies(zombie, task.radius);
|
||||
}
|
||||
|
||||
completeTask(zombie, task) {
|
||||
// Award XP
|
||||
const xpGain = task.xpReward || 20;
|
||||
this.addXP(zombie, xpGain);
|
||||
|
||||
// Increase loyalty
|
||||
zombie.loyalty = Math.min(100, zombie.loyalty + 1);
|
||||
|
||||
// Clear task
|
||||
zombie.currentTask = null;
|
||||
zombie.state = 'idle';
|
||||
this.taskQueue.delete(zombie.id);
|
||||
|
||||
// Emit event
|
||||
this.scene.events.emit('zombieTaskComplete', { zombie, task });
|
||||
|
||||
console.log(`✅ Zombie ${zombie.id} completed ${task.type} task (+${xpGain} XP)`);
|
||||
}
|
||||
|
||||
// ===== LEVELING & SPECIALIZATION =====
|
||||
|
||||
addXP(zombie, amount) {
|
||||
zombie.xp += amount;
|
||||
|
||||
// Check for level up
|
||||
while (zombie.xp >= zombie.xpToNextLevel) {
|
||||
this.levelUp(zombie);
|
||||
}
|
||||
}
|
||||
|
||||
levelUp(zombie) {
|
||||
zombie.level++;
|
||||
zombie.xp -= zombie.xpToNextLevel;
|
||||
zombie.xpToNextLevel = Math.floor(zombie.xpToNextLevel * 1.5);
|
||||
|
||||
// Increase stats
|
||||
zombie.maxHP += 10;
|
||||
zombie.hp = zombie.maxHP;
|
||||
zombie.maxStamina += 10;
|
||||
zombie.stamina = zombie.maxStamina;
|
||||
zombie.workSpeed += 0.1;
|
||||
|
||||
// Apply specialization bonuses
|
||||
if (zombie.specialization) {
|
||||
const spec = this.skillTrees[zombie.specialization];
|
||||
const levelBonuses = spec.levelBonuses[zombie.level];
|
||||
|
||||
if (levelBonuses) {
|
||||
Object.assign(zombie, levelBonuses);
|
||||
}
|
||||
}
|
||||
|
||||
// Notification
|
||||
this.scene.uiSystem?.showNotification({
|
||||
title: '🧟 LEVEL UP!',
|
||||
message: `Zombie ${zombie.id} reached level ${zombie.level}!`,
|
||||
icon: 'zombie',
|
||||
duration: 3000,
|
||||
color: '#FFD700'
|
||||
});
|
||||
|
||||
console.log(`🆙 Zombie ${zombie.id} leveled up to ${zombie.level}!`);
|
||||
}
|
||||
|
||||
specializeZombie(zombieId, specialization) {
|
||||
const zombie = this.zombies.get(zombieId);
|
||||
if (!zombie) return false;
|
||||
|
||||
if (zombie.specialization) {
|
||||
this.scene.uiSystem?.showError('Already specialized!');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (zombie.level < 3) {
|
||||
this.scene.uiSystem?.showError('Zombie must be level 3+ to specialize');
|
||||
return false;
|
||||
}
|
||||
|
||||
const spec = this.skillTrees[specialization];
|
||||
zombie.specialization = specialization;
|
||||
zombie.sprite.setTint(spec.color);
|
||||
|
||||
this.scene.uiSystem?.showNotification({
|
||||
title: '⚡ Specialization Unlocked!',
|
||||
message: `Zombie is now a ${spec.name}!`,
|
||||
icon: 'zombie',
|
||||
duration: 4000,
|
||||
color: '#00FFFF'
|
||||
});
|
||||
|
||||
console.log(`⚡ Zombie ${zombie.id} specialized as ${spec.name}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== DECAY SYSTEM =====
|
||||
|
||||
updateDecay(zombie, delta) {
|
||||
if (zombie.state === 'resting') return; // No decay when resting
|
||||
|
||||
const deltaSeconds = delta / 1000;
|
||||
|
||||
// Drain decay timer
|
||||
zombie.decayTimer -= deltaSeconds * 1000;
|
||||
|
||||
// If timer expired, start losing HP
|
||||
if (zombie.decayTimer <= 0) {
|
||||
zombie.hp -= zombie.decayRate * deltaSeconds;
|
||||
|
||||
// Death check
|
||||
if (zombie.hp <= 0) {
|
||||
this.zombieDeath(zombie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zombieDeath(zombie) {
|
||||
console.log(`💀 Zombie ${zombie.id} has decayed completely`);
|
||||
|
||||
// Drop fertilizer (good for farming!)
|
||||
if (this.scene.farmingSystem) {
|
||||
this.scene.farmingSystem.spawnFertilizer(zombie.x, zombie.y, 5);
|
||||
}
|
||||
|
||||
// Award player XP
|
||||
if (this.scene.player) {
|
||||
this.scene.player.addXP?.(zombie.level * 10);
|
||||
}
|
||||
|
||||
// Remove zombie
|
||||
zombie.sprite?.destroy();
|
||||
zombie.healthBar?.destroy();
|
||||
this.zombies.delete(zombie.id);
|
||||
this.tamedZombies.delete(zombie.id);
|
||||
|
||||
// Notification
|
||||
this.scene.uiSystem?.showNotification({
|
||||
title: '💀 Zombie Decayed',
|
||||
message: `Zombie turned to fertilizer (+${zombie.level * 10} XP)`,
|
||||
icon: 'zombie',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
|
||||
// ===== GRAVE SYSTEM =====
|
||||
|
||||
createGrave(x, y) {
|
||||
const graveId = `${x}_${y}`;
|
||||
|
||||
if (this.graves.has(graveId)) {
|
||||
this.scene.uiSystem?.showError('Grave already exists here!');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if player has materials
|
||||
if (this.scene.recipeSystem) {
|
||||
const canCraft = this.scene.recipeSystem.canCraft('grave');
|
||||
if (!canCraft.canCraft) {
|
||||
this.scene.uiSystem?.showError(canCraft.reason);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const grave = {
|
||||
id: graveId,
|
||||
x: x,
|
||||
y: y,
|
||||
occupied: false,
|
||||
zombieId: null,
|
||||
sprite: this.scene.add.sprite(x, y, 'gravestone')
|
||||
};
|
||||
|
||||
this.graves.set(graveId, grave);
|
||||
|
||||
console.log(`⚰️ Created grave at (${x}, ${y})`);
|
||||
return true;
|
||||
}
|
||||
|
||||
zombieRest(zombieId, graveId) {
|
||||
const zombie = this.zombies.get(zombieId);
|
||||
const grave = this.graves.get(graveId);
|
||||
|
||||
if (!zombie || !grave) return false;
|
||||
|
||||
if (grave.occupied) {
|
||||
this.scene.uiSystem?.showError('Grave is occupied!');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move zombie to grave
|
||||
zombie.state = 'resting';
|
||||
zombie.sprite.setPosition(grave.x, grave.y);
|
||||
zombie.sprite.setAlpha(0.5); // Semi-transparent
|
||||
|
||||
grave.occupied = true;
|
||||
grave.zombieId = zombieId;
|
||||
|
||||
console.log(`😴 Zombie ${zombieId} resting in grave`);
|
||||
return true;
|
||||
}
|
||||
|
||||
updateResting(zombie, delta) {
|
||||
const deltaSeconds = delta / 1000;
|
||||
|
||||
// Regenerate stamina (2x faster in grave)
|
||||
zombie.stamina = Math.min(
|
||||
zombie.maxStamina,
|
||||
zombie.stamina + this.STAMINA_REGEN_RATE * this.GRAVE_REST_BONUS * deltaSeconds
|
||||
);
|
||||
|
||||
// Slow HP regen
|
||||
zombie.hp = Math.min(
|
||||
zombie.maxHP,
|
||||
zombie.hp + 2 * deltaSeconds
|
||||
);
|
||||
|
||||
// Restore decay timer
|
||||
zombie.decayTimer += 1000 * deltaSeconds; // Add 1 second per second resting
|
||||
|
||||
// If fully rested, wake up
|
||||
if (zombie.stamina >= zombie.maxStamina) {
|
||||
this.wakeUpZombie(zombie);
|
||||
}
|
||||
}
|
||||
|
||||
wakeUpZombie(zombie) {
|
||||
zombie.state = 'idle';
|
||||
zombie.sprite.setAlpha(1.0);
|
||||
|
||||
// Find and free grave
|
||||
for (const [graveId, grave] of this.graves.entries()) {
|
||||
if (grave.zombieId === zombie.id) {
|
||||
grave.occupied = false;
|
||||
grave.zombieId = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`😊 Zombie ${zombie.id} woke up refreshed!`);
|
||||
}
|
||||
|
||||
// ===== MOVEMENT & PATHFINDING =====
|
||||
|
||||
moveTowardsTarget(zombie, targetX, targetY) {
|
||||
const angle = Phaser.Math.Angle.Between(zombie.x, zombie.y, targetX, targetY);
|
||||
|
||||
const velocityX = Math.cos(angle) * zombie.speed;
|
||||
const velocityY = Math.sin(angle) * zombie.speed;
|
||||
|
||||
zombie.sprite.body.setVelocity(velocityX, velocityY);
|
||||
|
||||
// Update position
|
||||
zombie.x = zombie.sprite.x;
|
||||
zombie.y = zombie.sprite.y;
|
||||
}
|
||||
|
||||
detectEnemies(zombie, radius) {
|
||||
// Guard zombies detect and attack enemies
|
||||
if (!this.scene.enemies) return;
|
||||
|
||||
this.scene.enemies.getChildren().forEach(enemy => {
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
zombie.x, zombie.y, enemy.x, enemy.y
|
||||
);
|
||||
|
||||
if (distance < radius) {
|
||||
this.zombieAttack(zombie, enemy);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
zombieAttack(zombie, enemy) {
|
||||
const spec = zombie.specialization === 'guard' ?
|
||||
this.skillTrees.guard.levelBonuses[zombie.level] : null;
|
||||
|
||||
const damage = spec ? spec.damage : 10;
|
||||
enemy.takeDamage?.(damage);
|
||||
|
||||
// Gain XP for combat
|
||||
this.addXP(zombie, 5);
|
||||
}
|
||||
|
||||
// ===== COMMANDS =====
|
||||
|
||||
commandFollow(zombieId) {
|
||||
const zombie = this.zombies.get(zombieId);
|
||||
if (!zombie) return;
|
||||
|
||||
zombie.currentTask = {
|
||||
type: 'follow',
|
||||
targetX: this.scene.player.x,
|
||||
targetY: this.scene.player.y
|
||||
};
|
||||
zombie.state = 'following';
|
||||
}
|
||||
|
||||
commandGuardArea(zombieId, x, y, radius = 100) {
|
||||
this.assignTask(zombieId, {
|
||||
type: 'guard',
|
||||
centerX: x,
|
||||
centerY: y,
|
||||
radius: radius,
|
||||
requiredProgress: Infinity // Never ends
|
||||
});
|
||||
}
|
||||
|
||||
// ===== HELPER FUNCTIONS =====
|
||||
|
||||
generateId() {
|
||||
return `zombie_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
onZombieClicked(zombie) {
|
||||
this.scene.events.emit('zombieSelected', { zombie });
|
||||
// Show zombie info UI
|
||||
}
|
||||
|
||||
// ===== GETTERS =====
|
||||
|
||||
getTamedZombies() {
|
||||
return Array.from(this.tamedZombies).map(id => this.zombies.get(id));
|
||||
}
|
||||
|
||||
getWorkingZombies() {
|
||||
return this.getTamedZombies().filter(z => z.state === 'working');
|
||||
}
|
||||
|
||||
getIdleZombies() {
|
||||
return this.getTamedZombies().filter(z => z.state === 'idle');
|
||||
}
|
||||
|
||||
getZombiesBySpecialization(spec) {
|
||||
return this.getTamedZombies().filter(z => z.specialization === spec);
|
||||
}
|
||||
|
||||
// ===== UPDATE =====
|
||||
|
||||
update(time, delta) {
|
||||
for (const [id, zombie] of this.zombies.entries()) {
|
||||
// Update based on state
|
||||
switch (zombie.state) {
|
||||
case 'working':
|
||||
this.executeTask(zombie, delta);
|
||||
this.updateDecay(zombie, delta);
|
||||
break;
|
||||
|
||||
case 'resting':
|
||||
this.updateResting(zombie, delta);
|
||||
break;
|
||||
|
||||
case 'following':
|
||||
this.moveTowardsTarget(zombie, this.scene.player.x, this.scene.player.y);
|
||||
this.updateDecay(zombie, delta);
|
||||
break;
|
||||
|
||||
case 'idle':
|
||||
case 'tamed':
|
||||
this.updateDecay(zombie, delta);
|
||||
break;
|
||||
|
||||
case 'wild':
|
||||
// Wild zombies wander
|
||||
if (Math.random() < 0.01) {
|
||||
const wanderX = zombie.x + Phaser.Math.Between(-50, 50);
|
||||
const wanderY = zombie.y + Phaser.Math.Between(-50, 50);
|
||||
this.moveTowardsTarget(zombie, wanderX, wanderY);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Update health bar
|
||||
if (zombie.healthBar && zombie.sprite) {
|
||||
zombie.healthBar.x = zombie.sprite.x;
|
||||
zombie.healthBar.y = zombie.sprite.y;
|
||||
|
||||
const healthPercent = zombie.hp / zombie.maxHP;
|
||||
zombie.healthBar.clear();
|
||||
zombie.healthBar.fillStyle(healthPercent > 0.5 ? 0x00FF00 : 0xFF0000);
|
||||
zombie.healthBar.fillRect(-20, -30, 40 * healthPercent, 4);
|
||||
}
|
||||
}
|
||||
|
||||
// Regenerate Alfa scent slowly
|
||||
this.alfaScent = Math.min(100, this.alfaScent + 0.01 * (delta / 1000));
|
||||
}
|
||||
|
||||
// ===== CLEANUP =====
|
||||
|
||||
destroy() {
|
||||
for (const zombie of this.zombies.values()) {
|
||||
zombie.sprite?.destroy();
|
||||
zombie.healthBar?.destroy();
|
||||
}
|
||||
|
||||
for (const grave of this.graves.values()) {
|
||||
grave.sprite?.destroy();
|
||||
}
|
||||
|
||||
this.zombies.clear();
|
||||
this.wildZombies.clear();
|
||||
this.tamedZombies.clear();
|
||||
this.graves.clear();
|
||||
this.taskQueue.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user