From c922204fe79e1a5cac880c357d12fa40990faa05 Mon Sep 17 00:00:00 2001 From: NovaFarma Dev Date: Mon, 22 Dec 2025 19:39:02 +0100 Subject: [PATCH] =?UTF-8?q?=20ZombieSystem=20-=20KRVAVA=20=C5=BDETEV=20cor?= =?UTF-8?q?e=20feature!=20Alfa=20taming,=20worker=20tasks,=20leveling,=20d?= =?UTF-8?q?ecay=20&=20graves=20(900=20LOC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/systems/ZombieSystem.js | 875 ++++++++++++++++++++++++++++++++++++ 1 file changed, 875 insertions(+) create mode 100644 src/systems/ZombieSystem.js diff --git a/src/systems/ZombieSystem.js b/src/systems/ZombieSystem.js new file mode 100644 index 0000000..8172976 --- /dev/null +++ b/src/systems/ZombieSystem.js @@ -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(); + } +}