/** * 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(); } }