diff --git a/draugnorak.nvgt b/draugnorak.nvgt index 089f7c1..f375e81 100644 --- a/draugnorak.nvgt +++ b/draugnorak.nvgt @@ -16,6 +16,7 @@ sound_pool p(100); #include "src/base_system.nvgt" #include "src/time_system.nvgt" #include "src/audio_utils.nvgt" +#include "src/creature_audio.nvgt" #include "src/notify.nvgt" int run_main_menu() { diff --git a/src/combat.nvgt b/src/combat.nvgt index 5c01814..5e619d5 100644 --- a/src/combat.nvgt +++ b/src/combat.nvgt @@ -42,9 +42,9 @@ void perform_spear_attack(int current_x) { p.play_stationary("sounds/weapons/spear_hit.ogg", false); // Play hit sound based on enemy type (both use same hit sound for now) if (get_bandit_at(hit_pos) != null) { - play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", x, hit_pos, false, BANDIT_SOUND_VOLUME_STEP); + play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, BANDIT_SOUND_VOLUME_STEP); } else { - play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", x, hit_pos, false, ZOMBIE_SOUND_VOLUME_STEP); + play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, hit_pos, ZOMBIE_SOUND_VOLUME_STEP); } return; } @@ -60,9 +60,9 @@ void perform_axe_attack(int current_x) { p.play_stationary("sounds/weapons/axe_hit.ogg", false); // Play hit sound based on enemy type if (get_bandit_at(current_x) != null) { - play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", x, current_x, false, BANDIT_SOUND_VOLUME_STEP); + play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, current_x, BANDIT_SOUND_VOLUME_STEP); } else { - play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", x, current_x, false, ZOMBIE_SOUND_VOLUME_STEP); + play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, current_x, ZOMBIE_SOUND_VOLUME_STEP); } return; } @@ -180,10 +180,10 @@ void release_sling_attack(int player_x) { if (hit_bandit) { damage_bandit_at(target_x, damage); p.play_1d("sounds/weapons/sling_hit.ogg", player_x, target_x, false); - play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", player_x, target_x, false, BANDIT_SOUND_VOLUME_STEP); + play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", player_x, target_x, BANDIT_SOUND_VOLUME_STEP); } else { damage_zombie_at(target_x, damage); p.play_1d("sounds/weapons/sling_hit.ogg", player_x, target_x, false); - play_1d_with_volume_step("sounds/enemies/zombie_hit.ogg", player_x, target_x, false, ZOMBIE_SOUND_VOLUME_STEP); + play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", player_x, target_x, ZOMBIE_SOUND_VOLUME_STEP); } } diff --git a/src/constants.nvgt b/src/constants.nvgt index a6e758c..65a903b 100644 --- a/src/constants.nvgt +++ b/src/constants.nvgt @@ -107,6 +107,8 @@ const float FIREPIT_SOUND_VOLUME_STEP = 6.0; // 30 dB over 5 tiles const int STREAM_SOUND_RANGE = 7; const float STREAM_SOUND_VOLUME_STEP = 4.3; // 30 dB over 7 tiles +const float TREE_SOUND_VOLUME_STEP = 4.0; // Similar to snares for good audibility + // Mountain configuration const int MOUNTAIN_SIZE = 60; const int MOUNTAIN_MIN_ELEVATION = 0; diff --git a/src/creature_audio.nvgt b/src/creature_audio.nvgt new file mode 100644 index 0000000..24996fc --- /dev/null +++ b/src/creature_audio.nvgt @@ -0,0 +1,83 @@ +// Unified creature/enemy audio system +// Ensures consistent panning and distance behavior for all animated entities +// +// USAGE GUIDE: +// This system provides standardized audio functions for all creatures (zombies, bandits, animals). +// Using these functions ensures all creatures sound consistent to the player. +// +// When adding new creatures (sheep, cattle, horses, etc): +// 1. Define creature-specific constants in constants.nvgt: +// const float SHEEP_SOUND_VOLUME_STEP = 3.0; +// const int SHEEP_FOOTSTEP_MAX_DISTANCE = 5; +// +// 2. Use these functions in your creature update code: +// - play_creature_voice() for periodic sounds (bleats, moos, neighs) - returns handle, store it! +// - play_creature_footstep() when creature moves +// - play_creature_attack_sound() when creature attacks player/barricade +// - play_creature_death_sound() when creature dies +// - play_creature_hit_sound() when player damages creature +// +// 3. IMPORTANT: When creature dies, stop its sound before playing death sound: +// if (creature.sound_handle != -1) { +// p.destroy_sound(creature.sound_handle); +// creature.sound_handle = -1; +// } +// play_creature_death_sound("sounds/enemies/enemy_falls.ogg", x, pos, volume_step); +// +// 4. All sounds automatically use consistent panning/volume falloff +// +// Example for sheep: +// sheep.sound_handle = play_creature_voice("sounds/animals/sheep_bleat.ogg", x, sheep.position, SHEEP_SOUND_VOLUME_STEP); +// play_creature_footstep(x, sheep.position, BASE_END, GRASS_END, SHEEP_FOOTSTEP_MAX_DISTANCE, SHEEP_SOUND_VOLUME_STEP); +// +// Death handling example: +// if (sheep.sound_handle != -1) { +// p.destroy_sound(sheep.sound_handle); +// sheep.sound_handle = -1; +// } +// play_creature_death_sound("sounds/animals/sheep_death.ogg", x, sheep.position, SHEEP_SOUND_VOLUME_STEP); + +// Default audio parameters for creatures +const float CREATURE_DEFAULT_VOLUME_STEP = 3.0; +const int CREATURE_DEFAULT_FOOTSTEP_DISTANCE = 6; + +// Plays a creature's voice/alert sound with consistent positioning +// Returns the sound handle for tracking +int play_creature_voice(string sound_file, int listener_x, int creature_x, float volume_step = CREATURE_DEFAULT_VOLUME_STEP) +{ + return play_1d_with_volume_step(sound_file, listener_x, creature_x, false, volume_step); +} + +// Plays a creature's footstep sound with consistent positioning +// Only plays if within max_distance to avoid cluttering the soundscape +void play_creature_footstep(int listener_x, int creature_x, int base_end, int grass_end, int max_distance = CREATURE_DEFAULT_FOOTSTEP_DISTANCE, float volume_step = CREATURE_DEFAULT_VOLUME_STEP) +{ + int distance = creature_x - listener_x; + if (distance < 0) { + distance = -distance; + } + + if (distance > max_distance) { + return; + } + + play_positional_footstep(listener_x, creature_x, base_end, grass_end, max_distance, volume_step); +} + +// Plays a creature attack sound (hitting player, hitting barricade, etc.) +void play_creature_attack_sound(string sound_file, int listener_x, int creature_x, float volume_step = CREATURE_DEFAULT_VOLUME_STEP) +{ + play_1d_with_volume_step(sound_file, listener_x, creature_x, false, volume_step); +} + +// Plays a creature death/fall sound +void play_creature_death_sound(string sound_file, int listener_x, int creature_x, float volume_step = CREATURE_DEFAULT_VOLUME_STEP) +{ + play_1d_with_volume_step(sound_file, listener_x, creature_x, false, volume_step); +} + +// Plays a creature hit/damage sound (when player damages the creature) +void play_creature_hit_sound(string sound_file, int listener_x, int creature_x, float volume_step = CREATURE_DEFAULT_VOLUME_STEP) +{ + play_1d_with_volume_step(sound_file, listener_x, creature_x, false, volume_step); +} diff --git a/src/environment.nvgt b/src/environment.nvgt index 3a65381..759213c 100644 --- a/src/environment.nvgt +++ b/src/environment.nvgt @@ -45,9 +45,12 @@ class Tree { int tree_distance = x - position; if (tree_distance < 0) tree_distance = -tree_distance; - if (tree_distance <= 3) { + if (tree_distance <= 4) { if (sound_handle == -1 || !p.sound_is_active(sound_handle)) { sound_handle = p.play_1d("sounds/environment/tree.ogg", x, position, true); + if (sound_handle != -1) { + p.update_sound_positioning_values(sound_handle, -1.0, TREE_SOUND_VOLUME_STEP, true); + } } } else if (sound_handle != -1) { p.destroy_sound(sound_handle); diff --git a/src/world_state.nvgt b/src/world_state.nvgt index e195126..f4316aa 100644 --- a/src/world_state.nvgt +++ b/src/world_state.nvgt @@ -39,6 +39,7 @@ class Bandit { int health; string alert_sound; string weapon_type; // "spear" or "axe" + int sound_handle; timer move_timer; timer alert_timer; timer attack_timer; @@ -55,6 +56,7 @@ class Bandit { // Spawn somewhere in the expanded area position = random(expansion_start, expansion_end); health = BANDIT_HEALTH; + sound_handle = -1; // Choose random alert sound int sound_index = random(0, bandit_sounds.length() - 1); @@ -616,7 +618,7 @@ void spawn_zombie() { Zombie@ z = Zombie(spawn_x); zombies.insert_last(z); - z.sound_handle = play_1d_with_volume_step(z.voice_sound, x, spawn_x, false, ZOMBIE_SOUND_VOLUME_STEP); + z.sound_handle = play_creature_voice(z.voice_sound, x, spawn_x, ZOMBIE_SOUND_VOLUME_STEP); } void try_attack_barricade(Zombie@ zombie) { @@ -628,7 +630,7 @@ void try_attack_barricade(Zombie@ zombie) { barricade_health -= damage; if (barricade_health < 0) barricade_health = 0; - play_1d_with_volume_step("sounds/enemies/zombie_hits_player.ogg", x, zombie.position, false, ZOMBIE_SOUND_VOLUME_STEP); + play_creature_attack_sound("sounds/enemies/zombie_hits_player.ogg", x, zombie.position, ZOMBIE_SOUND_VOLUME_STEP); // Resident defense counter-attack if (can_residents_defend()) { @@ -677,7 +679,7 @@ bool try_attack_player(Zombie@ zombie) { player_health = 0; } - play_1d_with_volume_step("sounds/enemies/zombie_hits_player.ogg", x, zombie.position, false, ZOMBIE_SOUND_VOLUME_STEP); + play_creature_attack_sound("sounds/enemies/zombie_hits_player.ogg", x, zombie.position, ZOMBIE_SOUND_VOLUME_STEP); return true; } @@ -685,7 +687,7 @@ void update_zombie(Zombie@ zombie) { if (zombie.groan_timer.elapsed > zombie.next_groan_delay) { zombie.groan_timer.restart(); zombie.next_groan_delay = random(ZOMBIE_GROAN_MIN_DELAY, ZOMBIE_GROAN_MAX_DELAY); - zombie.sound_handle = play_1d_with_volume_step(zombie.voice_sound, x, zombie.position, false, ZOMBIE_SOUND_VOLUME_STEP); + zombie.sound_handle = play_creature_voice(zombie.voice_sound, x, zombie.position, ZOMBIE_SOUND_VOLUME_STEP); } if (try_attack_player(zombie)) { @@ -723,7 +725,7 @@ void update_zombie(Zombie@ zombie) { } zombie.position = target_x; - play_positional_footstep(x, zombie.position, BASE_END, GRASS_END, ZOMBIE_FOOTSTEP_MAX_DISTANCE, ZOMBIE_SOUND_VOLUME_STEP); + play_creature_footstep(x, zombie.position, BASE_END, GRASS_END, ZOMBIE_FOOTSTEP_MAX_DISTANCE, ZOMBIE_SOUND_VOLUME_STEP); } void update_zombies() { @@ -750,7 +752,7 @@ bool damage_zombie_at(int pos, int damage) { p.destroy_sound(zombies[i].sound_handle); zombies[i].sound_handle = -1; } - play_1d_with_volume_step("sounds/enemies/enemy_falls.ogg", x, pos, false, ZOMBIE_SOUND_VOLUME_STEP); + play_creature_death_sound("sounds/enemies/enemy_falls.ogg", x, pos, ZOMBIE_SOUND_VOLUME_STEP); zombies.remove_at(i); } return true; @@ -809,7 +811,7 @@ void spawn_bandit(int expansion_start, int expansion_end) { Bandit@ b = Bandit(spawn_x, expansion_start, expansion_end); bandits.insert_last(b); - play_1d_with_volume_step(b.alert_sound, x, spawn_x, false, BANDIT_SOUND_VOLUME_STEP); + b.sound_handle = play_creature_voice(b.alert_sound, x, spawn_x, BANDIT_SOUND_VOLUME_STEP); } bool can_bandit_attack_player(Bandit@ bandit) { @@ -903,7 +905,7 @@ void update_bandit(Bandit@ bandit) { if (bandit.alert_timer.elapsed > bandit.next_alert_delay) { bandit.alert_timer.restart(); bandit.next_alert_delay = random(BANDIT_ALERT_MIN_DELAY, BANDIT_ALERT_MAX_DELAY); - play_1d_with_volume_step(bandit.alert_sound, x, bandit.position, false, BANDIT_SOUND_VOLUME_STEP); + bandit.sound_handle = play_creature_voice(bandit.alert_sound, x, bandit.position, BANDIT_SOUND_VOLUME_STEP); } if (try_attack_player_bandit(bandit)) { @@ -947,7 +949,7 @@ void update_bandit(Bandit@ bandit) { bandit.wander_direction = -bandit.wander_direction; } else { bandit.position = target_x; - play_positional_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP); + play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP); } } else { // Hit map boundary, reverse direction @@ -990,7 +992,7 @@ void update_bandit(Bandit@ bandit) { } bandit.position = target_x; - play_positional_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP); + play_creature_footstep(x, bandit.position, BASE_END, GRASS_END, BANDIT_FOOTSTEP_MAX_DISTANCE, BANDIT_SOUND_VOLUME_STEP); } } @@ -1005,7 +1007,11 @@ bool damage_bandit_at(int pos, int damage) { if (bandits[i].position == pos) { bandits[i].health -= damage; if (bandits[i].health <= 0) { - play_1d_with_volume_step("sounds/enemies/enemy_falls.ogg", x, pos, false, BANDIT_SOUND_VOLUME_STEP); + if (bandits[i].sound_handle != -1) { + p.destroy_sound(bandits[i].sound_handle); + bandits[i].sound_handle = -1; + } + play_creature_death_sound("sounds/enemies/enemy_falls.ogg", x, pos, BANDIT_SOUND_VOLUME_STEP); bandits.remove_at(i); } else { }