From 370c527fcd02f0ef776a5f5c60a870fd1aad7ea4 Mon Sep 17 00:00:00 2001 From: David Kotnik Date: Mon, 5 Jan 2026 19:16:23 +0100 Subject: [PATCH] SYSTEMS 4-6/9 COMPLETE: ZombieScoutSkills (skill tree, active/passive abilities), NomadRaiderAI (state machine, pathfinding, loot stealing), FarmRaidSystem (wave spawning, difficulty scaling, rewards). Progress: 6/9 systems (67%). --- src/systems/FarmRaidSystem.js | 392 +++++++++++++++++++++++++++++ src/systems/NomadRaiderAI.js | 348 +++++++++++++++++++++++++ src/systems/ZombieScoutSkills.js | 420 +++++++++++++++++++++++++++++++ 3 files changed, 1160 insertions(+) create mode 100644 src/systems/FarmRaidSystem.js create mode 100644 src/systems/NomadRaiderAI.js create mode 100644 src/systems/ZombieScoutSkills.js diff --git a/src/systems/FarmRaidSystem.js b/src/systems/FarmRaidSystem.js new file mode 100644 index 000000000..61ee0ed55 --- /dev/null +++ b/src/systems/FarmRaidSystem.js @@ -0,0 +1,392 @@ +/** + * FARM RAID SYSTEM + * Spawn raider waves, defend farm, rewards + * Integrates with DefenseSystem and NomadRaiderAI + */ + +export class FarmRaidSystem { + constructor(scene) { + this.scene = scene; + + // Raid state + this.isRaidActive = false; + this.currentWave = 0; + this.totalWaves = 0; + this.raidDifficulty = 1; + + // Spawn management + this.activeRaiders = []; + this.spawnPoints = []; + + // Raid triggers + this.fameThreshold = 500; // Trigger raid at 500 fame + this.resourceThreshold = 5000; // Or 5000 resources + this.timeBetweenRaids = 600000; // 10 minutes minimum + this.lastRaidTime = 0; + + // Rewards + this.defenseRewards = new Map(); + + this.init(); + } + + init() { + this.initializeSpawnPoints(); + this.initializeRewards(); + + // Check for raid triggers periodically + this.scene.time.addEvent({ + delay: 30000, // Check every 30s + callback: () => this.checkRaidTriggers(), + loop: true + }); + } + + /** + * Initialize raid spawn points around farm + */ + initializeSpawnPoints() { + const farmCenter = { x: 400, y: 300 }; // Adjust to actual farm center + const spawnDistance = 400; + + // 8 spawn points around farm (N, NE, E, SE, S, SW, W, NW) + for (let angle = 0; angle < 360; angle += 45) { + const rad = Phaser.Math.DegToRad(angle); + this.spawnPoints.push({ + x: farmCenter.x + Math.cos(rad) * spawnDistance, + y: farmCenter.y + Math.sin(rad) * spawnDistance, + angle: angle + }); + } + } + + /** + * Initialize defense rewards + */ + initializeRewards() { + this.defenseRewards.set(1, { xp: 200, currency: 500, items: ['raider_loot_common'] }); + this.defenseRewards.set(2, { xp: 400, currency: 1000, items: ['raider_loot_common', 'raider_weapon'] }); + this.defenseRewards.set(3, { xp: 800, currency: 2000, items: ['raider_loot_rare', 'raider_armor'] }); + } + + /** + * Check if raid should trigger + */ + checkRaidTriggers() { + if (this.isRaidActive) return; + + const now = Date.now(); + const timeSinceLastRaid = now - this.lastRaidTime; + + // Cooldown check + if (timeSinceLastRaid < this.timeBetweenRaids) return; + + const fame = this.scene.gameState?.fame || 0; + const totalResources = this.scene.inventorySystem?.getTotalResourceValue() || 0; + + // Trigger conditions + if (fame >= this.fameThreshold || totalResources >= this.resourceThreshold) { + this.startRaid(); + } + } + + /** + * Start a raid + */ + startRaid(difficulty = null) { + if (this.isRaidActive) return; + + this.isRaidActive = true; + this.currentWave = 0; + this.raidDifficulty = difficulty || this.calculateDifficulty(); + this.totalWaves = 2 + this.raidDifficulty; // 3-5 waves + this.lastRaidTime = Date.now(); + + // Notification + this.scene.uiSystem?.showNotification( + `RAID INCOMING! ${this.totalWaves} waves approaching!`, + 'danger', + { priority: 'high' } + ); + + // SFX: Raid horn + this.scene.soundSystem?.play('raid_horn'); + + // VFX: Red screen flash + this.scene.cameras.main.flash(1000, 255, 0, 0); + + // Start first wave + this.scene.time.delayedCall(3000, () => this.spawnWave()); + + console.log(`🚨 RAID STARTED: Difficulty ${this.raidDifficulty}, ${this.totalWaves} waves`); + } + + /** + * Calculate raid difficulty based on game progress + */ + calculateDifficulty() { + const fame = this.scene.gameState?.fame || 0; + const population = this.scene.gameState?.population || 0; + + // Scale difficulty + return Math.min(5, Math.floor((fame / 500) + (population / 20))); + } + + /** + * Spawn a wave of raiders + */ + spawnWave() { + this.currentWave++; + + const raiderCount = 3 + (this.raidDifficulty * 2); // 5-13 raiders + const waveRaiders = []; + + // Spawn raiders + for (let i = 0; i < raiderCount; i++) { + const spawnPoint = Phaser.Utils.Array.GetRandom(this.spawnPoints); + const raider = this.spawnRaider(spawnPoint.x, spawnPoint.y); + waveRaiders.push(raider); + } + + this.activeRaiders.push(...waveRaiders); + + // Notification + this.scene.uiSystem?.showNotification( + `Wave ${this.currentWave}/${this.totalWaves}: ${raiderCount} raiders!`, + 'warning' + ); + + console.log(`🔥 Wave ${this.currentWave}: Spawned ${raiderCount} raiders`); + + // Check for next wave + this.scene.time.delayedCall(5000, () => this.checkWaveProgress()); + } + + /** + * Spawn individual raider + */ + spawnRaider(x, y) { + // Create raider sprite + const raider = this.scene.physics.add.sprite(x, y, 'nomad_raider'); + + // Stats based on difficulty + raider.maxHealth = 50 + (this.raidDifficulty * 20); + raider.health = raider.maxHealth; + raider.attackDamage = 10 + (this.raidDifficulty * 5); + raider.active = true; + + // Add AI + raider.ai = new NomadRaiderAI(this.scene, raider); + + // Collisions + this.scene.physics.add.collider(raider, this.scene.player); + this.scene.physics.add.collider(raider, this.scene.zombieScout); + + // Health bar + raider.healthBar = this.scene.add.graphics(); + + // Death handler + raider.takeDamage = (amount) => this.raiderTakeDamage(raider, amount); + + return raider; + } + + /** + * Raider takes damage + */ + raiderTakeDamage(raider, amount) { + raider.health -= amount; + + // Update health bar + this.updateRaiderHealthBar(raider); + + // VFX + raider.setTint(0xff0000); + this.scene.time.delayedCall(100, () => raider.clearTint()); + + // Death + if (raider.health <= 0) { + this.raiderDeath(raider); + } + } + + /** + * Raider death + */ + raiderDeath(raider) { + raider.active = false; + + // Drop loot + this.dropRaiderLoot(raider.x, raider.y); + + // VFX + this.scene.vfxSystem?.playEffect('raider_death', raider.x, raider.y); + + // Remove from active list + const index = this.activeRaiders.indexOf(raider); + if (index > -1) { + this.activeRaiders.splice(index, 1); + } + + // Cleanup + raider.ai?.destroy(); + raider.healthBar?.destroy(); + raider.destroy(); + + // Check if wave complete + this.checkWaveProgress(); + } + + /** + * Update raider health bar + */ + updateRaiderHealthBar(raider) { + if (!raider.healthBar) return; + + raider.healthBar.clear(); + + const barWidth = 40; + const barHeight = 4; + const healthPercent = raider.health / raider.maxHealth; + + // Background + raider.healthBar.fillStyle(0x000000); + raider.healthBar.fillRect(raider.x - barWidth / 2, raider.y - 30, barWidth, barHeight); + + // Health + raider.healthBar.fillStyle(0xff0000); + raider.healthBar.fillRect(raider.x - barWidth / 2, raider.y - 30, barWidth * healthPercent, barHeight); + } + + /** + * Drop loot from raider + */ + dropRaiderLoot(x, y) { + const lootTable = ['wood', 'stone', 'metal', 'raider_weapon', 'medicine']; + const drop = Phaser.Utils.Array.GetRandom(lootTable); + const amount = Phaser.Math.Between(1, 5); + + // Create loot drop + this.scene.lootSystem?.createDrop(x, y, drop, amount); + } + + /** + * Check if wave is complete + */ + checkWaveProgress() { + if (!this.isRaidActive) return; + + const aliveRaiders = this.activeRaiders.filter(r => r.active); + + if (aliveRaiders.length === 0) { + // Wave complete + if (this.currentWave < this.totalWaves) { + // Next wave + this.scene.uiSystem?.showNotification( + `Wave ${this.currentWave} cleared! Next wave incoming...`, + 'success' + ); + + this.scene.time.delayedCall(5000, () => this.spawnWave()); + } else { + // Raid complete + this.endRaid(true); + } + } + } + + /** + * End raid (success or failure) + */ + endRaid(success) { + this.isRaidActive = false; + + if (success) { + // Victory! + const rewards = this.defenseRewards.get(this.raidDifficulty) || this.defenseRewards.get(1); + + this.scene.uiSystem?.showNotification( + `RAID DEFENDED! +${rewards.xp} XP, +${rewards.currency} coins`, + 'victory', + { priority: 'high' } + ); + + // Grant rewards + this.scene.zombieScoutLeveling?.awardXP(rewards.xp, 'raid_defense'); + this.scene.economySystem?.addCurrency(rewards.currency); + + rewards.items.forEach(item => { + this.scene.inventorySystem?.addItem(item, 1); + }); + + // VFX + this.scene.vfxSystem?.playEffect('victory', 400, 300); + this.scene.cameras.main.flash(1000, 0, 255, 0); + + console.log(`✅ RAID DEFENDED! Rewards granted.`); + } else { + // Defeat + this.scene.uiSystem?.showNotification( + 'RAID DEFEATED YOU! Farm damaged.', + 'defeat', + { priority: 'high' } + ); + + // Damage farm buildings + this.damageFarm(); + + console.log(`❌ RAID FAILED! Farm damaged.`); + } + + // Cleanup remaining raiders + this.activeRaiders.forEach(raider => { + raider.ai?.destroy(); + raider.healthBar?.destroy(); + raider.destroy(); + }); + this.activeRaiders = []; + } + + /** + * Damage farm on raid failure + */ + damageFarm() { + // Damage random buildings + const buildings = this.scene.buildingSystem?.getAllBuildings() || []; + const damagedCount = Math.min(3, buildings.length); + + for (let i = 0; i < damagedCount; i++) { + const building = Phaser.Utils.Array.GetRandom(buildings); + building.takeDamage?.(50); + } + + // Destroy random crops + const crops = this.scene.crops || []; + const destroyedCrops = Math.min(10, crops.length); + + for (let i = 0; i < destroyedCrops; i++) { + const crop = Phaser.Utils.Array.GetRandom(crops); + crop.destroy?.(); + } + } + + /** + * Manual raid trigger (for testing/quests) + */ + triggerRaid(difficulty = 1) { + this.startRaid(difficulty); + } + + /** + * Get raid state + */ + getRaidState() { + return { + isActive: this.isRaidActive, + currentWave: this.currentWave, + totalWaves: this.totalWaves, + difficulty: this.raidDifficulty, + activeRaiders: this.activeRaiders.filter(r => r.active).length + }; + } +} diff --git a/src/systems/NomadRaiderAI.js b/src/systems/NomadRaiderAI.js new file mode 100644 index 000000000..be48f7780 --- /dev/null +++ b/src/systems/NomadRaiderAI.js @@ -0,0 +1,348 @@ +/** + * NOMAD RAIDER AI SYSTEM + * Enemy AI for raiding bandits + * Pathfinding, combat, loot stealing + */ + +export class NomadRaiderAI { + constructor(scene, raider) { + this.scene = scene; + this.raider = raider; + + // AI state + this.state = 'idle'; // idle, patrol, attack, steal, flee + this.target = null; + this.homePosition = { x: raider.x, y: raider.y }; + + // AI parameters + this.detectionRange = 200; + this.attackRange = 40; + this.fleeHealthThreshold = 0.3; // Flee at 30% HP + + // Behavior timers + this.stateTimer = 0; + this.decisionInterval = 1000; // Make decision every 1s + + // Loot targeting + this.targetedLoot = null; + + this.init(); + } + + init() { + // Start AI loop + this.aiLoop = this.scene.time.addEvent({ + delay: this.decisionInterval, + callback: () => this.makeDecision(), + loop: true + }); + } + + /** + * Main AI decision-making + */ + makeDecision() { + if (!this.raider.active) return; + + // Check health for flee condition + const healthPercent = this.raider.health / this.raider.maxHealth; + if (healthPercent < this.fleeHealthThreshold && this.state !== 'flee') { + this.setState('flee'); + return; + } + + // State machine + switch (this.state) { + case 'idle': + this.idleBehavior(); + break; + case 'patrol': + this.patrolBehavior(); + break; + case 'attack': + this.attackBehavior(); + break; + case 'steal': + this.stealBehavior(); + break; + case 'flee': + this.fleeBehavior(); + break; + } + } + + /** + * STATE: Idle - Look for targets + */ + idleBehavior() { + // Check for player + const player = this.scene.player; + if (this.isInRange(player, this.detectionRange)) { + this.target = player; + this.setState('attack'); + return; + } + + // Check for Zombie Scout + const scout = this.scene.zombieScout; + if (scout && this.isInRange(scout, this.detectionRange)) { + this.target = scout; + this.setState('attack'); + return; + } + + // Check for stealable loot (crops, chests) + const loot = this.findNearestLoot(); + if (loot) { + this.targetedLoot = loot; + this.setState('steal'); + return; + } + + // Default: patrol + this.setState('patrol'); + } + + /** + * STATE: Patrol - Wander around + */ + patrolBehavior() { + // Random wandering + if (this.stateTimer <= 0) { + const randomX = this.homePosition.x + Phaser.Math.Between(-150, 150); + const randomY = this.homePosition.y + Phaser.Math.Between(-150, 150); + + this.moveToward(randomX, randomY); + this.stateTimer = Phaser.Math.Between(2000, 4000); // Patrol for 2-4s + } + + this.stateTimer -= this.decisionInterval; + + // Check for threats while patrolling + if (this.detectThreat()) { + this.setState('attack'); + } + } + + /** + * STATE: Attack - Combat with player/scout + */ + attackBehavior() { + if (!this.target || !this.target.active) { + this.setState('idle'); + return; + } + + const distance = Phaser.Math.Distance.Between( + this.raider.x, this.raider.y, + this.target.x, this.target.y + ); + + // Move toward target + if (distance > this.attackRange) { + this.moveToward(this.target.x, this.target.y); + } else { + // In range: attack + this.performAttack(); + } + + // Lost target + if (distance > this.detectionRange * 1.5) { + this.target = null; + this.setState('idle'); + } + } + + /** + * STATE: Steal - Take loot and escape + */ + stealBehavior() { + if (!this.targetedLoot) { + this.setState('idle'); + return; + } + + const distance = Phaser.Math.Distance.Between( + this.raider.x, this.raider.y, + this.targetedLoot.x, this.targetedLoot.y + ); + + if (distance > 30) { + // Move toward loot + this.moveToward(this.targetedLoot.x, this.targetedLoot.y); + } else { + // Steal loot + this.stealLoot(this.targetedLoot); + this.targetedLoot = null; + this.setState('flee'); // Escape after stealing + } + } + + /** + * STATE: Flee - Escape when low health or after stealing + */ + fleeBehavior() { + // Run away from player + const player = this.scene.player; + if (player) { + const angle = Phaser.Math.Angle.Between( + player.x, player.y, + this.raider.x, this.raider.y + ); + + const fleeX = this.raider.x + Math.cos(angle) * 200; + const fleeY = this.raider.y + Math.sin(angle) * 200; + + this.moveToward(fleeX, fleeY); + } + + // Check if safe to stop fleeing + const distanceFromPlayer = Phaser.Math.Distance.Between( + this.raider.x, this.raider.y, + player.x, player.y + ); + + if (distanceFromPlayer > this.detectionRange * 2) { + // Safe: despawn or return to base + this.setState('idle'); + } + } + + /** + * Change AI state + */ + setState(newState) { + console.log(`Raider AI: ${this.state} → ${newState}`); + this.state = newState; + this.stateTimer = 0; + + // State entry actions + switch (newState) { + case 'attack': + this.raider.setTint(0xff0000); // Red tint when aggressive + break; + case 'flee': + this.raider.setTint(0xffff00); // Yellow when fleeing + break; + default: + this.raider.clearTint(); + } + } + + /** + * Movement + */ + moveToward(targetX, targetY) { + const angle = Phaser.Math.Angle.Between( + this.raider.x, this.raider.y, + targetX, targetY + ); + + const speed = this.state === 'flee' ? 150 : 100; // Faster when fleeing + + this.raider.setVelocity( + Math.cos(angle) * speed, + Math.sin(angle) * speed + ); + + // Flip sprite + this.raider.flipX = targetX < this.raider.x; + } + + /** + * Attack execution + */ + performAttack() { + if (!this.target) return; + + // Attack cooldown check + const now = Date.now(); + if (this.raider.lastAttack && now - this.raider.lastAttack < 1500) return; + + this.raider.lastAttack = now; + + // Deal damage + this.target.takeDamage?.(this.raider.attackDamage || 10); + + // VFX + this.scene.vfxSystem?.playEffect('raider_attack', this.target.x, this.target.y); + this.scene.soundSystem?.play('raider_hit'); + + console.log(`Raider attacked ${this.target.name || 'target'} for ${this.raider.attackDamage || 10} damage`); + } + + /** + * Loot stealing + */ + stealLoot(loot) { + // Determine loot value + const stolenValue = loot.value || Phaser.Math.Between(50, 200); + + // Remove loot from world + loot.destroy?.() || loot.setVisible(false); + + // Notification + this.scene.uiSystem?.showNotification( + `Raider stole ${stolenValue} worth of loot!`, + 'warning' + ); + + // VFX + this.scene.vfxSystem?.playEffect('loot_stolen', loot.x, loot.y); + + console.log(`Raider stole loot worth ${stolenValue}`); + } + + /** + * Detection helpers + */ + isInRange(target, range) { + if (!target) return false; + + const distance = Phaser.Math.Distance.Between( + this.raider.x, this.raider.y, + target.x, target.y + ); + + return distance <= range; + } + + detectThreat() { + const player = this.scene.player; + const scout = this.scene.zombieScout; + + return this.isInRange(player, this.detectionRange) || + this.isInRange(scout, this.detectionRange); + } + + findNearestLoot() { + // Find crops or storage + const lootables = this.scene.crops?.filter(c => c.isHarvestable) || []; + + let nearest = null; + let minDist = 300; // Max search range + + lootables.forEach(loot => { + const dist = Phaser.Math.Distance.Between( + this.raider.x, this.raider.y, + loot.x, loot.y + ); + + if (dist < minDist) { + minDist = dist; + nearest = loot; + } + }); + + return nearest; + } + + /** + * Cleanup + */ + destroy() { + if (this.aiLoop) { + this.aiLoop.remove(); + } + } +} diff --git a/src/systems/ZombieScoutSkills.js b/src/systems/ZombieScoutSkills.js new file mode 100644 index 000000000..19f7fe209 --- /dev/null +++ b/src/systems/ZombieScoutSkills.js @@ -0,0 +1,420 @@ +/** + * ZOMBIE SCOUT SKILLS SYSTEM + * Skill tree with active/passive abilities + * Integrates with ZombieScoutLevelingSystem + */ + +export class ZombieScoutSkills { + constructor(scene) { + this.scene = scene; + + // Skill trees + this.skills = new Map(); + this.unlockedSkills = new Set(); + this.activeSkills = new Map(); // Currently equipped active skills + + // Skill categories + this.categories = ['digging', 'combat', 'utility']; + + this.init(); + } + + init() { + this.initializeSkillTree(); + } + + /** + * Initialize all available skills + */ + initializeSkillTree() { + // DIGGING SKILLS + this.registerSkill({ + id: 'speed_dig', + name: 'Speed Dig', + category: 'digging', + type: 'active', + unlockLevel: 10, + cooldown: 30000, // 30 seconds + duration: 10000, // 10 seconds + description: 'Dig 50% faster for 10 seconds', + effect: () => this.activateSpeedDig() + }); + + this.registerSkill({ + id: 'deep_scan', + name: 'Deep Scan', + category: 'digging', + type: 'passive', + unlockLevel: 5, + description: 'Reveal buried items within 5 tiles', + effect: () => this.enableDeepScan() + }); + + this.registerSkill({ + id: 'treasure_sense', + name: 'Treasure Sense', + category: 'digging', + type: 'active', + unlockLevel: 15, + cooldown: 60000, // 60 seconds + description: 'Reveal all buried treasure on screen', + effect: () => this.activateTreasureSense() + }); + + // COMBAT SKILLS + this.registerSkill({ + id: 'basic_attack', + name: 'Claw Swipe', + category: 'combat', + type: 'active', + unlockLevel: 5, + cooldown: 2000, // 2 seconds + damage: 10, + description: 'Basic melee attack dealing 10 damage', + effect: () => this.performBasicAttack() + }); + + this.registerSkill({ + id: 'leap_attack', + name: 'Leap Attack', + category: 'combat', + type: 'active', + unlockLevel: 12, + cooldown: 8000, // 8 seconds + damage: 25, + description: 'Leap at enemy dealing 25 damage', + effect: () => this.performLeapAttack() + }); + + this.registerSkill({ + id: 'tank_stance', + name: 'Undead Resilience', + category: 'combat', + type: 'passive', + unlockLevel: 8, + description: '+20% damage resistance', + effect: () => this.enableTankStance() + }); + + // UTILITY SKILLS + this.registerSkill({ + id: 'scout_speed', + name: 'Scout Sprint', + category: 'utility', + type: 'active', + unlockLevel: 7, + cooldown: 20000, // 20 seconds + duration: 5000, // 5 seconds + description: 'Move 100% faster for 5 seconds', + effect: () => this.activateScoutSpeed() + }); + + this.registerSkill({ + id: 'pack_mule', + name: 'Pack Mule', + category: 'utility', + type: 'passive', + unlockLevel: 6, + description: '+10 inventory slots', + effect: () => this.enablePackMule() + }); + + this.registerSkill({ + id: 'night_vision', + name: 'Night Vision', + category: 'utility', + type: 'passive', + unlockLevel: 10, + description: 'See in darkness without penalty', + effect: () => this.enableNightVision() + }); + } + + /** + * Register a skill in the system + */ + registerSkill(skillData) { + this.skills.set(skillData.id, { + ...skillData, + unlocked: false, + active: false, + lastUsed: 0 + }); + } + + /** + * Unlock skill (called when level requirement met) + */ + unlockSkill(skillId) { + const skill = this.skills.get(skillId); + if (!skill) return false; + + const scoutLevel = this.scene.zombieScoutLeveling?.currentLevel || 1; + + // Check level requirement + if (scoutLevel < skill.unlockLevel) { + console.warn(`Scout level ${scoutLevel} too low for ${skill.name} (requires ${skill.unlockLevel})`); + return false; + } + + skill.unlocked = true; + this.unlockedSkills.add(skillId); + + // Auto-activate passive skills + if (skill.type === 'passive') { + skill.effect(); + } + + // Notification + this.scene.uiSystem?.showNotification( + `Skill Unlocked: ${skill.name}!`, + 'skill_unlock', + { description: skill.description } + ); + + console.log(`✨ Unlocked skill: ${skill.name}`); + return true; + } + + /** + * Use active skill + */ + useSkill(skillId) { + const skill = this.skills.get(skillId); + if (!skill || !skill.unlocked || skill.type !== 'active') return false; + + // Check cooldown + const now = Date.now(); + const timeSinceLastUse = now - skill.lastUsed; + + if (timeSinceLastUse < skill.cooldown) { + const remainingCooldown = Math.ceil((skill.cooldown - timeSinceLastUse) / 1000); + console.log(`Skill on cooldown: ${remainingCooldown}s remaining`); + return false; + } + + // Activate skill + skill.lastUsed = now; + skill.effect(); + + // UI feedback + this.scene.uiSystem?.showSkillActivation(skill.name, skill.cooldown); + + console.log(`🔥 Activated: ${skill.name}`); + return true; + } + + /** + * SKILL EFFECTS + */ + + activateSpeedDig() { + const scout = this.scene.zombieScout; + if (!scout) return; + + scout.digSpeed *= 1.5; // 50% faster + + // VFX + this.scene.vfxSystem?.playEffect('speed_buff', scout.x, scout.y); + + // Restore after duration + this.scene.time.delayedCall(10000, () => { + scout.digSpeed /= 1.5; + console.log('Speed Dig expired'); + }); + } + + enableDeepScan() { + // Enable passive treasure detection + this.scene.gameState.buffs.treasure_detection_range = 5; + console.log('Deep Scan enabled: 5 tile range'); + } + + activateTreasureSense() { + // Reveal all buried items on screen + this.scene.treasureSystem?.revealAllTreasures(); + + // VFX: Screen pulse + this.scene.cameras.main.flash(500, 255, 215, 0, true); + + console.log('Treasure Sense activated: All treasures revealed'); + } + + performBasicAttack() { + const scout = this.scene.zombieScout; + if (!scout) return; + + // Find nearest enemy + const enemy = this.findNearestEnemy(scout, 50); + if (!enemy) { + console.log('No enemy in range'); + return; + } + + // Deal damage + enemy.takeDamage(10); + + // VFX + this.scene.vfxSystem?.playEffect('claw_swipe', enemy.x, enemy.y); + this.scene.soundSystem?.play('zombie_attack'); + + console.log('Basic attack: 10 damage'); + } + + performLeapAttack() { + const scout = this.scene.zombieScout; + if (!scout) return; + + // Find nearest enemy + const enemy = this.findNearestEnemy(scout, 150); + if (!enemy) return; + + // Leap animation + this.scene.tweens.add({ + targets: scout, + x: enemy.x, + y: enemy.y - 20, + duration: 300, + ease: 'Quad.easeOut', + onComplete: () => { + // Deal damage on landing + enemy.takeDamage(25); + this.scene.vfxSystem?.playEffect('impact', enemy.x, enemy.y); + this.scene.cameras.main.shake(200, 0.008); + } + }); + + console.log('Leap attack: 25 damage'); + } + + enableTankStance() { + const scout = this.scene.zombieScout; + if (scout) { + scout.damageResistance = (scout.damageResistance || 0) + 0.2; // +20% + console.log('Undead Resilience enabled: +20% damage resistance'); + } + } + + activateScoutSpeed() { + const scout = this.scene.zombieScout; + if (!scout) return; + + scout.moveSpeed *= 2; // 100% faster + + // VFX: Speed trails + this.scene.vfxSystem?.playEffect('speed_trail', scout.x, scout.y, { duration: 5000 }); + + // Restore after duration + this.scene.time.delayedCall(5000, () => { + scout.moveSpeed /= 2; + console.log('Scout Sprint expired'); + }); + } + + enablePackMule() { + this.scene.inventorySystem?.addInventorySlots(10); + console.log('Pack Mule enabled: +10 inventory slots'); + } + + enableNightVision() { + this.scene.gameState.buffs.night_vision = true; + console.log('Night Vision enabled: Full visibility at night'); + } + + /** + * Helper: Find nearest enemy + */ + findNearestEnemy(from, maxDistance) { + let nearest = null; + let minDist = maxDistance; + + // Search for enemies in scene + const enemies = this.scene.enemies || []; + + enemies.forEach(enemy => { + const dist = Phaser.Math.Distance.Between(from.x, from.y, enemy.x, enemy.y); + if (dist < minDist) { + minDist = dist; + nearest = enemy; + } + }); + + return nearest; + } + + /** + * Get unlocked skills by category + */ + getSkillsByCategory(category) { + return Array.from(this.skills.values()) + .filter(s => s.category === category && s.unlocked); + } + + /** + * Get all unlocked skills + */ + getUnlockedSkills() { + return Array.from(this.skills.values()).filter(s => s.unlocked); + } + + /** + * Check skill availability + */ + isSkillAvailable(skillId) { + const skill = this.skills.get(skillId); + if (!skill || !skill.unlocked) return false; + + if (skill.type === 'passive') return true; + + const now = Date.now(); + return (now - skill.lastUsed) >= skill.cooldown; + } + + /** + * Get skill cooldown remaining + */ + getCooldownRemaining(skillId) { + const skill = this.skills.get(skillId); + if (!skill) return 0; + + const now = Date.now(); + const elapsed = now - skill.lastUsed; + const remaining = Math.max(0, skill.cooldown - elapsed); + + return Math.ceil(remaining / 1000); // Return in seconds + } + + /** + * Save/Load + */ + getSaveData() { + return { + unlockedSkills: Array.from(this.unlockedSkills), + skillCooldowns: Array.from(this.skills.entries()).map(([id, skill]) => ({ + id, + lastUsed: skill.lastUsed + })) + }; + } + + loadSaveData(data) { + this.unlockedSkills = new Set(data.unlockedSkills || []); + + // Restore cooldowns + data.skillCooldowns?.forEach(({ id, lastUsed }) => { + const skill = this.skills.get(id); + if (skill) { + skill.lastUsed = lastUsed; + skill.unlocked = this.unlockedSkills.has(id); + } + }); + + // Re-apply passive skills + this.unlockedSkills.forEach(skillId => { + const skill = this.skills.get(skillId); + if (skill && skill.type === 'passive') { + skill.effect(); + } + }); + } +}