Character dialog added. First attempt, let's see what happens.
This commit is contained in:
23
README.md
23
README.md
@@ -9,6 +9,7 @@ Reusable NVGT helpers for Storm projects.
|
||||
- `text_reader_compat.nvgt`: Optional aliases (`text_reader*`) for legacy code.
|
||||
- `ui.nvgt`: Dialog wrappers with optional main-window restoration.
|
||||
- `speech_history.nvgt`: `speak_with_history()` wrapper plus comma/period history navigation.
|
||||
- `character_dialog.nvgt`: Blocking dialog lines with repeat/next/skip keys and optional per-word pre-speech sounds.
|
||||
- `notifications.nvgt`: Queued notifications with history and optional sound playback.
|
||||
- `menu_helpers.nvgt`: Simple menu runner + filter helpers with `sounds/menu` defaults.
|
||||
- `menu_music.nvgt`: Reusable menu music start/pause/stop helpers with blocking fade-out pause by default.
|
||||
@@ -29,6 +30,7 @@ Reusable NVGT helpers for Storm projects.
|
||||
#include "libstorm-nvgt/text_reader_compat.nvgt" // Optional legacy aliases
|
||||
#include "libstorm-nvgt/ui.nvgt"
|
||||
#include "libstorm-nvgt/speech_history.nvgt"
|
||||
#include "libstorm-nvgt/character_dialog.nvgt"
|
||||
#include "libstorm-nvgt/notifications.nvgt"
|
||||
#include "libstorm-nvgt/menu_helpers.nvgt"
|
||||
#include "libstorm-nvgt/menu_music.nvgt"
|
||||
@@ -110,6 +112,22 @@ void stop_main_menu_music() {
|
||||
file_viewer_file("README.txt", "Readme", true);
|
||||
```
|
||||
|
||||
## character_dialog Example
|
||||
|
||||
```nvgt
|
||||
#include "libstorm-nvgt/character_dialog.nvgt"
|
||||
|
||||
void play_intro_dialog() {
|
||||
string[] lines = {"Welcome to the gate.", "State your name and purpose."};
|
||||
character_dialog_show_lines(lines);
|
||||
}
|
||||
```
|
||||
|
||||
```nvgt
|
||||
// Optional override sound. Custom paths play once per line instead of once per word.
|
||||
character_dialog_set_sound_path("sounds/cutscene/chime");
|
||||
```
|
||||
|
||||
## speech_history + notifications Example
|
||||
|
||||
```nvgt
|
||||
@@ -140,7 +158,8 @@ void on_event() {
|
||||
## learn_sounds Example (Project Override File)
|
||||
|
||||
```nvgt
|
||||
// In your project root, create excluded_sounds.nvgt:
|
||||
// The override file can be named and placed anywhere in your project.
|
||||
// Example file: src/sound_settings.nvgt
|
||||
void configure_project_learn_sounds() {
|
||||
learn_sounds_reset_configuration();
|
||||
learn_sounds_add_skip_entry("sounds/menu/");
|
||||
@@ -151,7 +170,7 @@ void configure_project_learn_sounds() {
|
||||
```nvgt
|
||||
// In your game module:
|
||||
#include "libstorm-nvgt/learn_sounds.nvgt"
|
||||
#include "excluded_sounds.nvgt"
|
||||
#include "src/sound_settings.nvgt"
|
||||
#include "libstorm-nvgt/speech_history.nvgt"
|
||||
|
||||
void learn_sounds_bridge_speak(const string &in message, bool interrupt) {
|
||||
|
||||
201
character_dialog.nvgt
Normal file
201
character_dialog.nvgt
Normal file
@@ -0,0 +1,201 @@
|
||||
funcdef void character_dialog_speak_callback(const string& in message, bool interrupt);
|
||||
|
||||
string characterDialogSoundPath = "sounds/dialog";
|
||||
bool characterDialogShowUsageInstructions = true;
|
||||
bool characterDialogUsageInstructionsShown = false;
|
||||
bool characterDialogSoundLoaded = false;
|
||||
sound characterDialogSound;
|
||||
character_dialog_speak_callback @characterDialogSpeakCallback = null;
|
||||
|
||||
void character_dialog_set_speak_callback(character_dialog_speak_callback @callback) {
|
||||
@characterDialogSpeakCallback = @callback;
|
||||
}
|
||||
|
||||
void character_dialog_set_sound_path(const string soundPath) {
|
||||
characterDialogSoundPath = soundPath;
|
||||
characterDialogSoundLoaded = false;
|
||||
characterDialogSound.close();
|
||||
}
|
||||
|
||||
void character_dialog_set_show_usage_instructions(bool enabled) {
|
||||
characterDialogShowUsageInstructions = enabled;
|
||||
}
|
||||
|
||||
void character_dialog_reset_usage_instructions() {
|
||||
characterDialogUsageInstructionsShown = false;
|
||||
}
|
||||
|
||||
void character_dialog_reset() {
|
||||
characterDialogUsageInstructionsShown = false;
|
||||
characterDialogSoundLoaded = false;
|
||||
characterDialogSound.close();
|
||||
}
|
||||
|
||||
void character_dialog_speak(const string& in message, bool interrupt) {
|
||||
if (@characterDialogSpeakCallback !is null) {
|
||||
characterDialogSpeakCallback(message, interrupt);
|
||||
return;
|
||||
}
|
||||
|
||||
screen_reader_speak(message, interrupt);
|
||||
}
|
||||
|
||||
bool character_dialog_should_skip() {
|
||||
return key_pressed(KEY_ESCAPE) || key_pressed(KEY_AC_BACK);
|
||||
}
|
||||
|
||||
bool character_dialog_ensure_sound_loaded() {
|
||||
if (characterDialogSoundLoaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
characterDialogSound.close();
|
||||
if (characterDialogSound.load(characterDialogSoundPath)) {
|
||||
characterDialogSoundLoaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (characterDialogSound.load(characterDialogSoundPath + ".ogg")) {
|
||||
characterDialogSoundLoaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!characterDialogSound.load(characterDialogSoundPath + ".wav")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
characterDialogSoundLoaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool character_dialog_use_word_sound_sequence() {
|
||||
if (characterDialogSoundPath == "sounds/dialog" || characterDialogSoundPath == "sounds/dialog.ogg" ||
|
||||
characterDialogSoundPath == "sounds/dialog.wav") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int character_dialog_count_words(const string& in message) {
|
||||
if (message == "") {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wordCount = 0;
|
||||
bool inWord = false;
|
||||
for (uint charIndex = 0; charIndex < message.length(); charIndex++) {
|
||||
string character = message.substr(charIndex, 1);
|
||||
bool isWhitespace = character == " " || character == "\t" || character == "\n" || character == "\r";
|
||||
if (isWhitespace) {
|
||||
inWord = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inWord) {
|
||||
wordCount++;
|
||||
inWord = true;
|
||||
}
|
||||
}
|
||||
|
||||
return wordCount;
|
||||
}
|
||||
|
||||
bool character_dialog_play_word_sound_sequence(const string& in message) {
|
||||
if (!character_dialog_ensure_sound_loaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int playCount = 1;
|
||||
if (character_dialog_use_word_sound_sequence()) {
|
||||
playCount = character_dialog_count_words(message);
|
||||
}
|
||||
|
||||
for (int wordIndex = 0; wordIndex < playCount; wordIndex++) {
|
||||
characterDialogSound.play();
|
||||
while (characterDialogSound.playing) {
|
||||
wait(5);
|
||||
if (character_dialog_should_skip()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool character_dialog_repeat_requested() {
|
||||
if (key_down(KEY_RETURN) || key_down(KEY_NUMPAD_ENTER) || key_down(KEY_ESCAPE) || key_down(KEY_AC_BACK)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return total_keys_down() > 0;
|
||||
}
|
||||
|
||||
int character_dialog_wait_for_action(const string& in currentLine, bool interrupt) {
|
||||
bool keyHeld = false;
|
||||
|
||||
while (true) {
|
||||
wait(5);
|
||||
|
||||
if (character_dialog_should_skip()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (key_pressed(KEY_RETURN) || key_pressed(KEY_NUMPAD_ENTER)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool repeatRequested = character_dialog_repeat_requested();
|
||||
if (repeatRequested) {
|
||||
if (!keyHeld) {
|
||||
character_dialog_speak(currentLine, interrupt);
|
||||
keyHeld = true;
|
||||
}
|
||||
} else {
|
||||
keyHeld = false;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool character_dialog_show_lines(const string[] @dialogLines, bool interrupt = true) {
|
||||
if (@dialogLines is null || dialogLines.length() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint lineIndex = 0; lineIndex < dialogLines.length(); lineIndex++) {
|
||||
string currentLine = dialogLines[lineIndex];
|
||||
if (currentLine == "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (character_dialog_play_word_sound_sequence(currentLine)) {
|
||||
character_dialog_speak(" ", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
string displayLine = currentLine;
|
||||
if (characterDialogShowUsageInstructions && !characterDialogUsageInstructionsShown) {
|
||||
displayLine += "\nPress enter to continue, escape to skip, or any other key to repeat.";
|
||||
characterDialogUsageInstructionsShown = true;
|
||||
}
|
||||
|
||||
character_dialog_speak(displayLine, interrupt);
|
||||
int action = character_dialog_wait_for_action(currentLine, interrupt);
|
||||
if (action < 0) {
|
||||
character_dialog_speak(" ", true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
character_dialog_speak(" ", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void character_dialog_show(const string[] @dialogLines, bool interrupt = true) {
|
||||
/* The orange goblins speak to me in the night,
|
||||
as the moon casts shadows pumpkins come to life! */
|
||||
character_dialog_show_lines(dialogLines, interrupt);
|
||||
}
|
||||
Reference in New Issue
Block a user