Lots of changes, forgot to add the heal scroll sound. Sounds for when enemies come in and go out of range. Menu filtering added.

This commit is contained in:
Storm Dragon
2026-01-25 21:10:38 -05:00
parent 815ac97a64
commit 43b24b6d11
30 changed files with 731 additions and 119 deletions
+1
View File
@@ -164,6 +164,7 @@ void main()
update_bandits();
update_boars();
update_flying_creatures();
update_weapon_range_audio_all();
update_world_drops();
update_blessings();
update_notifications();
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5 -1
View File
@@ -303,7 +303,11 @@ void attempt_resident_sling_defense() {
}
// Play hit sound on enemy
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, targetPos, ZOMBIE_SOUND_VOLUME_STEP);
if (targetIsBandit) {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, targetPos, BANDIT_SOUND_VOLUME_STEP);
} else {
play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", x, targetPos, ZOMBIE_SOUND_VOLUME_STEP);
}
}
void process_daily_weapon_breakage() {
+1 -1
View File
@@ -171,7 +171,7 @@ void release_sling_attack(int player_x) {
hit_bandit = true;
break;
}
// Then check for boar
GroundGame@ boar = get_boar_at(check_x);
if (boar != null) {
+1
View File
@@ -119,6 +119,7 @@ const int INVASION_DURATION_HOURS = 1;
const int BANDIT_DETECTION_RADIUS = 5;
const int BANDIT_WANDER_DIRECTION_CHANGE_MIN = 3000;
const int BANDIT_WANDER_DIRECTION_CHANGE_MAX = 8000;
const int INVADER_SOUND_VARIANTS_MAX = 5;
// Audio ranges and volume falloff
// Formula: final_volume = start_volume - (volume_step × distance)
+4 -4
View File
@@ -155,7 +155,7 @@ void reinforce_barricade_max_with_sticks() {
int to_do = (max_reinforcements < can_afford) ? max_reinforcements : can_afford;
int total_cost = to_do * BARRICADE_STICK_COST;
simulate_crafting(total_cost);
simulate_crafting(to_do);
add_personal_count(ITEM_STICKS, -total_cost);
barricade_health += (to_do * BARRICADE_STICK_HEALTH);
if (barricade_health > BARRICADE_MAX_HEALTH) barricade_health = BARRICADE_MAX_HEALTH;
@@ -181,7 +181,7 @@ void reinforce_barricade_max_with_vines() {
int to_do = (max_reinforcements < can_afford) ? max_reinforcements : can_afford;
int total_cost = to_do * BARRICADE_VINE_COST;
simulate_crafting(total_cost);
simulate_crafting(to_do);
add_personal_count(ITEM_VINES, -total_cost);
barricade_health += (to_do * BARRICADE_VINE_HEALTH);
if (barricade_health > BARRICADE_MAX_HEALTH) barricade_health = BARRICADE_MAX_HEALTH;
@@ -207,7 +207,7 @@ void reinforce_barricade_max_with_log() {
int to_do = (max_reinforcements < can_afford) ? max_reinforcements : can_afford;
int total_cost = to_do * BARRICADE_LOG_COST;
simulate_crafting(total_cost);
simulate_crafting(to_do);
add_personal_count(ITEM_LOGS, -total_cost);
barricade_health += (to_do * BARRICADE_LOG_HEALTH);
if (barricade_health > BARRICADE_MAX_HEALTH) barricade_health = BARRICADE_MAX_HEALTH;
@@ -233,7 +233,7 @@ void reinforce_barricade_max_with_stones() {
int to_do = (max_reinforcements < can_afford) ? max_reinforcements : can_afford;
int total_cost = to_do * BARRICADE_STONE_COST;
simulate_crafting(total_cost);
simulate_crafting(to_do);
add_personal_count(ITEM_STONES, -total_cost);
barricade_health += (to_do * BARRICADE_STONE_HEALTH);
if (barricade_health > BARRICADE_MAX_HEALTH) barricade_health = BARRICADE_MAX_HEALTH;
+51 -2
View File
@@ -17,7 +17,7 @@
// - 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_death_sounds() when creature dies (adds optional *_dies.ogg)
// - play_creature_hit_sound() when player damages creature
//
// 3. IMPORTANT: When creature dies, stop its sound before playing death sound:
@@ -25,7 +25,7 @@
// p.destroy_sound(creature.sound_handle);
// creature.sound_handle = -1;
// }
// play_creature_death_sound("sounds/enemies/enemy_falls.ogg", x, pos, volume_step);
// play_creature_death_sounds("sounds/enemies/enemy_falls.ogg", creature.voice_sound, x, pos, volume_step);
//
// 4. All sounds automatically use consistent panning/volume falloff
//
@@ -79,6 +79,55 @@ void play_creature_death_sound(string sound_file, int listener_x, int creature_x
play_1d_with_volume_step(sound_file, listener_x, creature_x, false, volume_step);
}
string get_creature_death_sound_from_alert(string alert_sound)
{
if (alert_sound == "") {
return "";
}
string[] parts = alert_sound.split("/");
string filename = alert_sound;
if (parts.length() > 0) {
filename = parts[parts.length() - 1];
}
int dot_index = filename.find(".");
if (dot_index > 0) {
filename = filename.substr(0, dot_index);
}
// Strip trailing digits (bandit1 -> bandit, goblin2 -> goblin)
while (filename.length() > 0) {
string last_char = filename.substr(filename.length() - 1, 1);
if (last_char >= "0" && last_char <= "9") {
filename = filename.substr(0, filename.length() - 1);
} else {
break;
}
}
if (filename == "") {
return "";
}
string death_sound = "sounds/enemies/" + filename + "_dies.ogg";
if (!file_exists(death_sound)) {
return "";
}
return death_sound;
}
void play_creature_death_sounds(string default_sound, string alert_sound, int listener_x, int creature_x, float volume_step = CREATURE_DEFAULT_VOLUME_STEP)
{
play_creature_death_sound(default_sound, listener_x, creature_x, volume_step);
string death_sound = get_creature_death_sound_from_alert(alert_sound);
if (death_sound != "" && death_sound != default_sound) {
play_creature_death_sound(death_sound, listener_x, creature_x, 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)
{
+2 -2
View File
@@ -217,7 +217,7 @@ bool damage_goblin_at(int pos, int damage) {
goblins[i].sound_handle = -1;
}
// REQUIRED: Use creature_audio for death sound
play_creature_death_sound("sounds/enemies/enemy_falls.ogg", x, pos, GOBLIN_SOUND_VOLUME_STEP);
play_creature_death_sounds("sounds/enemies/enemy_falls.ogg", goblins[i].voice_sound, x, pos, GOBLIN_SOUND_VOLUME_STEP);
goblins.remove_at(i);
}
return true;
@@ -307,7 +307,7 @@ play_creature_hit_sound("sounds/enemies/zombie_hit.ogg", player_x, target_x, GOB
[ ] Update: play_creature_voice() with stored handle
[ ] Movement: play_creature_footstep()
[ ] Attacks: play_creature_attack_sound() for weapons
[ ] Death: Stop sound_handle BEFORE play_creature_death_sound()
[ ] Death: Stop sound_handle BEFORE play_creature_death_sounds()
[ ] Player damages creature: play_creature_hit_sound()
Functions:
+53 -6
View File
@@ -3,12 +3,44 @@
string[] bandit_sounds = {"sounds/enemies/bandit1.ogg", "sounds/enemies/bandit2.ogg"};
string[] get_invader_sound_list(const string&in invader_type) {
string[] sounds;
if (invader_type == "") {
return sounds;
}
for (int i = 1; i <= INVADER_SOUND_VARIANTS_MAX; i++) {
string sound_file = "sounds/enemies/" + invader_type + i + ".ogg";
if (file_exists(sound_file)) {
sounds.insert_last(sound_file);
}
}
return sounds;
}
string pick_invader_alert_sound(const string&in invader_type) {
string[] sounds = get_invader_sound_list(invader_type);
if (sounds.length() == 0) {
sounds = bandit_sounds;
}
if (sounds.length() == 0) {
return "";
}
int sound_index = random(0, sounds.length() - 1);
return sounds[sound_index];
}
class Bandit {
int position;
int health;
string alert_sound;
string invader_type;
string weapon_type; // "spear" or "axe"
int sound_handle;
bool in_weapon_range;
timer move_timer;
timer attack_timer;
int move_interval;
@@ -19,15 +51,15 @@ class Bandit {
timer wander_direction_timer;
int wander_direction_change_interval;
Bandit(int pos, int expansion_start, int expansion_end) {
Bandit(int pos, int expansion_start, int expansion_end, string invader = "bandit") {
// Spawn somewhere in the expanded area
position = random(expansion_start, expansion_end);
health = BANDIT_HEALTH;
sound_handle = -1;
invader_type = invader;
// Choose random alert sound
int sound_index = random(0, bandit_sounds.length() - 1);
alert_sound = bandit_sounds[sound_index];
alert_sound = pick_invader_alert_sound(invader_type);
// Choose random weapon
weapon_type = (random(0, 1) == 0) ? "spear" : "axe";
@@ -43,14 +75,28 @@ class Bandit {
wander_direction = 0;
wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
wander_direction_timer.restart();
in_weapon_range = false;
}
}
Bandit@[] bandits;
void update_bandit_weapon_range_audio() {
for (uint i = 0; i < bandits.length(); i++) {
update_weapon_range_audio(bandits[i].position, bandits[i].in_weapon_range);
}
}
bool bandit_range_audio_registered = false;
void ensure_bandit_range_audio_registration() {
if (bandit_range_audio_registered) return;
bandit_range_audio_registered = register_weapon_range_audio_callback(@update_bandit_weapon_range_audio);
}
void clear_bandits() {
if (bandits.length() == 0) return;
for (uint i = 0; i < bandits.length(); i++) {
force_weapon_range_exit(bandits[i].position, bandits[i].in_weapon_range);
if (bandits[i].sound_handle != -1) {
p.destroy_sound(bandits[i].sound_handle);
bandits[i].sound_handle = -1;
@@ -68,7 +114,7 @@ Bandit@ get_bandit_at(int pos) {
return null;
}
void spawn_bandit(int expansion_start, int expansion_end) {
void spawn_bandit(int expansion_start, int expansion_end, const string&in invader_type = "bandit") {
int spawn_x = -1;
for (int attempts = 0; attempts < 20; attempts++) {
int candidate = random(expansion_start, expansion_end);
@@ -81,7 +127,7 @@ void spawn_bandit(int expansion_start, int expansion_end) {
spawn_x = random(expansion_start, expansion_end);
}
Bandit@ b = Bandit(spawn_x, expansion_start, expansion_end);
Bandit@ b = Bandit(spawn_x, expansion_start, expansion_end, invader_type);
bandits.insert_last(b);
// Play looping sound that follows the bandit
int[] areaStarts;
@@ -289,6 +335,7 @@ void update_bandit(Bandit@ bandit, bool audio_active) {
}
void update_bandits() {
ensure_bandit_range_audio_registration();
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
@@ -309,7 +356,7 @@ bool damage_bandit_at(int pos, int damage) {
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);
play_creature_death_sounds("sounds/enemies/enemy_falls.ogg", bandits[i].alert_sound, x, pos, BANDIT_SOUND_VOLUME_STEP);
bandits.remove_at(i);
}
return true;
+22
View File
@@ -44,6 +44,7 @@ class FlyingCreature {
bool fading_out;
bool ready_to_remove;
timer fade_timer;
bool in_weapon_range;
FlyingCreature(string type, int pos, int home_start, int home_end, FlyingCreatureConfig@ cfg) {
position = pos;
@@ -65,10 +66,29 @@ class FlyingCreature {
next_move_delay = random(cfg.move_interval_min, cfg.move_interval_max);
fading_out = false;
ready_to_remove = false;
in_weapon_range = false;
}
}
FlyingCreature@[] flying_creatures;
void update_flying_creature_weapon_range_audio() {
for (uint i = 0; i < flying_creatures.length(); i++) {
if (flying_creatures[i].state == "flying") {
update_weapon_range_audio(flying_creatures[i].position, flying_creatures[i].in_weapon_range);
} else if (flying_creatures[i].state == "fading") {
force_weapon_range_exit(flying_creatures[i].position, flying_creatures[i].in_weapon_range);
} else {
flying_creatures[i].in_weapon_range = false;
}
}
}
bool flying_creature_range_audio_registered = false;
void ensure_flying_creature_range_audio_registration() {
if (flying_creature_range_audio_registered) return;
flying_creature_range_audio_registered = register_weapon_range_audio_callback(@update_flying_creature_weapon_range_audio);
}
void init_flying_creature_configs() {
flying_creature_configs.resize(0);
@@ -116,6 +136,7 @@ FlyingCreatureConfig@ get_flying_creature_config_by_drop_type(string drop_type)
void clear_flying_creatures() {
for (uint i = 0; i < flying_creatures.length(); i++) {
force_weapon_range_exit(flying_creatures[i].position, flying_creatures[i].in_weapon_range);
if (flying_creatures[i].sound_handle != -1) {
p.destroy_sound(flying_creatures[i].sound_handle);
flying_creatures[i].sound_handle = -1;
@@ -357,6 +378,7 @@ void update_flying_creature(FlyingCreature@ creature, bool audio_active) {
}
void update_flying_creatures() {
ensure_flying_creature_range_audio_registration();
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
+16
View File
@@ -16,6 +16,7 @@ class GroundGame {
int area_end;
int wander_direction; // -1, 0, 1
string animal_type; // "boar", future: "mountain_goat", "ram", etc.
bool in_weapon_range;
GroundGame(int pos, int start, int end, string type = "boar") {
position = pos;
@@ -33,14 +34,28 @@ class GroundGame {
attack_timer.restart();
next_move_delay = random(BOAR_MOVE_INTERVAL_MIN, BOAR_MOVE_INTERVAL_MAX);
in_weapon_range = false;
}
}
GroundGame@[] ground_games;
void update_ground_game_weapon_range_audio() {
for (uint i = 0; i < ground_games.length(); i++) {
update_weapon_range_audio(ground_games[i].position, ground_games[i].in_weapon_range);
}
}
bool ground_game_range_audio_registered = false;
void ensure_ground_game_range_audio_registration() {
if (ground_game_range_audio_registered) return;
ground_game_range_audio_registered = register_weapon_range_audio_callback(@update_ground_game_weapon_range_audio);
}
void clear_ground_games() {
if (ground_games.length() == 0) return;
for (uint i = 0; i < ground_games.length(); i++) {
force_weapon_range_exit(ground_games[i].position, ground_games[i].in_weapon_range);
if (ground_games[i].sound_handle != -1) {
p.destroy_sound(ground_games[i].sound_handle);
ground_games[i].sound_handle = -1;
@@ -189,6 +204,7 @@ void update_ground_game(GroundGame@ game, bool audio_active) {
}
void update_ground_games() {
ensure_ground_game_range_audio_registration();
int[] areaStarts;
int[] areaEnds;
get_active_audio_areas(areaStarts, areaEnds);
+17 -1
View File
@@ -9,6 +9,7 @@ class Undead {
string undead_type; // "zombie", future: "vampire", "ghost", etc.
string voice_sound;
int sound_handle;
bool in_weapon_range;
timer move_timer;
timer attack_timer;
@@ -19,16 +20,30 @@ class Undead {
int sound_index = random(0, undead_zombie_sounds.length() - 1);
voice_sound = undead_zombie_sounds[sound_index];
sound_handle = -1;
in_weapon_range = false;
move_timer.restart();
attack_timer.restart();
}
}
Undead@[] undeads;
void update_undead_weapon_range_audio() {
for (uint i = 0; i < undeads.length(); i++) {
update_weapon_range_audio(undeads[i].position, undeads[i].in_weapon_range);
}
}
bool undead_range_audio_registered = false;
void ensure_undead_range_audio_registration() {
if (undead_range_audio_registered) return;
undead_range_audio_registered = register_weapon_range_audio_callback(@update_undead_weapon_range_audio);
}
void clear_undeads() {
if (undeads.length() == 0) return;
for (uint i = 0; i < undeads.length(); i++) {
force_weapon_range_exit(undeads[i].position, undeads[i].in_weapon_range);
if (undeads[i].sound_handle != -1) {
p.destroy_sound(undeads[i].sound_handle);
undeads[i].sound_handle = -1;
@@ -191,6 +206,7 @@ void update_undead(Undead@ undead, bool audio_active) {
}
void update_undeads() {
ensure_undead_range_audio_registration();
if (is_daytime) {
clear_undeads();
return;
@@ -227,7 +243,7 @@ bool damage_undead_at(int pos, int damage) {
p.destroy_sound(undeads[i].sound_handle);
undeads[i].sound_handle = -1;
}
play_creature_death_sound("sounds/enemies/enemy_falls.ogg", x, pos, ZOMBIE_SOUND_VOLUME_STEP);
play_creature_death_sounds("sounds/enemies/enemy_falls.ogg", undeads[i].voice_sound, x, pos, ZOMBIE_SOUND_VOLUME_STEP);
undeads.remove_at(i);
}
return true;
+1
View File
@@ -1,6 +1,7 @@
// Inventory module includes
#include "src/item_registry.nvgt"
#include "src/inventory_items.nvgt"
#include "src/weapon_range_audio.nvgt"
#include "src/runes/rune_data.nvgt"
#include "src/runes/rune_effects.nvgt"
#include "src/inventory_menus.nvgt"
+47 -1
View File
@@ -39,12 +39,18 @@ int equipped_feet = EQUIP_NONE;
// Quick slots
int[] quick_slots;
int[] item_count_slots;
void reset_quick_slots() {
quick_slots.resize(10);
for (uint i = 0; i < quick_slots.length(); i++) {
quick_slots[i] = -1;
}
item_count_slots.resize(10);
for (uint i = 0; i < item_count_slots.length(); i++) {
item_count_slots[i] = -1;
}
}
int get_personal_stack_limit() {
@@ -218,6 +224,37 @@ int get_quick_slot_key() {
return -1;
}
int get_item_count_for_type(int item_type) {
if (is_runed_item_type(item_type)) {
int equip_type = 0;
int rune_type = 0;
decode_runed_item_type(item_type, equip_type, rune_type);
return get_runed_item_count(equip_type, rune_type);
}
return get_personal_count(item_type);
}
string get_item_count_label(int item_type, int count) {
if (is_runed_item_type(item_type)) {
int equip_type = 0;
int rune_type = 0;
decode_runed_item_type(item_type, equip_type, rune_type);
string name = "Runed " + get_base_equipment_name(equip_type) + " of " + get_rune_effect_name(rune_type);
return count + " " + name;
}
return count + " " + get_item_label_for_count(item_type, count);
}
string get_item_count_binding_name(int item_type) {
if (is_runed_item_type(item_type)) {
int equip_type = 0;
int rune_type = 0;
decode_runed_item_type(item_type, equip_type, rune_type);
return "Runed " + get_base_equipment_name(equip_type) + " of " + get_rune_effect_name(rune_type);
}
return get_item_display_name(item_type);
}
bool try_consume_heal_scroll() {
if (player_health > 0) return false;
if (get_personal_count(ITEM_HEAL_SCROLL) <= 0) return false;
@@ -236,7 +273,16 @@ void activate_quick_slot(int slot_index) {
int equip_type = quick_slots[slot_index];
if (equip_type < 0) {
speak_with_history("No item bound to slot " + slot_index + ".", true);
int item_type = -1;
if (slot_index >= 0 && slot_index < int(item_count_slots.length())) {
item_type = item_count_slots[slot_index];
}
if (item_type != -1) {
int count = get_item_count_for_type(item_type);
speak_with_history(get_item_count_label(item_type, count) + ".", true);
} else {
speak_with_history("No item bound to slot " + slot_index + ".", true);
}
return;
}
+19
View File
@@ -314,3 +314,22 @@ string get_item_label_for_count(int item_type, int count) {
}
return get_item_label(item_type);
}
// Encode runed item info into a negative item type
// Format: -1000 - (equip_type * 10) - rune_type
// This allows us to extract both equip_type and rune_type from a single int
int encode_runed_item_type(int equip_type, int rune_type) {
return -1000 - (equip_type * 10) - rune_type;
}
// Decode a runed item type back to equip_type and rune_type
void decode_runed_item_type(int encoded, int& out equip_type, int& out rune_type) {
int decoded = -(encoded + 1000);
equip_type = decoded / 10;
rune_type = decoded % 10;
}
// Check if an item_type represents a runed item
bool is_runed_item_type(int item_type) {
return item_type <= -1000;
}
+35 -8
View File
@@ -256,6 +256,10 @@ void run_action_menu(int x) {
options.insert_last("Check fishing pole");
action_types.insert_last(7);
}
string filter_text = "";
int[] filtered_indices;
string[] filtered_options;
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
while(true) {
wait(5);
@@ -265,20 +269,40 @@ void run_action_menu(int x) {
break;
}
bool filter_changed = update_menu_filter_state(filter_text, options, filtered_indices, filtered_options, selection);
if (filter_changed) {
if (filtered_options.length() == 0) {
if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
} else {
speak_with_history("No options.", true);
}
} else {
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection++;
if (selection >= int(filtered_options.length())) selection = 0;
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection--;
if (selection < 0) selection = int(filtered_options.length()) - 1;
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_RETURN)) {
int action = action_types[selection];
if (filtered_indices.length() == 0) {
continue;
}
int action = action_types[filtered_indices[selection]];
if (action == 0) {
try_place_snare(x);
} else if (action == 1) {
@@ -300,7 +324,10 @@ void run_action_menu(int x) {
}
if (key_pressed(KEY_TAB)) {
int action = action_types[selection];
if (filtered_indices.length() == 0) {
continue;
}
int action = action_types[filtered_indices[selection]];
if (action == 0) {
speak_with_history("Can't do that.", true);
} else if (action == 1) {
+49 -14
View File
@@ -81,6 +81,10 @@ void run_altar_menu() {
string[] options;
int[] item_types;
build_personal_inventory_options(options, item_types);
string filter_text = "";
int[] filtered_indices;
string[] filtered_options;
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
while(true) {
wait(5);
@@ -90,30 +94,61 @@ void run_altar_menu() {
break;
}
bool filter_changed = update_menu_filter_state(filter_text, options, filtered_indices, filtered_options, selection);
if (filter_changed) {
if (filtered_options.length() == 0) {
if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
} else {
speak_with_history("No options.", true);
}
} else {
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection++;
if (selection >= int(filtered_options.length())) selection = 0;
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection--;
if (selection < 0) selection = int(filtered_options.length()) - 1;
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_RETURN)) {
sacrifice_item(item_types[selection]);
build_personal_inventory_options(options, item_types);
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_indices.length() > 0) {
sacrifice_item(item_types[filtered_indices[selection]]);
build_personal_inventory_options(options, item_types);
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
if (selection >= int(filtered_options.length())) selection = 0;
if (filtered_options.length() > 0) {
speak_with_history(filtered_options[selection], true);
} else if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
}
}
}
if (key_pressed(KEY_TAB)) {
sacrifice_item_max(item_types[selection]);
build_personal_inventory_options(options, item_types);
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_indices.length() > 0) {
sacrifice_item_max(item_types[filtered_indices[selection]]);
build_personal_inventory_options(options, item_types);
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
if (selection >= int(filtered_options.length())) selection = 0;
if (filtered_options.length() > 0) {
speak_with_history(filtered_options[selection], true);
} else if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
}
}
}
}
}
+27 -6
View File
@@ -43,6 +43,10 @@ void run_base_info_menu() {
if (world_stables.length() > 0) options.insert_last("Stable built");
else options.insert_last("Stable not built");
string filter_text = "";
int[] filtered_indices;
string[] filtered_options;
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
while(true) {
wait(5);
@@ -52,16 +56,33 @@ void run_base_info_menu() {
break;
}
bool filter_changed = update_menu_filter_state(filter_text, options, filtered_indices, filtered_options, selection);
if (filter_changed) {
if (filtered_options.length() == 0) {
if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
} else {
speak_with_history("No options.", true);
}
} else {
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection++;
if (selection >= int(filtered_options.length())) selection = 0;
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection--;
if (selection < 0) selection = int(filtered_options.length()) - 1;
speak_with_history(filtered_options[selection], true);
}
}
}
}
+35 -11
View File
@@ -145,6 +145,10 @@ void run_equipment_menu() {
rune_types.insert_last(RUNE_SWIFTNESS);
}
}
string filter_text = "";
int[] filtered_indices;
string[] filtered_options;
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
while(true) {
wait(5);
@@ -154,30 +158,50 @@ void run_equipment_menu() {
break;
}
bool filter_changed = update_menu_filter_state(filter_text, options, filtered_indices, filtered_options, selection);
if (filter_changed) {
if (filtered_options.length() == 0) {
if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
} else {
speak_with_history("No options.", true);
}
} else {
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= int(options.length())) selection = 0;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection++;
if (selection >= int(filtered_options.length())) selection = 0;
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = int(options.length()) - 1;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection--;
if (selection < 0) selection = int(filtered_options.length()) - 1;
speak_with_history(filtered_options[selection], true);
}
}
int slot_index = get_quick_slot_key();
if (slot_index != -1) {
int equip_type = equipment_types[selection];
int rune_type = rune_types[selection];
if (slot_index != -1 && filtered_indices.length() > 0) {
int equip_type = equipment_types[filtered_indices[selection]];
int rune_type = rune_types[filtered_indices[selection]];
quick_slots[slot_index] = equip_type;
string name = get_full_equipment_name(equip_type, rune_type);
speak_with_history(name + " set to slot " + slot_index + ".", true);
}
if (key_pressed(KEY_RETURN)) {
int equip_type = equipment_types[selection];
int rune_type = rune_types[selection];
if (filtered_indices.length() == 0) {
continue;
}
int equip_type = equipment_types[filtered_indices[selection]];
int rune_type = rune_types[filtered_indices[selection]];
string name = get_full_equipment_name(equip_type, rune_type);
if (is_runed_item_equipped(equip_type, rune_type)) {
+59 -14
View File
@@ -105,6 +105,10 @@ void run_inventory_menu(bool allow_deposit) {
string[] options;
int[] item_types;
build_personal_inventory_options(options, item_types);
string filter_text = "";
int[] filtered_indices;
string[] filtered_options;
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
while(true) {
wait(5);
@@ -114,30 +118,71 @@ void run_inventory_menu(bool allow_deposit) {
break;
}
bool filter_changed = update_menu_filter_state(filter_text, options, filtered_indices, filtered_options, selection);
if (filter_changed) {
if (filtered_options.length() == 0) {
if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
} else {
speak_with_history("No options.", true);
}
} else {
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection++;
if (selection >= int(filtered_options.length())) selection = 0;
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection--;
if (selection < 0) selection = int(filtered_options.length()) - 1;
speak_with_history(filtered_options[selection], true);
}
}
int slot_index = get_quick_slot_key();
if (slot_index != -1 && filtered_indices.length() > 0) {
int item_type = item_types[filtered_indices[selection]];
if (slot_index >= 0 && slot_index < int(item_count_slots.length())) {
item_count_slots[slot_index] = item_type;
string name = get_item_count_binding_name(item_type);
speak_with_history(name + " count set to slot " + slot_index + ".", true);
}
}
if (allow_deposit && key_pressed(KEY_RETURN)) {
deposit_item(item_types[selection]);
build_personal_inventory_options(options, item_types);
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_indices.length() > 0) {
deposit_item(item_types[filtered_indices[selection]]);
build_personal_inventory_options(options, item_types);
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
if (selection >= int(filtered_options.length())) selection = 0;
if (filtered_options.length() > 0) {
speak_with_history(filtered_options[selection], true);
} else if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
}
}
}
if (allow_deposit && key_pressed(KEY_TAB)) {
deposit_item_max(item_types[selection]);
build_personal_inventory_options(options, item_types);
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_indices.length() > 0) {
deposit_item_max(item_types[filtered_indices[selection]]);
build_personal_inventory_options(options, item_types);
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
if (selection >= int(filtered_options.length())) selection = 0;
if (filtered_options.length() > 0) {
speak_with_history(filtered_options[selection], true);
} else if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
}
}
}
}
}
+73
View File
@@ -14,6 +14,7 @@ void menu_background_tick() {
update_bandits();
update_boars();
update_flying_creatures();
update_weapon_range_audio_all();
update_world_drops();
update_blessings();
update_notifications();
@@ -79,3 +80,75 @@ string get_base_fire_status() {
if (total == 0) return "No fires in base";
return "Fires in base: " + burning + " burning, " + total + " total";
}
string get_menu_filter_letter() {
if (key_pressed(KEY_A)) return "a";
if (key_pressed(KEY_B)) return "b";
if (key_pressed(KEY_C)) return "c";
if (key_pressed(KEY_D)) return "d";
if (key_pressed(KEY_E)) return "e";
if (key_pressed(KEY_F)) return "f";
if (key_pressed(KEY_G)) return "g";
if (key_pressed(KEY_H)) return "h";
if (key_pressed(KEY_I)) return "i";
if (key_pressed(KEY_J)) return "j";
if (key_pressed(KEY_K)) return "k";
if (key_pressed(KEY_L)) return "l";
if (key_pressed(KEY_M)) return "m";
if (key_pressed(KEY_N)) return "n";
if (key_pressed(KEY_O)) return "o";
if (key_pressed(KEY_P)) return "p";
if (key_pressed(KEY_Q)) return "q";
if (key_pressed(KEY_R)) return "r";
if (key_pressed(KEY_S)) return "s";
if (key_pressed(KEY_T)) return "t";
if (key_pressed(KEY_U)) return "u";
if (key_pressed(KEY_V)) return "v";
if (key_pressed(KEY_W)) return "w";
if (key_pressed(KEY_X)) return "x";
if (key_pressed(KEY_Y)) return "y";
if (key_pressed(KEY_Z)) return "z";
return "";
}
void apply_menu_filter(const string &in filter_text, const string[]@ options, int[]@ filtered_indices, string[]@ filtered_options) {
filtered_indices.resize(0);
filtered_options.resize(0);
string filter_lower = filter_text.lower();
for (uint i = 0; i < options.length(); i++) {
if (filter_lower.length() == 0) {
filtered_indices.insert_last(i);
filtered_options.insert_last(options[i]);
continue;
}
string option_lower = options[i].lower();
if (option_lower.length() >= filter_lower.length() && option_lower.substr(0, filter_lower.length()) == filter_lower) {
filtered_indices.insert_last(i);
filtered_options.insert_last(options[i]);
}
}
}
bool update_menu_filter_state(string &inout filter_text, const string[]@ options, int[]@ filtered_indices, string[]@ filtered_options, int &inout selection) {
bool filter_changed = false;
if (key_pressed(KEY_BACK) && filter_text.length() > 0) {
filter_text = filter_text.substr(0, filter_text.length() - 1);
filter_changed = true;
}
string filter_letter = get_menu_filter_letter();
if (filter_letter != "") {
filter_text += filter_letter;
filter_changed = true;
}
if (filter_changed) {
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
if (selection >= int(filtered_options.length())) selection = 0;
}
return filter_changed;
}
+49 -33
View File
@@ -39,25 +39,6 @@ int prompt_transfer_amount(const string prompt, int max_amount) {
return amount;
}
// Encode runed item info into a negative item type
// Format: -1000 - (equip_type * 10) - rune_type
// This allows us to extract both equip_type and rune_type from a single int
int encode_runed_item_type(int equip_type, int rune_type) {
return -1000 - (equip_type * 10) - rune_type;
}
// Decode a runed item type back to equip_type and rune_type
void decode_runed_item_type(int encoded, int& out equip_type, int& out rune_type) {
int decoded = -(encoded + 1000);
equip_type = decoded / 10;
rune_type = decoded % 10;
}
// Check if an item_type represents a runed item
bool is_runed_item_type(int item_type) {
return item_type <= -1000;
}
void deposit_item(int item_type) {
// Handle runed items
if (is_runed_item_type(item_type)) {
@@ -313,6 +294,10 @@ void run_storage_menu() {
string[] options;
int[] item_types;
build_storage_inventory_options(options, item_types);
string filter_text = "";
int[] filtered_indices;
string[] filtered_options;
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
while(true) {
wait(5);
@@ -322,30 +307,61 @@ void run_storage_menu() {
break;
}
bool filter_changed = update_menu_filter_state(filter_text, options, filtered_indices, filtered_options, selection);
if (filter_changed) {
if (filtered_options.length() == 0) {
if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
} else {
speak_with_history("No options.", true);
}
} else {
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_DOWN)) {
selection++;
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection++;
if (selection >= int(filtered_options.length())) selection = 0;
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_UP)) {
selection--;
if (selection < 0) selection = options.length() - 1;
speak_with_history(options[selection], true);
if (filtered_options.length() > 0) {
selection--;
if (selection < 0) selection = int(filtered_options.length()) - 1;
speak_with_history(filtered_options[selection], true);
}
}
if (key_pressed(KEY_RETURN)) {
withdraw_item(item_types[selection]);
build_storage_inventory_options(options, item_types);
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_indices.length() > 0) {
withdraw_item(item_types[filtered_indices[selection]]);
build_storage_inventory_options(options, item_types);
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
if (selection >= int(filtered_options.length())) selection = 0;
if (filtered_options.length() > 0) {
speak_with_history(filtered_options[selection], true);
} else if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
}
}
}
if (key_pressed(KEY_TAB)) {
withdraw_item_max(item_types[selection]);
build_storage_inventory_options(options, item_types);
if (selection >= options.length()) selection = 0;
speak_with_history(options[selection], true);
if (filtered_indices.length() > 0) {
withdraw_item_max(item_types[filtered_indices[selection]]);
build_storage_inventory_options(options, item_types);
apply_menu_filter(filter_text, options, filtered_indices, filtered_options);
if (selection >= int(filtered_options.length())) selection = 0;
if (filtered_options.length() > 0) {
speak_with_history(filtered_options[selection], true);
} else if (filter_text.length() > 0) {
speak_with_history("No matches for " + filter_text + ".", true);
}
}
}
}
}
+38 -5
View File
@@ -291,7 +291,7 @@ string serialize_stream(WorldStream@ stream) {
}
string serialize_bandit(Bandit@ bandit) {
return bandit.position + "|" + bandit.health + "|" + bandit.weapon_type + "|" + bandit.behavior_state + "|" + bandit.wander_direction + "|" + bandit.move_interval;
return bandit.position + "|" + bandit.health + "|" + bandit.weapon_type + "|" + bandit.behavior_state + "|" + bandit.wander_direction + "|" + bandit.move_interval + "|" + bandit.invader_type;
}
string serialize_mountain(MountainRange@ mountain) {
@@ -420,6 +420,12 @@ bool save_game_state() {
}
saveData.set("equipment_quick_slots", join_string_array(quickSlotData));
string[] itemCountSlotData;
for (uint i = 0; i < item_count_slots.length(); i++) {
itemCountSlotData.insert_last("" + item_count_slots[i]);
}
saveData.set("item_count_slots", join_string_array(itemCountSlotData));
// Rune system data
saveData.set("rune_swiftness_unlocked", rune_swiftness_unlocked);
saveData.set("unicorn_boss_defeated", unicorn_boss_defeated);
@@ -467,6 +473,7 @@ bool save_game_state() {
saveData.set("time_invasion_triggered_today", invasion_triggered_today);
saveData.set("time_invasion_roll_done_today", invasion_roll_done_today);
saveData.set("time_invasion_scheduled_hour", invasion_scheduled_hour);
saveData.set("time_invasion_enemy_type", invasion_enemy_type);
saveData.set("quest_roll_done_today", quest_roll_done_today);
string[] questData;
for (uint i = 0; i < quest_queue.length(); i++) {
@@ -735,6 +742,18 @@ bool load_game_state() {
}
}
string[] loadedItemCountSlots = get_string_list_or_split(saveData, "item_count_slots");
uint count_slot_count = loadedItemCountSlots.length();
if (count_slot_count > item_count_slots.length()) count_slot_count = item_count_slots.length();
for (uint i = 0; i < count_slot_count; i++) {
int slot_value = parse_int(loadedItemCountSlots[i]);
if (slot_value >= 0 && slot_value < ITEM_COUNT) {
item_count_slots[i] = slot_value;
} else if (is_runed_item_type(slot_value)) {
item_count_slots[i] = slot_value;
}
}
// Load rune system data
rune_swiftness_unlocked = get_bool(saveData, "rune_swiftness_unlocked", false);
unicorn_boss_defeated = get_bool(saveData, "unicorn_boss_defeated", false);
@@ -825,6 +844,15 @@ bool load_game_state() {
invasion_triggered_today = get_bool(saveData, "time_invasion_triggered_today", false);
invasion_roll_done_today = get_bool(saveData, "time_invasion_roll_done_today", false);
invasion_scheduled_hour = int(get_number(saveData, "time_invasion_scheduled_hour", -1));
string loaded_invasion_type;
if (saveData.get("time_invasion_enemy_type", loaded_invasion_type)) {
invasion_enemy_type = loaded_invasion_type;
} else {
invasion_enemy_type = "bandit";
}
if (invasion_enemy_type == "") {
invasion_enemy_type = "bandit";
}
if (invasion_chance < 0) invasion_chance = 0;
if (invasion_chance > 100) invasion_chance = 100;
if (invasion_scheduled_hour < -1) invasion_scheduled_hour = -1;
@@ -959,23 +987,28 @@ bool load_game_state() {
string state = parts[3];
int wander_dir = parse_int(parts[4]);
int move_int = parse_int(parts[5]);
string invader_type = "bandit";
if (parts.length() >= 7) {
invader_type = parts[6];
if (invader_type == "") invader_type = "bandit";
}
// Create bandit with dummy expansion area (position will be overridden)
Bandit@ b = Bandit(pos, pos, pos);
Bandit@ b = Bandit(pos, pos, pos, invader_type);
b.position = pos;
b.health = health;
b.weapon_type = weapon;
b.behavior_state = state;
b.wander_direction = wander_dir;
b.move_interval = move_int;
b.invader_type = invader_type;
b.wander_direction_change_interval = random(BANDIT_WANDER_DIRECTION_CHANGE_MIN, BANDIT_WANDER_DIRECTION_CHANGE_MAX);
b.wander_direction_timer.restart();
b.move_timer.restart();
b.attack_timer.restart();
// Restore alert sound based on weapon type
int sound_index = random(0, bandit_sounds.length() - 1);
b.alert_sound = bandit_sounds[sound_index];
// Restore alert sound based on invader type
b.alert_sound = pick_invader_alert_sound(invader_type);
bandits.insert_last(b);
// Start looping sound for loaded bandit
+48 -10
View File
@@ -29,6 +29,12 @@ int invasion_chance = 25;
bool invasion_triggered_today = false;
bool invasion_roll_done_today = false;
int invasion_scheduled_hour = -1;
string invasion_enemy_type = "bandit";
// Invasion mapping: "terrain=enemy" (defaults to bandits when no match)
// Terrain keys: "mountain" for mountain ranges, or regular types like "grass", "snow", "forest", "deep_forest", "stone".
string default_invasion_enemy_type = "bandit";
string[] invasion_terrain_enemy_map = {"mountain=goblin"};
void init_time() {
current_hour = 8;
@@ -46,10 +52,37 @@ void init_time() {
invasion_triggered_today = false;
invasion_roll_done_today = false;
invasion_scheduled_hour = -1;
invasion_enemy_type = "bandit";
update_ambience(true); // Force start
}
void expand_area() {
string get_invasion_enemy_type_for_terrain(const string&in terrain_type) {
for (uint i = 0; i < invasion_terrain_enemy_map.length(); i++) {
string entry = invasion_terrain_enemy_map[i];
int eq_pos = entry.find("=");
if (eq_pos <= 0) {
continue;
}
string key = entry.substr(0, eq_pos);
string value = entry.substr(eq_pos + 1);
if (key == terrain_type && value != "") {
return value;
}
}
return default_invasion_enemy_type;
}
string get_invasion_enemy_label(const string&in enemy_type) {
if (enemy_type != "") return enemy_type;
return default_invasion_enemy_type;
}
string get_invasion_enemy_plural(const string&in enemy_type) {
return get_invasion_enemy_label(enemy_type) + "s";
}
string expand_area() {
// Play invasion sound
p.play_stationary("sounds/enemies/invasion.ogg", false);
@@ -57,12 +90,13 @@ void expand_area() {
int type_roll = random(0, 3);
if (type_roll == 0) {
expand_mountain();
} else {
expand_regular_area();
return "mountain";
}
return expand_regular_area();
}
void expand_regular_area() {
string expand_regular_area() {
// Calculate new area
int new_start = MAP_SIZE;
int new_end = MAP_SIZE + EXPANSION_SIZE - 1;
@@ -120,6 +154,7 @@ void expand_regular_area() {
area_expanded_today = true;
notify("The area has expanded! New territory discovered to the east.");
return terrain_type;
}
void expand_mountain() {
@@ -147,10 +182,13 @@ void expand_mountain() {
}
void start_invasion() {
expand_area();
string expansion_terrain = expand_area();
invasion_active = true;
invasion_start_hour = current_hour;
notify("Bandits are invading from the new area!");
invasion_enemy_type = get_invasion_enemy_type_for_terrain(expansion_terrain);
string source = (expansion_terrain == "mountain") ? "the mountains" : "the new area";
string enemy_plural = get_invasion_enemy_plural(invasion_enemy_type);
notify(enemy_plural + " are invading from " + source + "!");
}
void update_invasion_chance_for_new_day() {
@@ -268,7 +306,7 @@ void end_invasion() {
invasion_active = false;
invasion_start_hour = -1;
transition_bandits_to_wandering();
notify("The bandit invasion has ended.");
notify("The " + get_invasion_enemy_label(invasion_enemy_type) + " invasion has ended.");
attempt_resident_recruitment();
}
@@ -302,10 +340,10 @@ void manage_bandits_during_invasion() {
return;
}
// During daytime: if invasion is active, maintain bandit count
// During daytime: if invasion is active, maintain invader count
if (invasion_active && expanded_area_start != -1) {
while (bandits.length() < BANDIT_MAX_COUNT) {
spawn_bandit(expanded_area_start, expanded_area_end);
spawn_bandit(expanded_area_start, expanded_area_end, invasion_enemy_type);
}
}
// If invasion not active, wandering bandits persist during daytime
@@ -448,7 +486,7 @@ void update_time() {
// Proactive resident defense with slings
attempt_resident_sling_defense();
// Manage bandits during active invasion
// Manage invasion enemies during active invasion
manage_bandits_during_invasion();
}
+60
View File
@@ -0,0 +1,60 @@
// Weapon range enter/exit audio system
// Central registry so new enemies only register a callback once
funcdef void WeaponRangeAudioCallback();
WeaponRangeAudioCallback@[] weapon_range_audio_callbacks;
bool weapon_range_audio_initialized = false;
void init_weapon_range_audio_registry() {
if (weapon_range_audio_initialized) return;
weapon_range_audio_callbacks.resize(0);
weapon_range_audio_initialized = true;
}
bool register_weapon_range_audio_callback(WeaponRangeAudioCallback@ callback) {
init_weapon_range_audio_registry();
if (@callback is null) return false;
weapon_range_audio_callbacks.insert_last(callback);
return true;
}
int get_current_weapon_range() {
if (sling_equipped) return SLING_RANGE;
if (bow_equipped) return BOW_RANGE;
if (spear_equipped) return 1;
if (axe_equipped) return 0;
return -1;
}
void play_weapon_range_sound(string sound_file, int creature_pos) {
if (!file_exists(sound_file)) return;
// Notification sound: play at player position for consistent perception
play_1d_with_volume_step(sound_file, x, x, false, PLAYER_WEAPON_SOUND_VOLUME_STEP);
}
void update_weapon_range_audio(int creature_pos, bool &inout was_in_range) {
int range = get_current_weapon_range();
bool in_range = (range >= 0) && (abs(creature_pos - x) <= range);
if (in_range && !was_in_range) {
play_weapon_range_sound("sounds/enemies/enter_range.ogg", creature_pos);
} else if (!in_range && was_in_range) {
play_weapon_range_sound("sounds/enemies/exit_range.ogg", creature_pos);
}
was_in_range = in_range;
}
void force_weapon_range_exit(int creature_pos, bool &inout was_in_range) {
if (!was_in_range) return;
play_weapon_range_sound("sounds/enemies/exit_range.ogg", creature_pos);
was_in_range = false;
}
void update_weapon_range_audio_all() {
for (uint i = 0; i < weapon_range_audio_callbacks.length(); i++) {
WeaponRangeAudioCallback@ callback = weapon_range_audio_callbacks[i];
if (@callback !is null) {
callback();
}
}
}