Compare commits

..
5 Commits
8 changed files with 332 additions and 8 deletions
+18
View File
@@ -0,0 +1,18 @@
---
BasedOnStyle: LLVM
IndentWidth: 4
ContinuationIndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 120
BreakBeforeBraces: Attach
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
IndentCaseLabels: true
SortIncludes: Never
ReflowComments: false
PointerAlignment: Left
ReferenceAlignment: Left
SpaceBeforeParens: ControlStatements
...
+9
View File
@@ -0,0 +1,9 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://www.wtfpl.net/txt/copying/">here</a>.</p>
<hr>
<address>Apache/2.4.66 (Debian) Server at www.wtfpl.net Port 80</address>
</body></html>
+21 -2
View File
@@ -9,6 +9,7 @@ Reusable NVGT helpers for Storm projects.
- `text_reader_compat.nvgt`: Optional aliases (`text_reader*`) for legacy code. - `text_reader_compat.nvgt`: Optional aliases (`text_reader*`) for legacy code.
- `ui.nvgt`: Dialog wrappers with optional main-window restoration. - `ui.nvgt`: Dialog wrappers with optional main-window restoration.
- `speech_history.nvgt`: `speak_with_history()` wrapper plus comma/period history navigation. - `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. - `notifications.nvgt`: Queued notifications with history and optional sound playback.
- `menu_helpers.nvgt`: Simple menu runner + filter helpers with `sounds/menu` defaults. - `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. - `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/text_reader_compat.nvgt" // Optional legacy aliases
#include "libstorm-nvgt/ui.nvgt" #include "libstorm-nvgt/ui.nvgt"
#include "libstorm-nvgt/speech_history.nvgt" #include "libstorm-nvgt/speech_history.nvgt"
#include "libstorm-nvgt/character_dialog.nvgt"
#include "libstorm-nvgt/notifications.nvgt" #include "libstorm-nvgt/notifications.nvgt"
#include "libstorm-nvgt/menu_helpers.nvgt" #include "libstorm-nvgt/menu_helpers.nvgt"
#include "libstorm-nvgt/menu_music.nvgt" #include "libstorm-nvgt/menu_music.nvgt"
@@ -110,6 +112,22 @@ void stop_main_menu_music() {
file_viewer_file("README.txt", "Readme", true); 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 ## speech_history + notifications Example
```nvgt ```nvgt
@@ -140,7 +158,8 @@ void on_event() {
## learn_sounds Example (Project Override File) ## learn_sounds Example (Project Override File)
```nvgt ```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() { void configure_project_learn_sounds() {
learn_sounds_reset_configuration(); learn_sounds_reset_configuration();
learn_sounds_add_skip_entry("sounds/menu/"); learn_sounds_add_skip_entry("sounds/menu/");
@@ -151,7 +170,7 @@ void configure_project_learn_sounds() {
```nvgt ```nvgt
// In your game module: // In your game module:
#include "libstorm-nvgt/learn_sounds.nvgt" #include "libstorm-nvgt/learn_sounds.nvgt"
#include "excluded_sounds.nvgt" #include "src/sound_settings.nvgt"
#include "libstorm-nvgt/speech_history.nvgt" #include "libstorm-nvgt/speech_history.nvgt"
void learn_sounds_bridge_speak(const string &in message, bool interrupt) { void learn_sounds_bridge_speak(const string &in message, bool interrupt) {
+201
View 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() {
key_code[] @pressedKeys = keys_pressed();
if (@pressedKeys is null || pressedKeys.length() == 0)
return false;
for (uint keyIndex = 0; keyIndex < pressedKeys.length(); keyIndex++) {
int keyCode = pressedKeys[keyIndex];
if (keyCode == KEY_RETURN || keyCode == KEY_NUMPAD_ENTER || keyCode == KEY_ESCAPE || keyCode == KEY_AC_BACK)
continue;
return true;
}
return false;
}
int character_dialog_wait_for_action(const string& in currentLine, bool interrupt) {
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) {
character_dialog_speak(currentLine, interrupt);
}
}
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 += " Press 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);
}
+1 -1
View File
@@ -319,7 +319,7 @@ void learn_sounds_collect_entries(string[] @labels, string[] @soundPaths) {
continue; continue;
} }
string label = learn_sounds_label_from_path(soundPath); string label = i18n_text(learn_sounds_label_from_path(soundPath));
string description = learn_sounds_get_description(soundPath); string description = learn_sounds_get_description(soundPath);
if (description.length() > 0) { if (description.length() > 0) {
label += " - " + description; label += " - " + description;
+2 -1
View File
@@ -45,7 +45,8 @@ bool is_windows_reserved_filename(const string& in upperName) {
return false; return false;
} }
string sanitize_filename_base_with_reserved_prefix(string name, const int maxLength = 40, const string fallback = "item", string sanitize_filename_base_with_reserved_prefix(string name, const int maxLength = 40,
const string fallback = "item",
const string reservedPrefix = "file_") { const string reservedPrefix = "file_") {
string normalized = normalize_name_whitespace(name); string normalized = normalize_name_whitespace(name);
string result = ""; string result = "";
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail
# Show simple usage help.
print_usage() {
echo "Usage: scripts/format-nvgt.sh [path/to/file.nvgt ...]"
echo "Formats all tracked .nvgt files when no file paths are provided."
}
scriptDir=""
repoRoot=""
filePath=""
formattedCount=0
targetFiles=()
# Help flag is optional and exits early without formatting.
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
print_usage
exit 0
fi
# Stop immediately if clang-format is not installed.
if ! command -v clang-format >/dev/null 2>&1; then
echo "clang-format is required but was not found in PATH." >&2
exit 1
fi
# Resolve script location, then run from repo root so relative paths work.
scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repoRoot="$(cd "${scriptDir}/.." && pwd)"
cd "${repoRoot}"
# We require the project style file to keep formatting consistent.
if [[ ! -f ".clang-format" ]]; then
echo "Missing .clang-format in repo root: ${repoRoot}" >&2
exit 1
fi
# No args: format every tracked .nvgt file in git.
if [[ "$#" -eq 0 ]]; then
mapfile -t targetFiles < <(git ls-files "*.nvgt")
else
# Args provided: validate each file and format only those paths.
for filePath in "$@"; do
if [[ ! -f "${filePath}" ]]; then
echo "File not found: ${filePath}" >&2
exit 1
fi
if [[ "${filePath}" != *.nvgt ]]; then
echo "Only .nvgt files are supported: ${filePath}" >&2
exit 1
fi
targetFiles+=("${filePath}")
done
fi
if [[ "${#targetFiles[@]}" -eq 0 ]]; then
echo "No .nvgt files found to format."
exit 0
fi
# Force C++ parsing rules for NVGT while still using repo .clang-format.
for filePath in "${targetFiles[@]}"; do
clang-format -i --style=file --assume-filename=file.cpp "${filePath}"
formattedCount=$((formattedCount + 1))
done
echo -n "Formatted ${formattedCount} "
if [[ ${formattedCount} -ne 1 ]]; then
echo "files."
else
echo "file."
fi
exit 0
+4 -4
View File
@@ -83,8 +83,8 @@ void check_speech_history_keys() {
speechHistoryCurrentIndex--; speechHistoryCurrentIndex--;
if (speechHistoryCurrentIndex < 0) { if (speechHistoryCurrentIndex < 0) {
speechHistoryCurrentIndex = 0; speechHistoryCurrentIndex = 0;
screen_reader_speak(speech_history_transform_message("Oldest message. " + speechHistory[speechHistoryCurrentIndex]), screen_reader_speak(
true); speech_history_transform_message("Oldest message. " + speechHistory[speechHistoryCurrentIndex]), true);
return; return;
} }
@@ -105,8 +105,8 @@ void check_speech_history_keys() {
speechHistoryCurrentIndex++; speechHistoryCurrentIndex++;
if (speechHistoryCurrentIndex >= int(speechHistory.length())) { if (speechHistoryCurrentIndex >= int(speechHistory.length())) {
speechHistoryCurrentIndex = speechHistory.length() - 1; speechHistoryCurrentIndex = speechHistory.length() - 1;
screen_reader_speak(speech_history_transform_message("Newest message. " + speechHistory[speechHistoryCurrentIndex]), screen_reader_speak(
true); speech_history_transform_message("Newest message. " + speechHistory[speechHistoryCurrentIndex]), true);
return; return;
} }