Wire Draugnorak to reusable libstorm modules
Replace local ui/speech/text reader/notification modules with libstorm-nvgt integrations, keeping game-specific terrain lookup local via a new module.\n\nAdd compatibility layers for notifications and text_reader callsites, switch menu prefix filtering to shared menu helpers, and update the libstorm-nvgt submodule to include the learn_sounds select-sound toggle.
This commit is contained in:
+33
-249
@@ -1,268 +1,52 @@
|
||||
// Learn sounds menu
|
||||
#include "libstorm-nvgt/learn_sounds.nvgt"
|
||||
#include "libstorm-nvgt/docs_browser.nvgt"
|
||||
#include "excluded_sounds.nvgt"
|
||||
|
||||
// Skip entries can be full file paths or directories (ending with "/").
|
||||
string[] learnSoundSkipList = {
|
||||
"sounds/quests/bone1.ogg",
|
||||
"sounds/quests/bone2.ogg",
|
||||
"sounds/quests/bone3.ogg",
|
||||
"sounds/quests/bone4.ogg",
|
||||
"sounds/quests/bone5.ogg",
|
||||
"sounds/quests/bone6.ogg",
|
||||
"sounds/quests/bone7.ogg",
|
||||
"sounds/quests/bone8.ogg",
|
||||
"sounds/actions/fishpole.ogg",
|
||||
"sounds/actions/hit_ground.ogg",
|
||||
"sounds/menu/",
|
||||
"sounds/nature/",
|
||||
"sounds/pets/"
|
||||
};
|
||||
bool learnSoundsConfigured = false;
|
||||
bool docsBrowserConfigured = false;
|
||||
|
||||
// Description entries: keep paths/texts aligned by index.
|
||||
string[] learnSoundDescriptionPaths = {
|
||||
"sounds/actions/bad_cast.ogg",
|
||||
"sounds/actions/cast_strength.ogg",
|
||||
"sounds/enemies/enter_range.ogg",
|
||||
"sounds/enemies/exit_range.ogg",
|
||||
"sounds/actions/falling.ogg",
|
||||
"sounds/enemies/invasion.ogg",
|
||||
"sounds/items/miscellaneous.ogg"
|
||||
};
|
||||
|
||||
string[] learnSoundDescriptionTexts = {
|
||||
"Your cast missed the water and landed in nearby foliage where it is unlikely to attract fish.",
|
||||
"When casting release control when over water. When catching release when sound is over player.",
|
||||
"An enemy is in range of your currently wielded weapon.",
|
||||
"An enemy is no longer in range of your currently wielded weapon.",
|
||||
"Lowers in pitch as the fall progresses.",
|
||||
"The war drums pound! Prepare for combat!",
|
||||
"You picked up an item for which there is no specific sound."
|
||||
};
|
||||
|
||||
int learnSoundHandle = -1;
|
||||
|
||||
string normalize_path(const string&in path) {
|
||||
return path.replace("\\", "/", true);
|
||||
void learn_sounds_bridge_speak(const string &in message, bool interrupt) {
|
||||
speak_with_history(message, interrupt);
|
||||
}
|
||||
|
||||
bool is_directory_skip_entry(const string&in entry) {
|
||||
if (entry.length() == 0) return false;
|
||||
if (entry.substr(entry.length() - 1) == "/") return true;
|
||||
return directory_exists(entry);
|
||||
void learn_sounds_bridge_tick() {
|
||||
handle_global_volume_keys();
|
||||
}
|
||||
|
||||
bool should_skip_sound_path(const string&in path) {
|
||||
string normalizedPath = normalize_path(path);
|
||||
for (uint i = 0; i < learnSoundSkipList.length(); i++) {
|
||||
string entry = normalize_path(learnSoundSkipList[i]);
|
||||
if (entry.length() == 0) continue;
|
||||
void configure_docs_browser_if_needed() {
|
||||
if (docsBrowserConfigured) return;
|
||||
|
||||
bool isDir = is_directory_skip_entry(entry);
|
||||
if (isDir) {
|
||||
if (entry.substr(entry.length() - 1) != "/") entry += "/";
|
||||
if (normalizedPath.length() >= entry.length() &&
|
||||
normalizedPath.substr(0, entry.length()) == entry) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
docs_browser_set_speak_callback(learn_sounds_bridge_speak);
|
||||
docs_browser_set_tick_callback(learn_sounds_bridge_tick);
|
||||
docs_browser_set_docs_dir("files");
|
||||
docs_browser_set_menu_sound_dir("sounds/menu");
|
||||
docs_browser_set_wrap(true);
|
||||
docs_browser_reset_default_extensions();
|
||||
|
||||
if (normalizedPath == entry) return true;
|
||||
|
||||
if (entry.find_first("/") < 0 && entry.find_first("\\") < 0) {
|
||||
if (normalizedPath.length() >= entry.length() + 1 &&
|
||||
normalizedPath.substr(normalizedPath.length() - entry.length()) == entry) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string get_sound_description(const string&in path) {
|
||||
string normalizedPath = normalize_path(path);
|
||||
uint count = learnSoundDescriptionPaths.length();
|
||||
if (learnSoundDescriptionTexts.length() < count) count = learnSoundDescriptionTexts.length();
|
||||
for (uint i = 0; i < count; i++) {
|
||||
if (normalize_path(learnSoundDescriptionPaths[i]) == normalizedPath) {
|
||||
return learnSoundDescriptionTexts[i];
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void gather_sound_files(const string&in basePath, string[]@ outFiles) {
|
||||
string[]@ files = find_files(basePath + "/*.ogg");
|
||||
if (@files !is null) {
|
||||
for (uint i = 0; i < files.length(); i++) {
|
||||
outFiles.insert_last(basePath + "/" + files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
string[]@ folders = find_directories(basePath + "/*");
|
||||
if (@folders !is null) {
|
||||
for (uint i = 0; i < folders.length(); i++) {
|
||||
gather_sound_files(basePath + "/" + folders[i], outFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool sort_string_case_insensitive_sounds(const string &in a, const string &in b) {
|
||||
return a.lower() < b.lower();
|
||||
}
|
||||
|
||||
void append_unique_case_insensitive(string[]@ items, const string&in value) {
|
||||
string lowerValue = value.lower();
|
||||
for (uint i = 0; i < items.length(); i++) {
|
||||
if (items[i].lower() == lowerValue) return;
|
||||
}
|
||||
items.insert_last(value);
|
||||
}
|
||||
|
||||
string collapse_spaces(const string&in text) {
|
||||
string result = text;
|
||||
while (result.find_first(" ") > -1) {
|
||||
result = result.replace(" ", " ", true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string format_doc_label(const string&in filename) {
|
||||
string name = filename;
|
||||
string lowerName = name.lower();
|
||||
if (lowerName.length() > 3 && lowerName.substr(lowerName.length() - 3) == ".md") {
|
||||
name = name.substr(0, name.length() - 3);
|
||||
}
|
||||
name = name.replace("_", " ", true);
|
||||
name = name.replace("-", " ", true);
|
||||
name = collapse_spaces(name);
|
||||
name = name.lower();
|
||||
name.trim_whitespace_this();
|
||||
if (name.length() == 0) return "Document";
|
||||
string first = name.substr(0, 1).upper();
|
||||
if (name.length() == 1) return first;
|
||||
return first + name.substr(1);
|
||||
}
|
||||
|
||||
string get_sound_label_from_path(const string&in soundPath) {
|
||||
string normalizedPath = normalize_path(soundPath);
|
||||
int slashPos = normalizedPath.find_last_of("/");
|
||||
string name = (slashPos >= 0) ? normalizedPath.substr(slashPos + 1) : normalizedPath;
|
||||
string lowerName = name.lower();
|
||||
if (lowerName.length() > 4 && lowerName.substr(lowerName.length() - 4) == ".ogg") {
|
||||
name = name.substr(0, name.length() - 4);
|
||||
}
|
||||
name = name.replace("_", " ", true);
|
||||
name = name.replace("-", " ", true);
|
||||
name = collapse_spaces(name);
|
||||
name = name.lower();
|
||||
name.trim_whitespace_this();
|
||||
if (name.length() == 0) return "Sound";
|
||||
return name;
|
||||
docsBrowserConfigured = true;
|
||||
}
|
||||
|
||||
void add_doc_entries(string[]@ labels, string[]@ paths, int[]@ types) {
|
||||
if (!directory_exists("files")) return;
|
||||
|
||||
string[] docFiles;
|
||||
string[]@ mdFiles = find_files("files/*.md");
|
||||
if (@mdFiles !is null) {
|
||||
for (uint i = 0; i < mdFiles.length(); i++) {
|
||||
append_unique_case_insensitive(docFiles, mdFiles[i]);
|
||||
}
|
||||
}
|
||||
string[]@ mdCapsFiles = find_files("files/*.MD");
|
||||
if (@mdCapsFiles !is null) {
|
||||
for (uint i = 0; i < mdCapsFiles.length(); i++) {
|
||||
append_unique_case_insensitive(docFiles, mdCapsFiles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (docFiles.length() > 1) {
|
||||
docFiles.sort(sort_string_case_insensitive_sounds);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < docFiles.length(); i++) {
|
||||
string label = format_doc_label(docFiles[i]);
|
||||
labels.insert_last(label);
|
||||
paths.insert_last("files/" + docFiles[i]);
|
||||
types.insert_last(1);
|
||||
}
|
||||
configure_docs_browser_if_needed();
|
||||
docs_browser_add_entries(labels, paths, types, 1);
|
||||
}
|
||||
|
||||
void add_sound_entries(string[]@ labels, string[]@ paths, int[]@ types) {
|
||||
string[] soundFiles;
|
||||
gather_sound_files("sounds", soundFiles);
|
||||
if (soundFiles.length() > 1) {
|
||||
soundFiles.sort(sort_string_case_insensitive_sounds);
|
||||
}
|
||||
void configure_learn_sounds_if_needed() {
|
||||
if (learnSoundsConfigured) return;
|
||||
|
||||
for (uint i = 0; i < soundFiles.length(); i++) {
|
||||
string soundPath = normalize_path(soundFiles[i]);
|
||||
if (should_skip_sound_path(soundPath)) continue;
|
||||
learn_sounds_set_speak_callback(learn_sounds_bridge_speak);
|
||||
learn_sounds_set_tick_callback(learn_sounds_bridge_tick);
|
||||
learn_sounds_set_root_dir("sounds");
|
||||
learn_sounds_set_menu_sound_dir("sounds/menu");
|
||||
learn_sounds_set_wrap(true);
|
||||
learn_sounds_set_play_select_sound(false);
|
||||
|
||||
string label = get_sound_label_from_path(soundPath);
|
||||
string description = get_sound_description(soundPath);
|
||||
if (description.length() > 0) {
|
||||
label += " - " + description;
|
||||
}
|
||||
labels.insert_last(label);
|
||||
paths.insert_last(soundPath);
|
||||
types.insert_last(0);
|
||||
}
|
||||
configure_project_learn_sounds();
|
||||
|
||||
learnSoundsConfigured = true;
|
||||
}
|
||||
|
||||
void run_learn_sounds_menu() {
|
||||
speak_with_history("Learn sounds.", true);
|
||||
|
||||
string[] labels;
|
||||
string[] entryPaths;
|
||||
int[] entryTypes; // 0 = sound
|
||||
|
||||
add_sound_entries(labels, entryPaths, entryTypes);
|
||||
|
||||
if (labels.length() == 0) {
|
||||
speak_with_history("No sounds available.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
int selection = 0;
|
||||
speak_with_history(labels[selection], true);
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
speak_with_history("Closed.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_DOWN)) {
|
||||
play_menu_move_sound();
|
||||
selection++;
|
||||
if (selection >= int(labels.length())) selection = 0;
|
||||
speak_with_history(labels[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_UP)) {
|
||||
play_menu_move_sound();
|
||||
selection--;
|
||||
if (selection < 0) selection = int(labels.length()) - 1;
|
||||
speak_with_history(labels[selection], true);
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN)) {
|
||||
if (entryTypes[selection] == 1) {
|
||||
text_reader_file(entryPaths[selection], labels[selection], true);
|
||||
return;
|
||||
}
|
||||
string soundPath = entryPaths[selection];
|
||||
if (file_exists(soundPath)) {
|
||||
safe_destroy_sound(learnSoundHandle);
|
||||
learnSoundHandle = p.play_stationary(soundPath, false);
|
||||
} else {
|
||||
speak_with_history("Sound not found.", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
configure_learn_sounds_if_needed();
|
||||
learn_sounds_run_menu();
|
||||
}
|
||||
|
||||
@@ -105,73 +105,13 @@ string get_base_fire_status() {
|
||||
}
|
||||
|
||||
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 "";
|
||||
return menu_get_filter_letter();
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
menu_apply_prefix_filter(filter_text, options, filtered_indices, filtered_options);
|
||||
}
|
||||
|
||||
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;
|
||||
return menu_update_prefix_filter(filter_text, options, filtered_indices, filtered_options, selection);
|
||||
}
|
||||
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
// Notification System
|
||||
string[] notification_history;
|
||||
const int MAX_NOTIFICATIONS = 10;
|
||||
const int NOTIFICATION_DELAY = 3000; // 3 seconds between notifications
|
||||
int current_notification_index = -1;
|
||||
string[] notification_queue;
|
||||
timer notification_timer;
|
||||
bool notification_active = false;
|
||||
int notification_sound_handle = -1;
|
||||
|
||||
void notify(string message) {
|
||||
// Add to queue (don't play yet)
|
||||
notification_queue.insert_last(message);
|
||||
|
||||
// Add to history immediately so it appears in history even if queued
|
||||
notification_history.insert_last(message);
|
||||
|
||||
// Keep only last 10 notifications
|
||||
if (notification_history.length() > MAX_NOTIFICATIONS) {
|
||||
notification_history.remove_at(0);
|
||||
}
|
||||
|
||||
// Reset index to most recent
|
||||
current_notification_index = notification_history.length() - 1;
|
||||
}
|
||||
|
||||
void update_notifications() {
|
||||
if (notification_queue.length() == 0) {
|
||||
if (notification_active && notification_timer.elapsed >= NOTIFICATION_DELAY) {
|
||||
notification_active = false;
|
||||
}
|
||||
notification_sound_handle = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// If a notification is currently active, wait for delay
|
||||
if (notification_active && notification_timer.elapsed < NOTIFICATION_DELAY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're waiting for the notification sound to finish playing
|
||||
if (notification_sound_handle != -1) {
|
||||
// Check if sound is still playing
|
||||
if (p.sound_is_active(notification_sound_handle)) {
|
||||
return; // Still playing, wait
|
||||
}
|
||||
// Sound finished, now speak
|
||||
speak_with_history(notification_queue[0], true);
|
||||
notification_queue.remove_at(0);
|
||||
notification_sound_handle = -1;
|
||||
|
||||
// Start timer for next notification
|
||||
notification_timer.restart();
|
||||
notification_active = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Play next notification sound (don't speak yet)
|
||||
notification_sound_handle = p.play_stationary("sounds/notify.ogg", false);
|
||||
}
|
||||
|
||||
void check_notification_keys() {
|
||||
// [ for previous notification (older) with position
|
||||
if (key_pressed(KEY_LEFTBRACKET)) {
|
||||
if (notification_history.length() == 0) {
|
||||
speak_with_history("No notifications.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
current_notification_index--;
|
||||
if (current_notification_index < 0) {
|
||||
current_notification_index = 0;
|
||||
speak_with_history("Oldest notification. " + notification_history[current_notification_index], true);
|
||||
return;
|
||||
}
|
||||
int position = current_notification_index + 1;
|
||||
speak_with_history(notification_history[current_notification_index] + " " + position + " of " + notification_history.length(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// ] for next notification (newer) with position
|
||||
if (key_pressed(KEY_RIGHTBRACKET)) {
|
||||
if (notification_history.length() == 0) {
|
||||
speak_with_history("No notifications.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
current_notification_index++;
|
||||
if (current_notification_index >= notification_history.length()) {
|
||||
current_notification_index = notification_history.length() - 1;
|
||||
speak_with_history("Newest notification. " + notification_history[current_notification_index], true);
|
||||
return;
|
||||
}
|
||||
int position = current_notification_index + 1;
|
||||
speak_with_history(notification_history[current_notification_index] + " " + position + " of " + notification_history.length(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// \ for most recent notification (without position)
|
||||
if (key_pressed(KEY_BACKSLASH)) {
|
||||
if (notification_history.length() == 0) {
|
||||
speak_with_history("No notifications.", true);
|
||||
return;
|
||||
}
|
||||
current_notification_index = notification_history.length() - 1;
|
||||
speak_with_history(notification_history[current_notification_index], true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "libstorm-nvgt/notifications.nvgt"
|
||||
|
||||
const int NOTIFICATION_HISTORY_LIMIT = 10;
|
||||
const int NOTIFICATION_DELAY_MS = 3000;
|
||||
const string NOTIFICATION_SOUND_PATH = "sounds/notify";
|
||||
|
||||
bool notificationsCompatConfigured = false;
|
||||
|
||||
void notify_compat_speak(const string &in message, bool interrupt) {
|
||||
speak_with_history(message, interrupt);
|
||||
}
|
||||
|
||||
void configure_notification_compat_if_needed() {
|
||||
if (notificationsCompatConfigured) return;
|
||||
|
||||
notifications_set_max_history(NOTIFICATION_HISTORY_LIMIT);
|
||||
notifications_set_delay_ms(NOTIFICATION_DELAY_MS);
|
||||
notifications_set_sound_path(NOTIFICATION_SOUND_PATH);
|
||||
notifications_set_speak_callback(notify_compat_speak);
|
||||
notificationsCompatConfigured = true;
|
||||
}
|
||||
|
||||
void notify(string message) {
|
||||
configure_notification_compat_if_needed();
|
||||
notifications_enqueue(message);
|
||||
}
|
||||
|
||||
void update_notifications() {
|
||||
configure_notification_compat_if_needed();
|
||||
notifications_update();
|
||||
}
|
||||
|
||||
void check_notification_keys() {
|
||||
configure_notification_compat_if_needed();
|
||||
notifications_check_keys();
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Speech History System
|
||||
// Tracks last 10 unique screen reader announcements for navigation with comma/period
|
||||
|
||||
string[] speech_history;
|
||||
const int MAX_SPEECH_HISTORY = 10;
|
||||
int current_speech_index = -1;
|
||||
|
||||
// Main speak function - wrapper around screen_reader_speak with history tracking
|
||||
void speak_with_history(string message, bool interrupt) {
|
||||
// Only add to history if not already present (no duplicates)
|
||||
bool already_exists = false;
|
||||
for (uint i = 0; i < speech_history.length(); i++) {
|
||||
if (speech_history[i] == message) {
|
||||
already_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to history if not a duplicate
|
||||
if (!already_exists) {
|
||||
speech_history.insert_last(message);
|
||||
|
||||
// Keep only last 10 messages
|
||||
if (speech_history.length() > MAX_SPEECH_HISTORY) {
|
||||
speech_history.remove_at(0);
|
||||
}
|
||||
|
||||
// Reset index to most recent
|
||||
current_speech_index = speech_history.length() - 1;
|
||||
}
|
||||
|
||||
// Call the built-in screen_reader_speak function
|
||||
screen_reader_speak(message, interrupt);
|
||||
}
|
||||
|
||||
void check_speech_history_keys() {
|
||||
// , (comma) for previous speech message (older)
|
||||
if (key_pressed(KEY_COMMA)) {
|
||||
if (speech_history.length() == 0) {
|
||||
screen_reader_speak("No speech history.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
current_speech_index--;
|
||||
if (current_speech_index < 0) {
|
||||
current_speech_index = 0;
|
||||
screen_reader_speak("Oldest message. " + speech_history[current_speech_index], true);
|
||||
return;
|
||||
}
|
||||
int position = current_speech_index + 1;
|
||||
screen_reader_speak(speech_history[current_speech_index] + " " + position + " of " + speech_history.length(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// . (period) for next speech message (newer)
|
||||
if (key_pressed(KEY_PERIOD)) {
|
||||
if (speech_history.length() == 0) {
|
||||
screen_reader_speak("No speech history.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
current_speech_index++;
|
||||
if (current_speech_index >= speech_history.length()) {
|
||||
current_speech_index = speech_history.length() - 1;
|
||||
screen_reader_speak("Newest message. " + speech_history[current_speech_index], true);
|
||||
return;
|
||||
}
|
||||
int position = current_speech_index + 1;
|
||||
screen_reader_speak(speech_history[current_speech_index] + " " + position + " of " + speech_history.length(), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Game-specific terrain lookup used by movement, menus, and reports.
|
||||
string get_terrain_at_position(int posX) {
|
||||
if (is_position_in_water(posX) || is_mountain_stream_at(posX)) {
|
||||
return "water";
|
||||
}
|
||||
|
||||
MountainRange@ mountain = get_mountain_at(posX);
|
||||
if (mountain !is null) {
|
||||
return mountain.get_terrain_at(posX);
|
||||
}
|
||||
|
||||
if (posX <= BASE_END) return "wood";
|
||||
if (posX <= GRASS_END) return "grass";
|
||||
if (posX <= GRAVEL_END) return "gravel";
|
||||
|
||||
int index = posX - expanded_area_start;
|
||||
if (index >= 0 && index < int(expanded_terrain_types.length())) {
|
||||
string terrain = expanded_terrain_types[index];
|
||||
if (terrain.find("mountain:") == 0) {
|
||||
terrain = terrain.substr(9);
|
||||
}
|
||||
return terrain;
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
// text_reader.nvgt - Simple text document reader/editor using NVGT's audio_form
|
||||
// Provides accessible navigation through text documents with optional editing
|
||||
|
||||
#include "form.nvgt"
|
||||
|
||||
// Opens a text reader/editor window with string content
|
||||
// Parameters:
|
||||
// content: The text content to display (can be file contents or direct string)
|
||||
// title: Window title (default: "Text Reader")
|
||||
// readonly: If true, text cannot be edited (default: true)
|
||||
// Returns: The modified text if readonly=false and user presses OK, empty string if canceled or readonly=true
|
||||
string text_reader(string content, string title = "Text Reader", bool readonly = true) {
|
||||
audio_form f;
|
||||
f.create_window(title, false, true);
|
||||
|
||||
// Create the multiline input box
|
||||
// In readonly mode, it's still navigable with arrows/home/end/etc
|
||||
// In edit mode, user can modify text
|
||||
int text_control = f.create_input_box(
|
||||
(readonly ? "Document (read only)" : "Document (editable)"),
|
||||
content,
|
||||
"", // no password mask
|
||||
0, // no max length
|
||||
readonly,
|
||||
true, // multiline = true
|
||||
true // multiline_enter = true (Ctrl+Enter for newlines)
|
||||
);
|
||||
|
||||
int ok_button = -1;
|
||||
int close_button = -1;
|
||||
|
||||
if (readonly) {
|
||||
// In readonly mode, just have a Close button
|
||||
close_button = f.create_button("&Close", true, true);
|
||||
} else {
|
||||
// In edit mode, have OK and Cancel buttons
|
||||
ok_button = f.create_button("&OK", true);
|
||||
close_button = f.create_button("&Cancel", false, true);
|
||||
}
|
||||
|
||||
f.focus(text_control);
|
||||
|
||||
// Monitor loop
|
||||
while (true) {
|
||||
f.monitor();
|
||||
wait(5);
|
||||
handle_global_volume_keys();
|
||||
|
||||
// Check if user pressed OK (edit mode only)
|
||||
if (!readonly && ok_button != -1 && f.is_pressed(ok_button)) {
|
||||
return f.get_text(text_control);
|
||||
}
|
||||
|
||||
// Check if user pressed Close/Cancel
|
||||
if (close_button != -1 && f.is_pressed(close_button)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Check for Escape key as alternative close method
|
||||
if (key_pressed(KEY_ESCAPE)) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// Opens a text reader/editor window with an array of lines
|
||||
// Parameters:
|
||||
// lines: Array of text lines to display
|
||||
// title: Window title (default: "Text Reader")
|
||||
// readonly: If true, text cannot be edited (default: true)
|
||||
// Returns: The modified text if readonly=false and user presses OK, empty string if canceled or readonly=true
|
||||
string text_reader_lines(string[] lines, string title = "Text Reader", bool readonly = true) {
|
||||
string content = join(lines, "\n");
|
||||
return text_reader(content, title, readonly);
|
||||
}
|
||||
|
||||
// Convenience function to read a file and display it in the text reader
|
||||
// Parameters:
|
||||
// file_path: Path to the file to read
|
||||
// title: Window title (default: uses file_path as title)
|
||||
// readonly: If true, text cannot be edited (default: true)
|
||||
// Returns: The modified text if readonly=false and user saves, empty string otherwise
|
||||
string text_reader_file(string file_path, string title = "", bool readonly = true) {
|
||||
file f;
|
||||
if (!f.open(file_path, "rb")) {
|
||||
screen_reader_speak("Failed to open file: " + file_path, true);
|
||||
return "";
|
||||
}
|
||||
|
||||
string content = f.read();
|
||||
f.close();
|
||||
|
||||
// Use file_path as title if no custom title provided
|
||||
if (title == "") {
|
||||
title = file_path;
|
||||
}
|
||||
|
||||
string result = text_reader(content, title, readonly);
|
||||
|
||||
// If in edit mode and user pressed OK, save the file
|
||||
if (!readonly && result != "") {
|
||||
if (f.open(file_path, "wb")) {
|
||||
f.write(result);
|
||||
f.close();
|
||||
screen_reader_speak("File saved successfully", true);
|
||||
return result;
|
||||
} else {
|
||||
screen_reader_speak("Failed to save file", true);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Compatibility aliases that keep legacy text_reader* callsites unchanged.
|
||||
// file_viewer* is provided by libstorm-nvgt/docs_browser.nvgt.
|
||||
string text_reader(string content, string title = "Text Reader", bool readOnly = true) {
|
||||
return file_viewer(content, title, readOnly);
|
||||
}
|
||||
|
||||
string text_reader_lines(string[] lines, string title = "Text Reader", bool readOnly = true) {
|
||||
return file_viewer_lines(lines, title, readOnly);
|
||||
}
|
||||
|
||||
string text_reader_file(string filePath, string title = "", bool readOnly = true) {
|
||||
return file_viewer_file(filePath, title, readOnly);
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
// UI helpers
|
||||
string get_terrain_at_position(int pos_x) {
|
||||
// Check for water first (streams in expanded areas or mountain streams)
|
||||
if (is_position_in_water(pos_x) || is_mountain_stream_at(pos_x)) {
|
||||
return "water";
|
||||
}
|
||||
|
||||
// Check mountain terrain
|
||||
MountainRange@ mountain = get_mountain_at(pos_x);
|
||||
if (mountain !is null) {
|
||||
return mountain.get_terrain_at(pos_x);
|
||||
}
|
||||
|
||||
// Base area
|
||||
if (pos_x <= BASE_END) return "wood";
|
||||
|
||||
// Grass area
|
||||
if (pos_x <= GRASS_END) return "grass";
|
||||
|
||||
// Gravel area
|
||||
if (pos_x <= GRAVEL_END) return "gravel";
|
||||
|
||||
// Expanded areas
|
||||
int index = pos_x - expanded_area_start;
|
||||
if (index >= 0 && index < int(expanded_terrain_types.length())) {
|
||||
string terrain = expanded_terrain_types[index];
|
||||
// Handle "mountain:terrain" format from older saves
|
||||
if (terrain.find("mountain:") == 0) {
|
||||
terrain = terrain.substr(9);
|
||||
}
|
||||
return terrain;
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
string ui_input_box(const string title, const string prompt, const string default_value) {
|
||||
string result = virtual_input_box(prompt, prompt, default_value);
|
||||
show_window("Draugnorak");
|
||||
return result;
|
||||
}
|
||||
|
||||
int ui_question(const string title, const string prompt) {
|
||||
// Put the prompt in both title (for screen reader) and message (for dialog to work)
|
||||
int result = virtual_question(prompt, prompt);
|
||||
show_window("Draugnorak");
|
||||
return result;
|
||||
}
|
||||
|
||||
void ui_info_box(const string title, const string heading, const string message) {
|
||||
virtual_info_box(title, heading, message);
|
||||
show_window("Draugnorak");
|
||||
}
|
||||
Reference in New Issue
Block a user