Files
novafarma/src/systems/NomadRaiderAI.js

349 lines
8.9 KiB
JavaScript

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