diff --git a/ANIMAL_SOUND_MANIFEST.md b/ANIMAL_SOUND_MANIFEST.md new file mode 100644 index 000000000..d1f4dfe34 --- /dev/null +++ b/ANIMAL_SOUND_MANIFEST.md @@ -0,0 +1,158 @@ +# ๐Ÿ”Š **ANIMAL & NPC SOUND MANIFEST** +**Krvava ลฝetev - Gothic Farm Game** + +**Source:** Kenney Sound Packs (already downloaded) +**Location:** `/assets/audio/sfx/` + +--- + +## ๐Ÿฎ **FARM ANIMAL SOUNDS** + +### **Required Sounds (23 total):** + +**COW:** +- `cow_idle.wav` - Low moo, ambient +- `cow_flee.wav` - Alarmed moo, louder +- `cow_footstep.wav` - Heavy hoof step + +**PIG:** +- `pig_idle.wav` - Snort, oink +- `pig_flee.wav` - Squealing +- `pig_footstep.wav` - Medium step + +**SHEEP:** +- `sheep_idle.wav` - Baa sound +- `sheep_flee.wav` - Panicked baa +- `sheep_footstep.wav` - Light hoof step + +**CHICKEN:** +- `chicken_idle.wav` - Cluck +- `chicken_flee.wav` - Fast clucking/cackle +- `chicken_footstep.wav` - Light scratch + +**DUCK:** +- `duck_idle.wav` - Quack +- `duck_flee.wav` - Fast quacking +- `duck_footstep.wav` - Paddle sound + +**GOAT:** +- `goat_idle.wav` - Maa/bleat +- `goat_flee.wav` - Loud bleat +- `goat_footstep.wav` - Hoof clatter + +**HORSE:** +- `horse_idle.wav` - Neigh/whinny +- `horse_flee.wav` - Gallop neigh +- `horse_footstep.wav` - Heavy gallop + +**RABBIT:** +- `rabbit_idle.wav` - Light chitter +- `rabbit_flee.wav` - Quick squeak +- `rabbit_footstep.wav` - Soft hop + +--- + +## ๐Ÿ‘ค **NPC AMBIENT SOUNDS** + +**GRONK (Shop):** +- `gronk_idle_breath.wav` - Heavy breathing +- `gronk_coins.wav` - Coin counting +- `gronk_grumble.wav` - Low grumble + +**GENERIC NPC:** +- `npc_footstep_dirt.wav` - Walking on dirt +- `npc_footstep_cobble.wav` - Walking on cobblestone +- `npc_clothing_rustle.wav` - Moving + +--- + +## ๐ŸŒพ **ENVIRONMENTAL SOUNDS (Related)** + +**MUD & TERRAIN:** +- `footstep_mud_1.wav` - Squelch +- `footstep_mud_2.wav` - Squelch variant +- `footstep_grass.wav` - Soft grass step +- `footstep_cobble.wav` - Hard stone step + +**FARM AMBIENCE:** +- `farm_wind.wav` - Light wind +- `farm_distant_animals.wav` - Mixed distant sounds +- `gate_creak.wav` - Gate opening/closing +- `chain_rattle.wav` - Metal chains (locked gate) + +--- + +## ๐Ÿ“‚ **KENNEY PACK MAPPING** + +**Where to find these in your Kenney downloads:** + +**Animal Sounds:** `Kenney/Voice-Over Pack/` +- Look for: animal, creature sounds +- Variants: low, high pitch versions + +**Footsteps:** `Kenney/Impact Sounds/` +- Look for: step, walk, shuffle +- Different surfaces: grass, stone, mud + +**Ambient:** `Kenney/RPG Audio/` +- Environment loops +- Object interactions + +**UI/Interact:** `Kenney/Interface Sounds/` +- For shop interactions + +--- + +## ๐ŸŽฎ **IMPLEMENTATION CHECKLIST** + +**Phase 1 - Priority (10 sounds):** +- [ ] `cow_idle.wav` +- [ ] `cow_flee.wav` +- [ ] `pig_idle.wav` +- [ ] `sheep_idle.wav` +- [ ] `chicken_idle.wav` +- [ ] `horse_idle.wav` +- [ ] `footstep_mud_1.wav` +- [ ] `footstep_grass.wav` +- [ ] `npc_footstep_dirt.wav` +- [ ] `gronk_grumble.wav` + +**Phase 2 - Full Set (All 40+ sounds)** + +--- + +## ๐Ÿ”ง **PROCESSING NOTES** + +**Format:** Convert all to `.wav` or `.ogg` +**Sample Rate:** 44.1kHz +**Channels:** Mono (stereo for ambience) +**Normalization:** -3dB peak + +**Volume Levels:** +- Idle sounds: 70% +- Flee sounds: 90% +- Footsteps: 50% +- Ambience: 40% + +--- + +## ๐Ÿ’ก **NOIR SOUND DESIGN TWIST** + +**For glowing-eye effect in darkness:** + +When animal is in shadow/distant: +1. Mute normal animal sounds +2. Play low-pass filtered version (muffled) +3. Add eerie reverb/echo +4. Optional: Add faint heartbeat or breathing + +**When Kai's flashlight shines on them:** +1. Sharp sound cue (surprise!) +2. Immediate flee sound trigger +3. Rapid footstep sequence + +This makes the "glowing eyes in forest" moment EXTRA creepy! ๐Ÿ‘€โœจ + +--- + +**Start with Phase 1 (10 sounds), test, then expand!** diff --git a/DEMO_TASKS.md b/DEMO_TASKS.md index 474849c27..5a8ff70ae 100644 --- a/DEMO_TASKS.md +++ b/DEMO_TASKS.md @@ -1,10 +1,10 @@ # ๐Ÿ“‹ **DEMO PRODUCTION TASKS - COMPLETE CHECKLIST** **Created:** Jan 8, 2026 21:37 CET -**Updated:** Jan 8, 2026 23:21 CET +**Updated:** Jan 8, 2026 23:31 CET **Purpose:** ALL remaining demo assets as tasks -**Target:** 60 sprites total (5 house + 7 barn + 48 remaining) -**Status:** 40/60 complete (67%) โœจ +**Target:** 60 sprites total +**Status:** 56/60 complete (93%) ๐ŸŽ‰ --- diff --git a/src/ai/AnimalBehavior.js b/src/ai/AnimalBehavior.js new file mode 100644 index 000000000..b676a2745 --- /dev/null +++ b/src/ai/AnimalBehavior.js @@ -0,0 +1,278 @@ +/** + * ๐Ÿฎ 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(); + } + } +} diff --git a/src/ai/NPCIdleBehavior.js b/src/ai/NPCIdleBehavior.js new file mode 100644 index 000000000..e3c988e33 --- /dev/null +++ b/src/ai/NPCIdleBehavior.js @@ -0,0 +1,153 @@ +/** + * ๐Ÿ‘ค NPC IDLE BEHAVIOR SYSTEM + * "Krvava ลฝetev" - Gothic Farm Game + * + * Makes NPCs feel alive with random idle animations: + * - Fix hat + * - Wipe sweat + * - Look around + * - Scratch head + * - Adjust clothes + */ + +export class NPCIdleBehavior { + constructor(scene, sprite, npcType) { + this.scene = scene; + this.sprite = sprite; + this.npcType = npcType; + + // Idle animation timing + this.idleTimer = 0; + this.nextIdleTime = Phaser.Math.Between(3000, 8000); + + // Available idle animations + this.idleAnimations = this.getIdleAnimations(); + this.currentIdle = null; + } + + getIdleAnimations() { + // Different NPCs have different idle behaviors + const genericIdles = [ + 'fix_hat', + 'wipe_sweat', + 'look_around', + 'scratch_head', + 'adjust_clothes', + 'yawn', + 'stretch' + ]; + + const npcSpecific = { + 'gronk': [...genericIdles, 'adjust_apron', 'count_coins', 'polish_item'], + 'farmer': [...genericIdles, 'lean_on_fence', 'check_watch', 'spit'], + 'merchant': [...genericIdles, 'fix_glasses', 'write_note', 'arrange_goods'], + 'default': genericIdles + }; + + return npcSpecific[this.npcType] || npcSpecific.default; + } + + update(time, delta) { + if (!this.sprite.active) return; + + this.idleTimer += delta; + + // Trigger random idle animation + if (this.idleTimer >= this.nextIdleTime && !this.currentIdle) { + this.playRandomIdle(); + this.idleTimer = 0; + this.nextIdleTime = Phaser.Math.Between(3000, 8000); + } + } + + playRandomIdle() { + const idle = Phaser.Utils.Array.GetRandom(this.idleAnimations); + this.currentIdle = idle; + + // Play animation if it exists + const animKey = `${this.npcType}_${idle}`; + + if (this.sprite.anims && this.sprite.anims.exists(animKey)) { + this.sprite.play(animKey); + + // Return to default idle after animation + this.sprite.on('animationcomplete', () => { + this.sprite.play(`${this.npcType}_idle`); + this.currentIdle = null; + }, this); + } else { + // Fallback: simple visual effects for idle + this.playFallbackIdle(idle); + } + } + + playFallbackIdle(idleType) { + // Create simple tween-based idle behaviors as fallback + switch (idleType) { + case 'fix_hat': + this.scene.tweens.add({ + targets: this.sprite, + angle: -5, + duration: 200, + yoyo: true, + onComplete: () => { + this.currentIdle = null; + } + }); + break; + + case 'wipe_sweat': + this.scene.tweens.add({ + targets: this.sprite, + scaleY: 0.98, + duration: 300, + yoyo: true, + onComplete: () => { + this.currentIdle = null; + } + }); + break; + + case 'look_around': + this.scene.tweens.add({ + targets: this.sprite, + scaleX: -Math.abs(this.sprite.scaleX), + duration: 400, + yoyo: true, + onComplete: () => { + this.currentIdle = null; + } + }); + break; + + case 'scratch_head': + this.scene.tweens.add({ + targets: this.sprite, + y: this.sprite.y - 5, + duration: 150, + yoyo: true, + repeat: 2, + onComplete: () => { + this.currentIdle = null; + } + }); + break; + + default: + // Generic bob + this.scene.tweens.add({ + targets: this.sprite, + y: this.sprite.y - 3, + duration: 200, + yoyo: true, + onComplete: () => { + this.currentIdle = null; + } + }); + } + } + + destroy() { + this.sprite = null; + } +}