/** * 🐮 ANIMAL AI BEHAVIOR SYSTEM * "Krvava Žetev" - Gothic Farm Game * * Behaviors: * - Wander: Random smooth movement (not grid-based) * - Flee: Run away from player when close * - Glowing Eyes: Neon eyes visible in darkness (noir effect!) * - Follow: Cargo animals (llama, horse) follow with delay */ export class AnimalBehavior { constructor(scene, sprite, animalType) { this.scene = scene; this.sprite = sprite; this.animalType = animalType; // Behavior states this.state = 'wander'; // 'wander', 'flee', 'follow', 'idle' this.wanderTimer = 0; this.wanderDuration = Phaser.Math.Between(2000, 5000); this.wanderAngle = Math.random() * Math.PI * 2; // Movement params this.speed = this.getSpeed(); this.fleeDistance = 100; // pixels this.fleeSpeed = this.speed * 2; // Glowing eyes effect (NOIR!) this.glowingEyes = null; this.eyeColor = this.getEyeColor(); this.createGlowingEyes(); // Cargo follow system (for llama, horse, donkey) this.isCargoAnimal = ['llama', 'horse', 'donkey'].includes(animalType); this.followTarget = null; this.followDelay = 0.5; // seconds this.followHistory = []; } getSpeed() { const speeds = { cow: 30, pig: 35, sheep: 25, chicken: 50, duck: 45, goat: 40, horse: 60, rabbit: 70, donkey: 35, llama: 40 }; return speeds[this.animalType] || 30; } getEyeColor() { // Noir glowing eyes - pink or green like Kai's dreads! const colors = { cow: 0xff0066, // neon pink pig: 0xff0066, // neon pink sheep: 0x00ff88, // neon green chicken: 0xffff00, // yellow duck: 0x00ffff, // cyan goat: 0xff0066, // neon pink horse: 0x00ff88, // neon green rabbit: 0xff00ff, // purple donkey: 0x00ff88, // neon green llama: 0xff0066 // neon pink }; return colors[this.animalType] || 0xff0066; } createGlowingEyes() { // Create glowing eye sprites for noir darkness effect this.glowingEyes = this.scene.add.container(this.sprite.x, this.sprite.y); // Two small glowing circles for eyes const leftEye = this.scene.add.circle(-8, -10, 3, this.eyeColor); const rightEye = this.scene.add.circle(8, -10, 3, this.eyeColor); // Add glow effect leftEye.setAlpha(0.8); rightEye.setAlpha(0.8); this.glowingEyes.add([leftEye, rightEye]); // Hide by default, show only in darkness or at distance this.glowingEyes.setVisible(false); this.glowingEyes.setDepth(this.sprite.depth + 1); } update(time, delta) { if (!this.sprite.active) return; // Update glowing eyes position if (this.glowingEyes) { this.glowingEyes.setPosition(this.sprite.x, this.sprite.y); this.updateGlowingEyesVisibility(); } // Behavior state machine switch (this.state) { case 'wander': this.updateWander(time, delta); break; case 'flee': this.updateFlee(time, delta); break; case 'follow': this.updateFollow(time, delta); break; case 'idle': this.updateIdle(time, delta); break; } // Check for player proximity (flee trigger) this.checkPlayerProximity(); } updateWander(time, delta) { this.wanderTimer += delta; // Change direction periodically if (this.wanderTimer >= this.wanderDuration) { this.wanderTimer = 0; this.wanderDuration = Phaser.Math.Between(2000, 5000); this.wanderAngle = Math.random() * Math.PI * 2; // Sometimes stop and idle if (Math.random() < 0.3) { this.state = 'idle'; this.sprite.setVelocity(0, 0); return; } } // Smooth movement in wander direction const vx = Math.cos(this.wanderAngle) * this.speed; const vy = Math.sin(this.wanderAngle) * this.speed; this.sprite.setVelocity(vx, vy); } updateFlee(time, delta) { const player = this.scene.player; if (!player) return; // Flee away from player const angle = Phaser.Math.Angle.Between( player.x, player.y, this.sprite.x, this.sprite.y ); const vx = Math.cos(angle) * this.fleeSpeed; const vy = Math.sin(angle) * this.fleeSpeed; this.sprite.setVelocity(vx, vy); // Return to wander if far enough const dist = Phaser.Math.Distance.Between( player.x, player.y, this.sprite.x, this.sprite.y ); if (dist > this.fleeDistance * 2) { this.state = 'wander'; } } updateFollow(time, delta) { if (!this.followTarget || !this.isCargoAnimal) return; // Record follow history for delayed follow this.followHistory.push({ x: this.followTarget.x, y: this.followTarget.y, time: time }); // Remove old history const cutoffTime = time - (this.followDelay * 1000); this.followHistory = this.followHistory.filter(h => h.time > cutoffTime); // Follow delayed position if (this.followHistory.length > 0) { const target = this.followHistory[0]; const angle = Phaser.Math.Angle.Between( this.sprite.x, this.sprite.y, target.x, target.y ); const dist = Phaser.Math.Distance.Between( this.sprite.x, this.sprite.y, target.x, target.y ); // Only move if far enough if (dist > 50) { const vx = Math.cos(angle) * this.speed; const vy = Math.sin(angle) * this.speed; this.sprite.setVelocity(vx, vy); } else { this.sprite.setVelocity(0, 0); } } } updateIdle(time, delta) { // Stop moving this.sprite.setVelocity(0, 0); // Return to wander after random time if (Math.random() < 0.01) { // ~1% chance per frame this.state = 'wander'; } } checkPlayerProximity() { const player = this.scene.player; if (!player) return; const dist = Phaser.Math.Distance.Between( player.x, player.y, this.sprite.x, this.sprite.y ); // Trigger flee if player too close if (dist < this.fleeDistance && this.state !== 'flee') { this.state = 'flee'; // Play flee sound if available if (this.scene.sound.get(`${this.animalType}_flee`)) { this.scene.sound.play(`${this.animalType}_flee`); } } } updateGlowingEyesVisibility() { if (!this.glowingEyes) return; const player = this.scene.player; if (!player) return; const dist = Phaser.Math.Distance.Between( player.x, player.y, this.sprite.x, this.sprite.y ); // Show glowing eyes in darkness or at distance // NOIR EFFECT: When Kai shines light in forest, just glowing eyes visible! const isDark = this.scene.darkness || false; // Check if darkness system active const isDistant = dist > 200 && dist < 400; // Medium distance if (isDark || isDistant) { this.glowingEyes.setVisible(true); // Hide actual sprite if far if (dist > 300) { this.sprite.setAlpha(0.2); } } else { this.glowingEyes.setVisible(false); this.sprite.setAlpha(1.0); } } setFollowTarget(target) { if (this.isCargoAnimal) { this.followTarget = target; this.state = 'follow'; } } destroy() { if (this.glowingEyes) { this.glowingEyes.destroy(); } } }