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