438 lines
13 KiB
JavaScript
438 lines
13 KiB
JavaScript
/**
|
|
* HIPOAudioSystem.js
|
|
*
|
|
* COMPLETE AUDIO MANAGER - Demo Ready!
|
|
*
|
|
* Features:
|
|
* - [AI_VOICE] detection in dialogue
|
|
* - Typewriter effect with character blips
|
|
* - Farm animal proximity sounds
|
|
* - Combat SFX integration
|
|
* - Noir City ambient (HIPODEVIL666CITY)
|
|
* - Xbox haptic feedback
|
|
*
|
|
* Created: Jan 10, 2026
|
|
* Author: David "HIPO" Kotnik
|
|
* Studio: Hipodevil666 Studios™
|
|
*/
|
|
|
|
export default class HIPOAudioSystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// Audio references
|
|
this.aiVoices = {};
|
|
this.sfx = {};
|
|
this.ambient = {};
|
|
this.blips = {};
|
|
|
|
// Dialogue state
|
|
this.currentDialogue = null;
|
|
this.typewriterActive = false;
|
|
|
|
// Farm animal timers
|
|
this.animalTimers = {};
|
|
|
|
// Haptic enabled
|
|
this.hapticEnabled = true;
|
|
|
|
console.log('🎙️ HIPO Audio System initialized!');
|
|
}
|
|
|
|
/**
|
|
* Preload all audio assets
|
|
*/
|
|
preloadAssets() {
|
|
const scene = this.scene;
|
|
|
|
console.log('🎵 Loading HIPO Audio System assets...');
|
|
|
|
// === AI VOICES (Edge-TTS Generated) ===
|
|
console.log(' 📂 AI Voices...');
|
|
|
|
// Gronk (Deep, Gritty Male)
|
|
for (let i = 1; i <= 8; i++) {
|
|
scene.load.audio(`gronk_voice_${i}`,
|
|
`assets/audio/voice/gronk/gronk_phrase_${String(i).padStart(2, '0')}.ogg`);
|
|
}
|
|
|
|
// Kai (Energetic, Bold - FEMALE!)
|
|
for (let i = 1; i <= 8; i++) {
|
|
scene.load.audio(`kai_voice_${i}`,
|
|
`assets/audio/voice/kai/kai_phrase_${String(i).padStart(2, '0')}.ogg`);
|
|
}
|
|
|
|
// Ana (Mysterious, Calm Female)
|
|
for (let i = 1; i <= 8; i++) {
|
|
scene.load.audio(`ana_voice_${i}`,
|
|
`assets/audio/voice/ana/ana_phrase_${String(i).padStart(2, '0')}.ogg`);
|
|
}
|
|
|
|
// === FARMING & NATURE SFX ===
|
|
console.log(' 🐄 Farming & Nature...');
|
|
scene.load.audio('sfx_sheep', 'assets/audio/sfx/farming/sheep.ogg');
|
|
scene.load.audio('sfx_pig', 'assets/audio/sfx/farming/pig.ogg');
|
|
scene.load.audio('sfx_chicken', 'assets/audio/sfx/farming/chicken.ogg');
|
|
scene.load.audio('sfx_horse', 'assets/audio/sfx/farming/horse.ogg');
|
|
scene.load.audio('sfx_goat', 'assets/audio/sfx/farming/goat.ogg');
|
|
scene.load.audio('sfx_cow', 'assets/audio/sfx/farming/cow.ogg');
|
|
|
|
// === COMBAT SFX ===
|
|
console.log(' ⚔️ Combat...');
|
|
scene.load.audio('sfx_zombie_hit', 'assets/audio/sfx/combat/zombie_hit.ogg');
|
|
scene.load.audio('sfx_zombie_death', 'assets/audio/sfx/combat/zombie_death.ogg');
|
|
scene.load.audio('sfx_player_hurt', 'assets/audio/sfx/combat/player_hurt.ogg');
|
|
|
|
// === AMBIENT ===
|
|
console.log(' 🌃 Ambient...');
|
|
scene.load.audio('ambient_noir_city', 'assets/audio/ambient/noir_city_echo.ogg');
|
|
scene.load.audio('ambient_farm_wind', 'assets/audio/ambient/wind_loop.ogg');
|
|
scene.load.audio('ambient_crickets', 'assets/audio/ambient/crickets_loop.ogg');
|
|
|
|
// === TYPEWRITER BLIPS (Character-specific) ===
|
|
console.log(' ⌨️ Typewriter blips...');
|
|
scene.load.audio('blip_gronk', 'assets/audio/ui/typewriter_low.ogg');
|
|
scene.load.audio('blip_kai', 'assets/audio/ui/typewriter_high.ogg');
|
|
scene.load.audio('blip_ana', 'assets/audio/ui/typewriter_mid.ogg');
|
|
scene.load.audio('blip_npc', 'assets/audio/ui/typewriter_normal.ogg');
|
|
|
|
console.log('✅ HIPO Audio System assets queued!');
|
|
}
|
|
|
|
/**
|
|
* Initialize audio after preload
|
|
*/
|
|
initialize() {
|
|
const scene = this.scene;
|
|
|
|
console.log('🎵 Initializing HIPO Audio System...');
|
|
|
|
// === AI VOICES ===
|
|
this.aiVoices = {
|
|
gronk: [],
|
|
kai: [],
|
|
ana: []
|
|
};
|
|
|
|
for (let i = 1; i <= 8; i++) {
|
|
this.aiVoices.gronk.push(scene.sound.add(`gronk_voice_${i}`, { volume: 0.8 }));
|
|
this.aiVoices.kai.push(scene.sound.add(`kai_voice_${i}`, { volume: 0.75 }));
|
|
this.aiVoices.ana.push(scene.sound.add(`ana_voice_${i}`, { volume: 0.7 }));
|
|
}
|
|
|
|
// === FARMING & NATURE ===
|
|
this.sfx.farming = {
|
|
sheep: scene.sound.add('sfx_sheep', { volume: 0.4 }),
|
|
pig: scene.sound.add('sfx_pig', { volume: 0.4 }),
|
|
chicken: scene.sound.add('sfx_chicken', { volume: 0.35 }),
|
|
horse: scene.sound.add('sfx_horse', { volume: 0.5 }),
|
|
goat: scene.sound.add('sfx_goat', { volume: 0.4 }),
|
|
cow: scene.sound.add('sfx_cow', { volume: 0.45 })
|
|
};
|
|
|
|
// === COMBAT ===
|
|
this.sfx.combat = {
|
|
zombieHit: scene.sound.add('sfx_zombie_hit', { volume: 0.6 }),
|
|
zombieDeath: scene.sound.add('sfx_zombie_death', { volume: 0.7 }),
|
|
playerHurt: scene.sound.add('sfx_player_hurt', { volume: 0.8 })
|
|
};
|
|
|
|
// === AMBIENT ===
|
|
this.ambient = {
|
|
noirCity: scene.sound.add('ambient_noir_city', {
|
|
loop: true,
|
|
volume: 0.2 // Noir city with echo
|
|
}),
|
|
farmWind: scene.sound.add('ambient_farm_wind', {
|
|
loop: true,
|
|
volume: 0.2
|
|
}),
|
|
crickets: scene.sound.add('ambient_crickets', {
|
|
loop: true,
|
|
volume: 0.25
|
|
})
|
|
};
|
|
|
|
// === TYPEWRITER BLIPS ===
|
|
this.blips = {
|
|
gronk: scene.sound.add('blip_gronk', { volume: 0.15 }),
|
|
kai: scene.sound.add('blip_kai', { volume: 0.13 }),
|
|
ana: scene.sound.add('blip_ana', { volume: 0.12 }),
|
|
npc: scene.sound.add('blip_npc', { volume: 0.1 })
|
|
};
|
|
|
|
console.log('✅ HIPO Audio System ready!');
|
|
}
|
|
|
|
/**
|
|
* Play dialogue with smart AI/Typewriter detection
|
|
*
|
|
* Format: "[AI_VOICE:character:phraseNumber] Text here"
|
|
* Example: "[AI_VOICE:gronk:2] Pink is best color!"
|
|
*
|
|
* OR standard: "Just normal text" (uses typewriter)
|
|
*/
|
|
playDialogue(text, character = 'npc', onComplete = null) {
|
|
// Check for [AI_VOICE] tag
|
|
const aiVoiceMatch = text.match(/\[AI_VOICE:(\w+):(\d+)\]\s*(.+)/);
|
|
|
|
if (aiVoiceMatch) {
|
|
// AI VOICE MODE
|
|
const voiceChar = aiVoiceMatch[1]; // gronk/kai/ana
|
|
const phraseNum = parseInt(aiVoiceMatch[2]);
|
|
const displayText = aiVoiceMatch[3];
|
|
|
|
console.log(`🎙️ AI VOICE: ${voiceChar} phrase ${phraseNum}`);
|
|
console.log(` Text: "${displayText}"`);
|
|
|
|
// Play AI voice
|
|
this.playAIVoice(voiceChar, phraseNum, onComplete);
|
|
|
|
// Show text instantly (no typewriter for AI voice)
|
|
this.showDialogueText(displayText, character, true);
|
|
|
|
// Light haptic for voice
|
|
this.vibrateLight();
|
|
|
|
} else {
|
|
// TYPEWRITER MODE
|
|
console.log(`⌨️ TYPEWRITER: ${character}`);
|
|
console.log(` Text: "${text}"`);
|
|
|
|
// Show text with typewriter effect
|
|
this.showDialogueText(text, character, false, onComplete);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Play AI voice
|
|
*/
|
|
playAIVoice(character, phraseNumber, onComplete = null) {
|
|
const voices = this.aiVoices[character];
|
|
|
|
if (!voices || !voices[phraseNumber - 1]) {
|
|
console.warn(`⚠️ AI Voice not found: ${character} phrase ${phraseNumber}`);
|
|
return;
|
|
}
|
|
|
|
const voice = voices[phraseNumber - 1];
|
|
voice.play();
|
|
|
|
if (onComplete) {
|
|
voice.once('complete', onComplete);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show dialogue text (with or without typewriter)
|
|
*/
|
|
showDialogueText(text, character, instant = false, onComplete = null) {
|
|
// This would integrate with your DialogueSystem
|
|
// For now, just log
|
|
console.log(` Display: "${text}" (instant: ${instant})`);
|
|
|
|
if (!instant) {
|
|
// Typewriter effect with character-specific blip
|
|
this.typewriterEffect(text, character, onComplete);
|
|
} else {
|
|
// Instant display
|
|
if (onComplete) onComplete();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Typewriter effect with blips
|
|
*/
|
|
typewriterEffect(text, character, onComplete) {
|
|
this.typewriterActive = true;
|
|
|
|
const blip = this.blips[character] || this.blips.npc;
|
|
const speed = 50; // ms per character
|
|
|
|
let index = 0;
|
|
|
|
const typeInterval = setInterval(() => {
|
|
if (index < text.length) {
|
|
const char = text[index];
|
|
|
|
// Play blip (skip spaces)
|
|
if (char !== ' ' && !blip.isPlaying) {
|
|
blip.play();
|
|
}
|
|
|
|
index++;
|
|
} else {
|
|
clearInterval(typeInterval);
|
|
this.typewriterActive = false;
|
|
if (onComplete) onComplete();
|
|
}
|
|
}, speed);
|
|
}
|
|
|
|
/**
|
|
* Start farm animal sounds (proximity-based random)
|
|
*/
|
|
startFarmAnimalSounds() {
|
|
const animals = ['sheep', 'pig', 'chicken', 'horse', 'goat', 'cow'];
|
|
|
|
animals.forEach(animal => {
|
|
this.animalTimers[animal] = this.scene.time.addEvent({
|
|
delay: Phaser.Math.Between(5000, 15000),
|
|
callback: () => {
|
|
this.playFarmAnimal(animal);
|
|
},
|
|
loop: true
|
|
});
|
|
});
|
|
|
|
console.log('🐄 Farm animal sounds started!');
|
|
}
|
|
|
|
/**
|
|
* Play farm animal sound (checks proximity to Kai)
|
|
*/
|
|
playFarmAnimal(animalType) {
|
|
const sound = this.sfx.farming[animalType];
|
|
|
|
if (!sound || sound.isPlaying) return;
|
|
|
|
// Check proximity to player (if player exists)
|
|
const player = this.scene.player || this.scene.kai;
|
|
|
|
if (player) {
|
|
// Simplified: just play (proximity would check farm location)
|
|
// In full game, check distance to farm area
|
|
const nearFarm = true; // TODO: Implement farm proximity
|
|
|
|
if (nearFarm) {
|
|
sound.play();
|
|
console.log(`🐄 ${animalType} sound!`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop farm animal sounds
|
|
*/
|
|
stopFarmAnimalSounds() {
|
|
Object.values(this.animalTimers).forEach(timer => {
|
|
if (timer) timer.remove();
|
|
});
|
|
this.animalTimers = {};
|
|
console.log('🔇 Farm animals stopped');
|
|
}
|
|
|
|
/**
|
|
* Play combat sound with haptic
|
|
*/
|
|
playCombat(type) {
|
|
const soundMap = {
|
|
'hit': 'zombieHit',
|
|
'death': 'zombieDeath',
|
|
'hurt': 'playerHurt'
|
|
};
|
|
|
|
const soundKey = soundMap[type];
|
|
const sound = this.sfx.combat[soundKey];
|
|
|
|
if (!sound) {
|
|
console.warn(`⚠️ Combat sound not found: ${type}`);
|
|
return;
|
|
}
|
|
|
|
sound.play();
|
|
|
|
// Haptic feedback
|
|
if (type === 'hurt') {
|
|
this.vibrateStrong(400); // Strong for player damage
|
|
} else {
|
|
this.vibrateStrong(200); // Normal for zombie
|
|
}
|
|
|
|
console.log(`⚔️ Combat: ${type}`);
|
|
}
|
|
|
|
/**
|
|
* Play ambient for location
|
|
*/
|
|
playAmbient(location) {
|
|
// Stop all ambient
|
|
Object.values(this.ambient).forEach(amb => amb.stop());
|
|
|
|
let ambient = null;
|
|
|
|
switch (location) {
|
|
case 'city':
|
|
case 'HIPODEVIL666CITY':
|
|
ambient = this.ambient.noirCity;
|
|
console.log('🌃 Noir City ambient (with echo)');
|
|
break;
|
|
case 'farm':
|
|
case 'grassland':
|
|
ambient = this.ambient.farmWind;
|
|
console.log('🌾 Farm ambient');
|
|
break;
|
|
case 'night':
|
|
ambient = this.ambient.crickets;
|
|
console.log('🌙 Night ambient');
|
|
break;
|
|
}
|
|
|
|
if (ambient) {
|
|
ambient.play();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop all ambient
|
|
*/
|
|
stopAmbient() {
|
|
Object.values(this.ambient).forEach(amb => amb.stop());
|
|
}
|
|
|
|
/**
|
|
* Light haptic feedback
|
|
*/
|
|
vibrateLight() {
|
|
this.vibrate(100, 0.3, 0.5);
|
|
}
|
|
|
|
/**
|
|
* Strong haptic feedback
|
|
*/
|
|
vibrateStrong(duration = 300) {
|
|
this.vibrate(duration, 0.7, 1.0);
|
|
}
|
|
|
|
/**
|
|
* Xbox controller vibration
|
|
*/
|
|
vibrate(duration, weak, strong) {
|
|
if (!this.hapticEnabled) return;
|
|
|
|
const scene = this.scene;
|
|
|
|
if (scene.input.gamepad && scene.input.gamepad.total > 0) {
|
|
const pad = scene.input.gamepad.getPad(0);
|
|
|
|
if (pad && pad.vibration) {
|
|
pad.vibration.playEffect('dual-rumble', {
|
|
startDelay: 0,
|
|
duration: duration,
|
|
weakMagnitude: weak,
|
|
strongMagnitude: strong
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup
|
|
*/
|
|
destroy() {
|
|
this.stopAmbient();
|
|
this.stopFarmAnimalSounds();
|
|
console.log('🔇 HIPO Audio System destroyed');
|
|
}
|
|
}
|