// Crookfinger's voice: he "speaks" in a squeaky made-up imp language - short synthesised // syllables in the rhythm of the text (the text itself is shown as usual). use crate::audio; use crate::game::VOICE_SYLLABLES; pub struct Voice { queue: Vec<(f64, usize)>, // time, sound index busy_until: f64, } // which messages Crookfinger says out loud pub fn speaks(key: &str) -> bool { key.starts_with("hint.") || key.starts_with("msg.wave_warn") || matches!( key, "msg.payday_soon" | "msg.researched" | "msg.mission_start" | "msg.mission_won" | "msg.rune_chest" | "msg.trap_made" | "msg.revenant_rises" | "msg.altar_gift" | "msg.altar_blessing" | "msg.brawl" | "msg.alarm" | "msg.creature_freed" | "msg.spring_taken" ) } impl Voice { pub fn new() -> Voice { Voice { queue: Vec::new(), busy_until: 0.0 } } // babble a text: one syllable per vowel group, pauses at spaces and punctuation pub fn say(&mut self, text: &str, now: f64, max_syllables: usize) { if now < self.busy_until { return; } let mut t = now + 0.05; let mut n = 0; let mut h: u32 = 2166136261; let mut in_vowel = false; for ch in text.chars() { h = (h ^ ch as u32).wrapping_mul(16777619); let vowel = "aeiouäöüyAEIOUÄÖÜY".contains(ch); if vowel && !in_vowel { let v = VOICE_SYLLABLES[(h >> 7) as usize % VOICE_SYLLABLES.len()]; self.queue.push((t, v.index())); t += 0.085 + ((h >> 13) % 4) as f64 * 0.012; n += 1; if n >= max_syllables { break; } } in_vowel = vowel; match ch { ' ' => t += 0.035, ',' | ';' | '-' => t += 0.14, '.' | '!' | '?' => t += 0.28, _ => {} } } self.busy_until = t + 4.0; } pub fn update(&mut self, now: f64, volume: f32) { let due: Vec = self.queue.iter().filter(|q| q.0 <= now).map(|q| q.1).collect(); self.queue.retain(|q| q.0 > now); for i in due { audio::play_sfx(i, 0.75 * volume.max(0.0), 0.0); } } pub fn stop(&mut self) { self.queue.clear(); } }