From 619cc4425dee0a383196697f23412d9251f1c405 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 21 Jan 2026 18:36:32 -0500 Subject: [PATCH] More refactor, getting there ... slowly. --- draugnorak.nvgt | 1 + src/enemies/flying_creatures.nvgt | 396 ++++++++++++++++++++++++++++++ src/world_state.nvgt | 396 +----------------------------- 3 files changed, 398 insertions(+), 395 deletions(-) create mode 100644 src/enemies/flying_creatures.nvgt diff --git a/draugnorak.nvgt b/draugnorak.nvgt index cf8d0c6..b12908c 100644 --- a/draugnorak.nvgt +++ b/draugnorak.nvgt @@ -12,6 +12,7 @@ sound_pool p(100); #include "src/enemies/undead.nvgt" #include "src/enemies/bandit.nvgt" #include "src/enemies/ground_game.nvgt" +#include "src/enemies/flying_creatures.nvgt" #include "src/world_state.nvgt" #include "src/ui.nvgt" #include "src/inventory.nvgt" diff --git a/src/enemies/flying_creatures.nvgt b/src/enemies/flying_creatures.nvgt new file mode 100644 index 0000000..3a4d0fb --- /dev/null +++ b/src/enemies/flying_creatures.nvgt @@ -0,0 +1,396 @@ +// Flying creatures (geese, ducks, etc.) +// Config-driven system for spawning and managing flying creatures near water + +string[] goose_sounds = {"sounds/game/goose.ogg"}; + +class FlyingCreatureConfig { + string id; + string drop_type; + string[] sounds; + string fall_sound; + string impact_sound; + int health; + int move_interval_min; + int move_interval_max; + int min_height; + int max_height; + float sound_volume_step; + int sound_delay_min; + int sound_delay_max; + int fall_speed; + int fly_away_chance; + int max_dist_from_water; + int hourly_spawn_chance; + int max_count; + int sight_range; + bool flee_on_sight; +} +FlyingCreatureConfig@[] flying_creature_configs; + +class FlyingCreature { + int position; + int health; + int height; + string state; // "flying", "falling", "fading" + int area_start; + int area_end; + string creature_type; + int sound_handle; + int fall_sound_handle; + timer move_timer; + timer sound_timer; + timer fall_timer; + int next_move_delay; + int next_sound_delay; + string voice_sound; + bool fading_out; + bool ready_to_remove; + timer fade_timer; + + FlyingCreature(string type, int pos, int home_start, int home_end, FlyingCreatureConfig@ cfg) { + position = pos; + health = cfg.health; + height = random(cfg.min_height, cfg.max_height); + state = "flying"; + area_start = home_start; + area_end = home_end; + creature_type = type; + sound_handle = -1; + fall_sound_handle = -1; + + if (cfg.sounds.length() > 0) { + voice_sound = cfg.sounds[random(0, cfg.sounds.length() - 1)]; + } + + move_timer.restart(); + sound_timer.restart(); + + next_move_delay = random(cfg.move_interval_min, cfg.move_interval_max); + next_sound_delay = random(cfg.sound_delay_min, cfg.sound_delay_max); + fading_out = false; + ready_to_remove = false; + } +} +FlyingCreature@[] flying_creatures; + +void init_flying_creature_configs() { + flying_creature_configs.resize(0); + + FlyingCreatureConfig@ goose_cfg = FlyingCreatureConfig(); + goose_cfg.id = "goose"; + goose_cfg.drop_type = "goose"; + goose_cfg.sounds = goose_sounds; + goose_cfg.fall_sound = "sounds/actions/falling.ogg"; + goose_cfg.impact_sound = "sounds/game/game_falls.ogg"; + goose_cfg.health = GOOSE_HEALTH; + goose_cfg.move_interval_min = GOOSE_MOVE_INTERVAL_MIN; + goose_cfg.move_interval_max = GOOSE_MOVE_INTERVAL_MAX; + goose_cfg.min_height = GOOSE_FLYING_HEIGHT_MIN; + goose_cfg.max_height = GOOSE_FLYING_HEIGHT_MAX; + goose_cfg.sound_volume_step = GOOSE_SOUND_VOLUME_STEP; + goose_cfg.sound_delay_min = GOOSE_FLIGHT_SOUND_DELAY_MIN; + goose_cfg.sound_delay_max = GOOSE_FLIGHT_SOUND_DELAY_MAX; + goose_cfg.fall_speed = GOOSE_FALL_SPEED; + goose_cfg.fly_away_chance = GOOSE_FLY_AWAY_CHANCE; + goose_cfg.max_dist_from_water = GOOSE_MAX_DIST_FROM_WATER; + goose_cfg.hourly_spawn_chance = GOOSE_HOURLY_SPAWN_CHANCE; + goose_cfg.max_count = GOOSE_MAX_COUNT; + goose_cfg.sight_range = GOOSE_SIGHT_RANGE; + goose_cfg.flee_on_sight = false; + flying_creature_configs.insert_last(goose_cfg); +} + +FlyingCreatureConfig@ get_flying_creature_config(string creature_type) { + for (uint i = 0; i < flying_creature_configs.length(); i++) { + if (flying_creature_configs[i].id == creature_type) { + return @flying_creature_configs[i]; + } + } + return null; +} + +FlyingCreatureConfig@ get_flying_creature_config_by_drop_type(string drop_type) { + for (uint i = 0; i < flying_creature_configs.length(); i++) { + if (flying_creature_configs[i].drop_type == drop_type) { + return @flying_creature_configs[i]; + } + } + return null; +} + +void clear_flying_creatures() { + for (uint i = 0; i < flying_creatures.length(); i++) { + if (flying_creatures[i].sound_handle != -1) { + p.destroy_sound(flying_creatures[i].sound_handle); + flying_creatures[i].sound_handle = -1; + } + if (flying_creatures[i].fall_sound_handle != -1) { + p.destroy_sound(flying_creatures[i].fall_sound_handle); + flying_creatures[i].fall_sound_handle = -1; + } + } + flying_creatures.resize(0); +} + +FlyingCreature@ get_flying_creature_at(int pos) { + for (uint i = 0; i < flying_creatures.length(); i++) { + if (flying_creatures[i].position == pos) { + return @flying_creatures[i]; + } + } + return null; +} + +int get_flying_creature_count(string creature_type) { + int count = 0; + for (uint i = 0; i < flying_creatures.length(); i++) { + if (flying_creatures[i].creature_type == creature_type) { + count++; + } + } + return count; +} + +bool get_random_flying_creature_area(FlyingCreatureConfig@ cfg, int &out area_start, int &out area_end) { + int stream_count = int(world_streams.length()); + int mountain_stream_count = 0; + for (uint i = 0; i < world_mountains.length(); i++) { + mountain_stream_count += int(world_mountains[i].stream_positions.length()); + } + + int total_areas = stream_count + mountain_stream_count; + if (total_areas <= 0) return false; + + int pick = random(0, total_areas - 1); + if (pick < stream_count) { + area_start = world_streams[pick].start_position; + area_end = world_streams[pick].end_position; + } else { + pick -= stream_count; + for (uint i = 0; i < world_mountains.length(); i++) { + int local_count = int(world_mountains[i].stream_positions.length()); + if (pick < local_count) { + int stream_pos = world_mountains[i].start_position + world_mountains[i].stream_positions[pick]; + area_start = stream_pos; + area_end = stream_pos; + break; + } + pick -= local_count; + } + } + + area_start -= cfg.max_dist_from_water; + area_end += cfg.max_dist_from_water; + if (area_start < 0) area_start = 0; + if (area_end >= MAP_SIZE) area_end = MAP_SIZE - 1; + return true; +} + +bool find_flying_creature_spawn(FlyingCreatureConfig@ cfg, int &out spawn_x, int &out area_start, int &out area_end) { + if (!get_random_flying_creature_area(cfg, area_start, area_end)) return false; + + for (int attempts = 0; attempts < 20; attempts++) { + int candidate = random(area_start, area_end); + if (get_flying_creature_at(candidate) == null) { + spawn_x = candidate; + return true; + } + } + return false; +} + +void fly_away_flying_creature(FlyingCreature@ creature, FlyingCreatureConfig@ cfg) { + creature.state = "fading"; + creature.fading_out = true; + creature.ready_to_remove = false; + creature.fade_timer.restart(); + creature.health = 0; + + if (creature.sound_handle == -1 || !p.sound_is_active(creature.sound_handle)) { + creature.ready_to_remove = true; + } + if (creature.fall_sound_handle != -1) { + p.destroy_sound(creature.fall_sound_handle); + creature.fall_sound_handle = -1; + } +} + +bool spawn_flying_creature(string creature_type) { + FlyingCreatureConfig@ cfg = get_flying_creature_config(creature_type); + if (cfg is null) return false; + + int spawn_x = -1; + int area_start = 0; + int area_end = 0; + + if (!find_flying_creature_spawn(cfg, spawn_x, area_start, area_end)) { + return false; + } + + FlyingCreature@ c = FlyingCreature(creature_type, spawn_x, area_start, area_end, cfg); + flying_creatures.insert_last(c); + c.sound_handle = play_creature_voice(c.voice_sound, x, spawn_x, cfg.sound_volume_step); + return true; +} + +void update_flying_creature(FlyingCreature@ creature) { + FlyingCreatureConfig@ cfg = get_flying_creature_config(creature.creature_type); + if (cfg is null) return; + + if (creature.state == "fading") { + if (!creature.fading_out) { + creature.fading_out = true; + creature.fade_timer.restart(); + } + + if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) { + float progress = float(creature.fade_timer.elapsed) / float(FLYING_CREATURE_FADE_OUT_DURATION); + if (progress < 0.0) progress = 0.0; + if (progress > 1.0) progress = 1.0; + float volume = 0.0 + (FLYING_CREATURE_FADE_OUT_MIN_VOLUME * progress); + p.update_sound_start_values(creature.sound_handle, 0.0, volume, 1.0); + } + + if (creature.fade_timer.elapsed >= FLYING_CREATURE_FADE_OUT_DURATION) { + if (creature.sound_handle != -1) { + p.destroy_sound(creature.sound_handle); + creature.sound_handle = -1; + } + creature.ready_to_remove = true; + } + return; + } + + if (creature.state == "flying") { + if (creature.position < creature.area_start || creature.position > creature.area_end) { + fly_away_flying_creature(creature, cfg); + return; + } + + if (creature.sound_timer.elapsed > creature.next_sound_delay) { + creature.sound_timer.restart(); + creature.next_sound_delay = random(cfg.sound_delay_min, cfg.sound_delay_max); + creature.sound_handle = play_creature_voice(creature.voice_sound, x, creature.position, cfg.sound_volume_step); + } + + if (cfg.fly_away_chance > 0 && random(1, 1000) <= cfg.fly_away_chance) { + fly_away_flying_creature(creature, cfg); + return; + } + + if (creature.move_timer.elapsed > creature.next_move_delay) { + creature.move_timer.restart(); + creature.next_move_delay = random(cfg.move_interval_min, cfg.move_interval_max); + + int dir = 0; + if (cfg.flee_on_sight && cfg.sight_range > 0) { + int distance_to_player = abs(x - creature.position); + if (distance_to_player <= cfg.sight_range) { + if (x > creature.position) dir = -1; + else if (x < creature.position) dir = 1; + } + } + if (dir == 0) dir = random(-1, 1); + if (dir != 0) { + int target_x = creature.position + dir; + if (target_x < creature.area_start || target_x > creature.area_end) { + fly_away_flying_creature(creature, cfg); + return; + } + if (target_x >= 0 && target_x < MAP_SIZE) { + creature.position = target_x; + if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) { + p.update_sound_1d(creature.sound_handle, creature.position); + } + } + } + } + } else if (creature.state == "falling") { + if (creature.fall_timer.elapsed > cfg.fall_speed) { + creature.fall_timer.restart(); + creature.height--; + + if (creature.fall_sound_handle != -1) { + p.destroy_sound(creature.fall_sound_handle); + } + + float pitch_percent = 50.0 + (50.0 * (float(creature.height) / float(cfg.max_height))); + if (pitch_percent < 50.0) pitch_percent = 50.0; + if (pitch_percent > 100.0) pitch_percent = 100.0; + + creature.fall_sound_handle = p.play_extended_1d(cfg.fall_sound, x, creature.position, 0, 0, true, 0, 0.0, 0.0, pitch_percent); + if (creature.fall_sound_handle != -1) { + p.update_sound_positioning_values(creature.fall_sound_handle, -1.0, cfg.sound_volume_step, true); + } + + if (creature.height <= 0) { + if (creature.fall_sound_handle != -1) { + p.destroy_sound(creature.fall_sound_handle); + creature.fall_sound_handle = -1; + } + + play_creature_death_sound(cfg.impact_sound, x, creature.position, cfg.sound_volume_step); + notify("A " + creature.creature_type + " fell from the sky at " + creature.position + "!"); + add_world_drop(creature.position, cfg.drop_type); + creature.health = 0; + } + } + } +} + +void update_flying_creatures() { + for (uint i = 0; i < flying_creatures.length(); i++) { + update_flying_creature(flying_creatures[i]); + + if (flying_creatures[i].health <= 0) { + if (flying_creatures[i].state == "falling" && flying_creatures[i].height <= 0) { + flying_creatures.remove_at(i); + i--; + } else if (flying_creatures[i].state == "flying") { + flying_creatures.remove_at(i); + i--; + } else if (flying_creatures[i].state == "fading" && flying_creatures[i].ready_to_remove) { + flying_creatures.remove_at(i); + i--; + } + } + } +} + +void attempt_hourly_flying_creature_spawn() { + for (uint i = 0; i < flying_creature_configs.length(); i++) { + FlyingCreatureConfig@ cfg = flying_creature_configs[i]; + if (get_flying_creature_count(cfg.id) >= cfg.max_count) continue; + if (random(1, 100) <= cfg.hourly_spawn_chance) { + spawn_flying_creature(cfg.id); + } + } +} + +bool damage_flying_creature_at(int pos, int damage) { + for (uint i = 0; i < flying_creatures.length(); i++) { + if (flying_creatures[i].position == pos && flying_creatures[i].state == "flying") { + FlyingCreatureConfig@ cfg = get_flying_creature_config(flying_creatures[i].creature_type); + if (cfg is null) return false; + + flying_creatures[i].health -= damage; + if (flying_creatures[i].health <= 0) { + flying_creatures[i].state = "falling"; + flying_creatures[i].fall_timer.restart(); + + if (flying_creatures[i].sound_handle != -1) { + p.destroy_sound(flying_creatures[i].sound_handle); + flying_creatures[i].sound_handle = -1; + } + + float pitch_percent = 50.0 + (50.0 * (float(flying_creatures[i].height) / float(cfg.max_height))); + flying_creatures[i].fall_sound_handle = p.play_extended_1d(cfg.fall_sound, x, pos, 0, 0, true, 0, 0.0, 0.0, pitch_percent); + if (flying_creatures[i].fall_sound_handle != -1) { + p.update_sound_positioning_values(flying_creatures[i].fall_sound_handle, -1.0, cfg.sound_volume_step, true); + } + } + return true; + } + } + return false; +} diff --git a/src/world_state.nvgt b/src/world_state.nvgt index 722c473..876fd1c 100644 --- a/src/world_state.nvgt +++ b/src/world_state.nvgt @@ -7,78 +7,6 @@ int barricade_health = 0; bool barricade_initialized = false; int residents_count = 0; -string[] goose_sounds = {"sounds/game/goose.ogg"}; - -class FlyingCreatureConfig { - string id; - string drop_type; - string[] sounds; - string fall_sound; - string impact_sound; - int health; - int move_interval_min; - int move_interval_max; - int min_height; - int max_height; - float sound_volume_step; - int sound_delay_min; - int sound_delay_max; - int fall_speed; - int fly_away_chance; - int max_dist_from_water; - int hourly_spawn_chance; - int max_count; - int sight_range; - bool flee_on_sight; -} -FlyingCreatureConfig@[] flying_creature_configs; - -class FlyingCreature { - int position; - int health; - int height; - string state; // "flying", "falling" - int area_start; - int area_end; - string creature_type; - int sound_handle; - int fall_sound_handle; - timer move_timer; - timer sound_timer; - timer fall_timer; - int next_move_delay; - int next_sound_delay; - string voice_sound; - bool fading_out; - bool ready_to_remove; - timer fade_timer; - - FlyingCreature(string type, int pos, int home_start, int home_end, FlyingCreatureConfig@ cfg) { - position = pos; - health = cfg.health; - height = random(cfg.min_height, cfg.max_height); - state = "flying"; - area_start = home_start; - area_end = home_end; - creature_type = type; - sound_handle = -1; - fall_sound_handle = -1; - - if (cfg.sounds.length() > 0) { - voice_sound = cfg.sounds[random(0, cfg.sounds.length() - 1)]; - } - - move_timer.restart(); - sound_timer.restart(); - - next_move_delay = random(cfg.move_interval_min, cfg.move_interval_max); - next_sound_delay = random(cfg.sound_delay_min, cfg.sound_delay_max); - fading_out = false; - ready_to_remove = false; - } -} -FlyingCreature@[] flying_creatures; - string get_random_small_game() { int index = random(0, small_game_types.length() - 1); return small_game_types[index]; @@ -1025,326 +953,4 @@ void clear_mountains() { world_mountains.resize(0); } -// Flying Creature Functions - -void init_flying_creature_configs() { - flying_creature_configs.resize(0); - - FlyingCreatureConfig@ goose_cfg = FlyingCreatureConfig(); - goose_cfg.id = "goose"; - goose_cfg.drop_type = "goose"; - goose_cfg.sounds = goose_sounds; - goose_cfg.fall_sound = "sounds/actions/falling.ogg"; - goose_cfg.impact_sound = "sounds/game/game_falls.ogg"; - goose_cfg.health = GOOSE_HEALTH; - goose_cfg.move_interval_min = GOOSE_MOVE_INTERVAL_MIN; - goose_cfg.move_interval_max = GOOSE_MOVE_INTERVAL_MAX; - goose_cfg.min_height = GOOSE_FLYING_HEIGHT_MIN; - goose_cfg.max_height = GOOSE_FLYING_HEIGHT_MAX; - goose_cfg.sound_volume_step = GOOSE_SOUND_VOLUME_STEP; - goose_cfg.sound_delay_min = GOOSE_FLIGHT_SOUND_DELAY_MIN; - goose_cfg.sound_delay_max = GOOSE_FLIGHT_SOUND_DELAY_MAX; - goose_cfg.fall_speed = GOOSE_FALL_SPEED; - goose_cfg.fly_away_chance = GOOSE_FLY_AWAY_CHANCE; - goose_cfg.max_dist_from_water = GOOSE_MAX_DIST_FROM_WATER; - goose_cfg.hourly_spawn_chance = GOOSE_HOURLY_SPAWN_CHANCE; - goose_cfg.max_count = GOOSE_MAX_COUNT; - goose_cfg.sight_range = GOOSE_SIGHT_RANGE; - goose_cfg.flee_on_sight = false; - flying_creature_configs.insert_last(goose_cfg); -} - -FlyingCreatureConfig@ get_flying_creature_config(string creature_type) { - for (uint i = 0; i < flying_creature_configs.length(); i++) { - if (flying_creature_configs[i].id == creature_type) { - return @flying_creature_configs[i]; - } - } - return null; -} - -FlyingCreatureConfig@ get_flying_creature_config_by_drop_type(string drop_type) { - for (uint i = 0; i < flying_creature_configs.length(); i++) { - if (flying_creature_configs[i].drop_type == drop_type) { - return @flying_creature_configs[i]; - } - } - return null; -} - -void clear_flying_creatures() { - for (uint i = 0; i < flying_creatures.length(); i++) { - if (flying_creatures[i].sound_handle != -1) { - p.destroy_sound(flying_creatures[i].sound_handle); - flying_creatures[i].sound_handle = -1; - } - if (flying_creatures[i].fall_sound_handle != -1) { - p.destroy_sound(flying_creatures[i].fall_sound_handle); - flying_creatures[i].fall_sound_handle = -1; - } - } - flying_creatures.resize(0); -} - -FlyingCreature@ get_flying_creature_at(int pos) { - for (uint i = 0; i < flying_creatures.length(); i++) { - if (flying_creatures[i].position == pos) { - return @flying_creatures[i]; - } - } - return null; -} - -int get_flying_creature_count(string creature_type) { - int count = 0; - for (uint i = 0; i < flying_creatures.length(); i++) { - if (flying_creatures[i].creature_type == creature_type) { - count++; - } - } - return count; -} - -bool get_random_flying_creature_area(FlyingCreatureConfig@ cfg, int &out area_start, int &out area_end) { - int stream_count = int(world_streams.length()); - int mountain_stream_count = 0; - for (uint i = 0; i < world_mountains.length(); i++) { - mountain_stream_count += int(world_mountains[i].stream_positions.length()); - } - - int total_areas = stream_count + mountain_stream_count; - if (total_areas <= 0) return false; - - int pick = random(0, total_areas - 1); - if (pick < stream_count) { - area_start = world_streams[pick].start_position; - area_end = world_streams[pick].end_position; - } else { - pick -= stream_count; - for (uint i = 0; i < world_mountains.length(); i++) { - int local_count = int(world_mountains[i].stream_positions.length()); - if (pick < local_count) { - int stream_pos = world_mountains[i].start_position + world_mountains[i].stream_positions[pick]; - area_start = stream_pos; - area_end = stream_pos; - break; - } - pick -= local_count; - } - } - - area_start -= cfg.max_dist_from_water; - area_end += cfg.max_dist_from_water; - if (area_start < 0) area_start = 0; - if (area_end >= MAP_SIZE) area_end = MAP_SIZE - 1; - return true; -} - -bool find_flying_creature_spawn(FlyingCreatureConfig@ cfg, int &out spawn_x, int &out area_start, int &out area_end) { - if (!get_random_flying_creature_area(cfg, area_start, area_end)) return false; - - for (int attempts = 0; attempts < 20; attempts++) { - int candidate = random(area_start, area_end); - if (get_flying_creature_at(candidate) == null) { - spawn_x = candidate; - return true; - } - } - return false; -} - -void fly_away_flying_creature(FlyingCreature@ creature, FlyingCreatureConfig@ cfg) { - creature.state = "fading"; - creature.fading_out = true; - creature.ready_to_remove = false; - creature.fade_timer.restart(); - creature.health = 0; - - if (creature.sound_handle == -1 || !p.sound_is_active(creature.sound_handle)) { - creature.ready_to_remove = true; - } - if (creature.fall_sound_handle != -1) { - p.destroy_sound(creature.fall_sound_handle); - creature.fall_sound_handle = -1; - } -} - -bool spawn_flying_creature(string creature_type) { - FlyingCreatureConfig@ cfg = get_flying_creature_config(creature_type); - if (cfg is null) return false; - - int spawn_x = -1; - int area_start = 0; - int area_end = 0; - - if (!find_flying_creature_spawn(cfg, spawn_x, area_start, area_end)) { - return false; - } - - FlyingCreature@ c = FlyingCreature(creature_type, spawn_x, area_start, area_end, cfg); - flying_creatures.insert_last(c); - c.sound_handle = play_creature_voice(c.voice_sound, x, spawn_x, cfg.sound_volume_step); - return true; -} - -void update_flying_creature(FlyingCreature@ creature) { - FlyingCreatureConfig@ cfg = get_flying_creature_config(creature.creature_type); - if (cfg is null) return; - - if (creature.state == "fading") { - if (!creature.fading_out) { - creature.fading_out = true; - creature.fade_timer.restart(); - } - - if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) { - float progress = float(creature.fade_timer.elapsed) / float(FLYING_CREATURE_FADE_OUT_DURATION); - if (progress < 0.0) progress = 0.0; - if (progress > 1.0) progress = 1.0; - float volume = 0.0 + (FLYING_CREATURE_FADE_OUT_MIN_VOLUME * progress); - p.update_sound_start_values(creature.sound_handle, 0.0, volume, 1.0); - } - - if (creature.fade_timer.elapsed >= FLYING_CREATURE_FADE_OUT_DURATION) { - if (creature.sound_handle != -1) { - p.destroy_sound(creature.sound_handle); - creature.sound_handle = -1; - } - creature.ready_to_remove = true; - } - return; - } - - if (creature.state == "flying") { - if (creature.position < creature.area_start || creature.position > creature.area_end) { - fly_away_flying_creature(creature, cfg); - return; - } - - if (creature.sound_timer.elapsed > creature.next_sound_delay) { - creature.sound_timer.restart(); - creature.next_sound_delay = random(cfg.sound_delay_min, cfg.sound_delay_max); - creature.sound_handle = play_creature_voice(creature.voice_sound, x, creature.position, cfg.sound_volume_step); - } - - if (cfg.fly_away_chance > 0 && random(1, 1000) <= cfg.fly_away_chance) { - fly_away_flying_creature(creature, cfg); - return; - } - - if (creature.move_timer.elapsed > creature.next_move_delay) { - creature.move_timer.restart(); - creature.next_move_delay = random(cfg.move_interval_min, cfg.move_interval_max); - - int dir = 0; - if (cfg.flee_on_sight && cfg.sight_range > 0) { - int distance_to_player = abs(x - creature.position); - if (distance_to_player <= cfg.sight_range) { - if (x > creature.position) dir = -1; - else if (x < creature.position) dir = 1; - } - } - if (dir == 0) dir = random(-1, 1); - if (dir != 0) { - int target_x = creature.position + dir; - if (target_x < creature.area_start || target_x > creature.area_end) { - fly_away_flying_creature(creature, cfg); - return; - } - if (target_x >= 0 && target_x < MAP_SIZE) { - creature.position = target_x; - if (creature.sound_handle != -1 && p.sound_is_active(creature.sound_handle)) { - p.update_sound_1d(creature.sound_handle, creature.position); - } - } - } - } - } else if (creature.state == "falling") { - if (creature.fall_timer.elapsed > cfg.fall_speed) { - creature.fall_timer.restart(); - creature.height--; - - if (creature.fall_sound_handle != -1) { - p.destroy_sound(creature.fall_sound_handle); - } - - float pitch_percent = 50.0 + (50.0 * (float(creature.height) / float(cfg.max_height))); - if (pitch_percent < 50.0) pitch_percent = 50.0; - if (pitch_percent > 100.0) pitch_percent = 100.0; - - creature.fall_sound_handle = p.play_extended_1d(cfg.fall_sound, x, creature.position, 0, 0, true, 0, 0.0, 0.0, pitch_percent); - if (creature.fall_sound_handle != -1) { - p.update_sound_positioning_values(creature.fall_sound_handle, -1.0, cfg.sound_volume_step, true); - } - - if (creature.height <= 0) { - if (creature.fall_sound_handle != -1) { - p.destroy_sound(creature.fall_sound_handle); - creature.fall_sound_handle = -1; - } - - play_creature_death_sound(cfg.impact_sound, x, creature.position, cfg.sound_volume_step); - notify("A " + creature.creature_type + " fell from the sky at " + creature.position + "!"); - add_world_drop(creature.position, cfg.drop_type); - creature.health = 0; - } - } - } -} - -void update_flying_creatures() { - for (uint i = 0; i < flying_creatures.length(); i++) { - update_flying_creature(flying_creatures[i]); - - if (flying_creatures[i].health <= 0) { - if (flying_creatures[i].state == "falling" && flying_creatures[i].height <= 0) { - flying_creatures.remove_at(i); - i--; - } else if (flying_creatures[i].state == "flying") { - flying_creatures.remove_at(i); - i--; - } else if (flying_creatures[i].state == "fading" && flying_creatures[i].ready_to_remove) { - flying_creatures.remove_at(i); - i--; - } - } - } -} - -void attempt_hourly_flying_creature_spawn() { - for (uint i = 0; i < flying_creature_configs.length(); i++) { - FlyingCreatureConfig@ cfg = flying_creature_configs[i]; - if (get_flying_creature_count(cfg.id) >= cfg.max_count) continue; - if (random(1, 100) <= cfg.hourly_spawn_chance) { - spawn_flying_creature(cfg.id); - } - } -} - -bool damage_flying_creature_at(int pos, int damage) { - for (uint i = 0; i < flying_creatures.length(); i++) { - if (flying_creatures[i].position == pos && flying_creatures[i].state == "flying") { - FlyingCreatureConfig@ cfg = get_flying_creature_config(flying_creatures[i].creature_type); - if (cfg is null) return false; - - flying_creatures[i].health -= damage; - if (flying_creatures[i].health <= 0) { - flying_creatures[i].state = "falling"; - flying_creatures[i].fall_timer.restart(); - - if (flying_creatures[i].sound_handle != -1) { - p.destroy_sound(flying_creatures[i].sound_handle); - flying_creatures[i].sound_handle = -1; - } - - float pitch_percent = 50.0 + (50.0 * (float(flying_creatures[i].height) / float(cfg.max_height))); - flying_creatures[i].fall_sound_handle = p.play_extended_1d(cfg.fall_sound, x, pos, 0, 0, true, 0, 0.0, 0.0, pitch_percent); - if (flying_creatures[i].fall_sound_handle != -1) { - p.update_sound_positioning_values(flying_creatures[i].fall_sound_handle, -1.0, cfg.sound_volume_step, true); - } - } - return true; - } - } - return false; -} +// Flying Creature functions moved to src/enemies/flying_creatures.nvgt