Wire main menu Options/Quit through the cutscene, fix locale crashes
Options and Quit now route through main_menu.jsonc markers the same way New Game does, with a new QUIT_GAME cutscene item type ending the process directly once quit is confirmed via MODAL_OPTIONS_MARKERS (whose "message" field is now optional, defaulting to "" like "title"). Fixes two crashes surfaced along the way: - uiModalLocalize asserted on an empty locale key instead of treating it as "nothing to show" - hit as soon as a modal omitted a title or message. - assetLocaleGetString's new empty-message-ID guard broke the PO format's own header-read convention (messageId "") used internally by assetLocaleLoaderAsync, crashing on every boot. Extracted the shared lookup into assetLocaleGetStringLookup and added a dedicated assetLocaleGetHeader for that internal read, leaving assetLocaleGetString's validation untouched for every real caller. Also replaced every raw assetLocaleGetString/WithVA call across the UI with the locale/localemanager.h helpers (adding localeManagerGetString for the plain, non-formatting case), so app code no longer bypasses them. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -1,9 +1,20 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"type": "MARKER",
|
||||
"name": "MAIN_MENU_IDLE"
|
||||
},
|
||||
{
|
||||
"type": "UI_SHOW",
|
||||
"name": "main_menu"
|
||||
},
|
||||
|
||||
// Options button goes here - no options screen yet, so this is just
|
||||
// an alias landing right back on the idle loop below.
|
||||
{
|
||||
"type": "MARKER",
|
||||
"name": "OPTIONS"
|
||||
},
|
||||
{
|
||||
"type": "IDLE"
|
||||
},
|
||||
@@ -88,6 +99,35 @@
|
||||
{
|
||||
"type": "MARKER",
|
||||
"name": "LOADED"
|
||||
},
|
||||
|
||||
// Quit Game button goes here. Cancelling jumps back to
|
||||
// MAIN_MENU_IDLE; confirming falls through to QUIT_GAME, which
|
||||
// ends the process directly.
|
||||
{
|
||||
"type": "MARKER",
|
||||
"name": "QUIT"
|
||||
},
|
||||
{
|
||||
"type": "MODAL_OPTIONS_MARKERS",
|
||||
"title": "main_menu.quit_confirm",
|
||||
"options": [
|
||||
{
|
||||
"text": "ui.confirm.confirm",
|
||||
"marker": "QUIT_CONFIRMED"
|
||||
},
|
||||
{
|
||||
"text": "ui.confirm.cancel",
|
||||
"marker": "MAIN_MENU_IDLE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "MARKER",
|
||||
"name": "QUIT_CONFIRMED"
|
||||
},
|
||||
{
|
||||
"type": "QUIT_GAME"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ errorret_t assetLocaleLoaderAsync(assetloading_t *loading) {
|
||||
assetLoaderErrorChain(loading, assetFileOpen(&localeFile->file));
|
||||
|
||||
char_t buffer[1024];
|
||||
assetLoaderErrorChain(loading, assetLocaleGetString(
|
||||
localeFile, "", 0, buffer, sizeof(buffer)
|
||||
assetLoaderErrorChain(loading, assetLocaleGetHeader(
|
||||
localeFile, buffer, sizeof(buffer)
|
||||
));
|
||||
assetLoaderErrorChain(loading, assetLocaleParseHeader(
|
||||
localeFile, buffer, sizeof(buffer)
|
||||
@@ -487,6 +487,7 @@ errorret_t assetLocaleCacheFind(
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(messageId, "Message ID cannot be NULL.");
|
||||
assertNotNull(outHit, "outHit cannot be NULL.");
|
||||
assertStrLenMin(messageId, 1, "Message ID cannot be empty.");
|
||||
|
||||
*outHit = false;
|
||||
if(file->cache == NULL) errorOk();
|
||||
@@ -527,6 +528,7 @@ void assetLocaleCacheInsert(
|
||||
assertNotNull(file, "Asset file cannot be NULL.");
|
||||
assertNotNull(messageId, "Message ID cannot be NULL.");
|
||||
assertNotNull(value, "Value cannot be NULL.");
|
||||
assertStrLenMin(messageId, 1, "Message ID cannot be empty.");
|
||||
|
||||
// Caching is a pure optimization - a message ID or value too long for a
|
||||
// fixed cache slot is just never cached, not an error.
|
||||
@@ -555,7 +557,13 @@ void assetLocaleCacheInsert(
|
||||
stringCopy(entry->value, value, ASSET_LOCALE_CACHE_VALUE_MAX);
|
||||
}
|
||||
|
||||
errorret_t assetLocaleGetString(
|
||||
// Shared by assetLocaleGetString (external callers, messageId required
|
||||
// non-empty) and assetLocaleGetHeader (messageId "", the PO format's own
|
||||
// sentinel for its header entry - see assetLocaleGetHeader). Caching is
|
||||
// skipped entirely for an empty messageId: assetLocaleCacheFind/Insert
|
||||
// both reject one outright (same guard as assetLocaleGetString's own),
|
||||
// and it's a one-shot boot-time read anyway, not worth caching.
|
||||
errorret_t assetLocaleGetStringLookup(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *messageId,
|
||||
const int32_t pluralCount,
|
||||
@@ -569,10 +577,12 @@ errorret_t assetLocaleGetString(
|
||||
assertTrue(stringBufferSize > 0, "String buffer size must be > 0");
|
||||
|
||||
bool_t cacheHit = false;
|
||||
errorChain(assetLocaleCacheFind(
|
||||
file, messageId, pluralCount, stringBuffer, stringBufferSize, &cacheHit
|
||||
));
|
||||
if(cacheHit) errorOk();
|
||||
if(messageId[0] != '\0') {
|
||||
errorChain(assetLocaleCacheFind(
|
||||
file, messageId, pluralCount, stringBuffer, stringBufferSize, &cacheHit
|
||||
));
|
||||
if(cacheHit) errorOk();
|
||||
}
|
||||
|
||||
assetfilelinereader_t reader;
|
||||
|
||||
@@ -717,11 +727,38 @@ errorret_t assetLocaleGetString(
|
||||
errorThrow("Failed to find msgstr for message ID: %s", messageId);
|
||||
}
|
||||
|
||||
assetLocaleCacheInsert(file, messageId, pluralCount, stringBuffer);
|
||||
if(messageId[0] != '\0') {
|
||||
assetLocaleCacheInsert(file, messageId, pluralCount, stringBuffer);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetLocaleGetString(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *messageId,
|
||||
const int32_t pluralCount,
|
||||
char_t *stringBuffer,
|
||||
const size_t stringBufferSize
|
||||
) {
|
||||
assertNotNull(messageId, "Message ID cannot be NULL.");
|
||||
assertStrLenMin(messageId, 1, "Message ID cannot be empty.");
|
||||
|
||||
return assetLocaleGetStringLookup(
|
||||
file, messageId, pluralCount, stringBuffer, stringBufferSize
|
||||
);
|
||||
}
|
||||
|
||||
errorret_t assetLocaleGetHeader(
|
||||
assetlocalefile_t *file,
|
||||
char_t *stringBuffer,
|
||||
const size_t stringBufferSize
|
||||
) {
|
||||
return assetLocaleGetStringLookup(
|
||||
file, "", 0, stringBuffer, stringBufferSize
|
||||
);
|
||||
}
|
||||
|
||||
errorret_t assetLocaleGetStringWithVA(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *messageId,
|
||||
|
||||
@@ -299,16 +299,42 @@ void assetLocaleCacheInsert(
|
||||
);
|
||||
|
||||
/**
|
||||
* Looks up a translated string by message ID from the open locale file.
|
||||
* Shared lookup core behind assetLocaleGetString and assetLocaleGetHeader:
|
||||
* checks the file's cache first (see @ref assetLocaleCacheFind; skipped
|
||||
* entirely for an empty messageId, since the cache rejects one), on a
|
||||
* miss rewinds the file and scans from the beginning, then caches the
|
||||
* result (see @ref assetLocaleCacheInsert, same empty-messageId skip)
|
||||
* before returning. For plural entries (`msgid_plural`) the
|
||||
* `pluralCount` is evaluated against the loaded plural rules to select
|
||||
* the correct `msgstr[N]` form.
|
||||
*
|
||||
* Checks the file's cache first (see @ref assetLocaleCacheFind); on a miss,
|
||||
* rewinds the file and scans from the beginning, then caches the result
|
||||
* (see @ref assetLocaleCacheInsert) before returning. For plural entries
|
||||
* (`msgid_plural`) the `pluralCount` is evaluated against the loaded plural
|
||||
* rules to select the correct `msgstr[N]` form.
|
||||
* Unlike assetLocaleGetString, messageId may be `""` here (see
|
||||
* assetLocaleGetHeader) - this is the shared implementation both of
|
||||
* them delegate to, not a public entry point of its own in practice.
|
||||
*
|
||||
* @param file Locale file to search. Must be open.
|
||||
* @param messageId PO message ID to find (`""` retrieves the header entry).
|
||||
* @param messageId PO message ID to find, or `""` for the header entry.
|
||||
* @param pluralCount Count used to select the plural form (ignored for
|
||||
* singular entries).
|
||||
* @param stringBuffer Buffer to receive the translated string.
|
||||
* @param stringBufferSize Capacity of `stringBuffer` in bytes.
|
||||
* @return OK on success, error if the message ID is not found or I/O fails.
|
||||
*/
|
||||
errorret_t assetLocaleGetStringLookup(
|
||||
assetlocalefile_t *file,
|
||||
const char_t *messageId,
|
||||
const int32_t pluralCount,
|
||||
char_t *stringBuffer,
|
||||
const size_t stringBufferSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Looks up a translated string by message ID from the open locale file.
|
||||
* See @ref assetLocaleGetStringLookup for the lookup/caching mechanics.
|
||||
*
|
||||
* @param file Locale file to search. Must be open.
|
||||
* @param messageId PO message ID to find. Must be non-empty - use
|
||||
* @ref assetLocaleGetHeader to read the header entry.
|
||||
* @param pluralCount Count used to select the plural form (ignored for
|
||||
* singular entries).
|
||||
* @param stringBuffer Buffer to receive the translated string.
|
||||
@@ -323,6 +349,27 @@ errorret_t assetLocaleGetString(
|
||||
const size_t stringBufferSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads the PO file's header entry (the `msgid ""` block every PO file
|
||||
* starts with, holding metadata like the plural-forms rule) - see @ref
|
||||
* assetLocaleParseHeader, its caller. A dedicated function rather than
|
||||
* calling assetLocaleGetString(file, "", ...) since messageId "" is a
|
||||
* PO-format-internal sentinel, not a real (untrusted) caller-supplied
|
||||
* message ID, so it doesn't go through assetLocaleGetString's normal
|
||||
* non-empty validation.
|
||||
*
|
||||
* @param file Locale file to search. Must be open.
|
||||
* @param stringBuffer Buffer to receive the raw header block.
|
||||
* @param stringBufferSize Capacity of `stringBuffer` in bytes.
|
||||
* @return OK on success, error if the header entry is missing or I/O
|
||||
* fails.
|
||||
*/
|
||||
errorret_t assetLocaleGetHeader(
|
||||
assetlocalefile_t *file,
|
||||
char_t *stringBuffer,
|
||||
const size_t stringBufferSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Looks up a translated string and formats it with a `printf`-style variadic
|
||||
* argument list.
|
||||
|
||||
@@ -34,13 +34,43 @@ errorret_t localeManagerInit();
|
||||
errorret_t localeManagerSetLocale(const localeinfo_t *locale);
|
||||
|
||||
/**
|
||||
* Get a localized string for the given message ID.
|
||||
*
|
||||
* Get a localized string for the given message ID verbatim, with no
|
||||
* printf-style formatting applied - use this (rather than
|
||||
* localeManagerGetText with no variadic args) whenever the raw string
|
||||
* is displayed as-is, or cached to be formatted later (e.g. a
|
||||
* "_format" message ID whose %-specifiers are filled in by the caller
|
||||
* itself via stringFormat once the actual values are known). Passing a
|
||||
* "_format" string through localeManagerGetText/GetTextArgs instead
|
||||
* would consume it immediately as a format string with zero args,
|
||||
* which is wrong for this use, and would also mishandle any literal
|
||||
* '%' in a plain display string.
|
||||
*
|
||||
* @param id The message ID to retrieve.
|
||||
* @param buffer Buffer to write the retrieved string to.
|
||||
* @param bufferSize Size of the buffer.
|
||||
* @param plural Plural index to retrieve.
|
||||
* @param ... Additional arguments for formatting the string.
|
||||
* @return An error code if a failure occurs.
|
||||
*/
|
||||
#define localeManagerGetString(id, buffer, bufferSize, plural) \
|
||||
assetLocaleGetString( \
|
||||
&LOCALE.entry->data.locale, \
|
||||
id, \
|
||||
plural, \
|
||||
buffer, \
|
||||
bufferSize \
|
||||
)
|
||||
|
||||
/**
|
||||
* Get a localized string for the given message ID, formatted
|
||||
* printf-style with the given variadic arguments.
|
||||
*
|
||||
* @param id The message ID to retrieve.
|
||||
* @param buffer Buffer to write the retrieved string to.
|
||||
* @param bufferSize Size of the buffer.
|
||||
* @param plural Plural index to retrieve.
|
||||
* @param ... Arguments for formatting the string - must not be empty,
|
||||
* since the retrieved string is always run through vsnprintf; see
|
||||
* localeManagerGetString for a message ID with no format args at all.
|
||||
* @return An error code if a failure occurs.
|
||||
*/
|
||||
#define localeManagerGetText(id, buffer, bufferSize, plural, ...) \
|
||||
|
||||
@@ -10,6 +10,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
cutsceneconcurrent.c
|
||||
cutscenemarker.c
|
||||
cutscenerestart.c
|
||||
cutscenequitgame.c
|
||||
cutscenescene.c
|
||||
cutsceneidle.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "engine/engine.h"
|
||||
|
||||
void cutsceneQuitGameStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
ENGINE.running = false;
|
||||
}
|
||||
|
||||
bool_t cutsceneQuitGameUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||
|
||||
/**
|
||||
* Starts a quit-game item (sets ENGINE.running to false, ending the
|
||||
* process's main loop).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneQuitGameStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a quit-game item. The engine is shutting down by the time this
|
||||
* would run, so this always reports incomplete (mirrors
|
||||
* cutsceneRestartUpdate).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns false always.
|
||||
*/
|
||||
bool_t cutsceneQuitGameUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
// QUIT_GAME has no fields of its own to parse - see cutsceneItemLoadNoop
|
||||
// (cutsceneitembase.h), used directly as its .load callback.
|
||||
@@ -220,6 +220,13 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
.load = cutsceneItemLoadNoop
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_QUIT_GAME] = {
|
||||
.init = cutsceneQuitGameStart,
|
||||
.update = cutsceneQuitGameUpdate,
|
||||
.jsonName = "QUIT_GAME",
|
||||
.load = cutsceneItemLoadNoop
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_SCENE] = {
|
||||
.init = cutsceneSceneStart,
|
||||
.update = cutsceneSceneUpdate,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "control/cutsceneconcurrent.h"
|
||||
#include "control/cutscenemarker.h"
|
||||
#include "control/cutscenerestart.h"
|
||||
#include "control/cutscenequitgame.h"
|
||||
#include "control/cutscenescene.h"
|
||||
#include "control/cutsceneidle.h"
|
||||
#include "entity/cutsceneentityteleport.h"
|
||||
@@ -87,6 +88,7 @@ typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_PRINT,
|
||||
CUTSCENE_ITEM_TYPE_MARKER,
|
||||
CUTSCENE_ITEM_TYPE_RESTART,
|
||||
CUTSCENE_ITEM_TYPE_QUIT_GAME,
|
||||
CUTSCENE_ITEM_TYPE_SCENE,
|
||||
CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK,
|
||||
CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS,
|
||||
|
||||
@@ -61,11 +61,11 @@ errorret_t cutsceneModalOptionsMarkersLoad(
|
||||
CUTSCENE_MODAL_TITLE_MAX_CHARS
|
||||
);
|
||||
if(!cutsceneJsonCopyString(
|
||||
itemObj, "message", NULL, item->modalOptionsMarkers.message,
|
||||
itemObj, "message", "", item->modalOptionsMarkers.message,
|
||||
CUTSCENE_MODAL_MESSAGE_MAX_CHARS
|
||||
)) {
|
||||
errorThrow(
|
||||
"Cutscene item (MODAL_OPTIONS_MARKERS): missing/too-long 'message'"
|
||||
"Cutscene item (MODAL_OPTIONS_MARKERS): 'message' too long"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,7 @@ errorret_t itemGetName(
|
||||
assertTrue(item > ITEM_ID_NULL, "Item ID must not be null");
|
||||
assertTrue(item < ITEM_ID_COUNT, "Item ID out of range");
|
||||
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
ITEMS[item].name,
|
||||
0,
|
||||
buffer,
|
||||
bufferSize
|
||||
));
|
||||
errorChain(localeManagerGetString(ITEMS[item].name, buffer, bufferSize, 0));
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ void uiSaveSlotInit(
|
||||
&saveSlot->numberLabel, saveSlot->numberText,
|
||||
saveSlot->numberSprites, UI_SAVE_SLOT_NUMBER_SPRITES_MAX
|
||||
);
|
||||
errorret_t result = assetLocaleGetStringWithVA(
|
||||
&LOCALE.entry->data.locale, "ui.save_slot.number_format", 0,
|
||||
saveSlot->numberText, UI_SAVE_SLOT_NUMBER_TEXT_MAX,
|
||||
errorret_t result = localeManagerGetText(
|
||||
"ui.save_slot.number_format",
|
||||
saveSlot->numberText, UI_SAVE_SLOT_NUMBER_TEXT_MAX, 0,
|
||||
slotIndex + 1
|
||||
);
|
||||
if(errorIsNotOk(result)) errorCatch(result);
|
||||
|
||||
@@ -32,9 +32,8 @@ void uiSelectSaveFormatSlot(
|
||||
saveslotcache_t *cache = &SAVE.caches[index];
|
||||
|
||||
if(!saveSlotInUse(cache)) {
|
||||
errorret_t result = assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale, "ui.select_save.empty", 0,
|
||||
buffer, bufferSize
|
||||
errorret_t result = localeManagerGetString(
|
||||
"ui.select_save.empty", buffer, bufferSize, 0
|
||||
);
|
||||
if(errorIsNotOk(result)) errorCatch(result);
|
||||
return;
|
||||
@@ -43,9 +42,8 @@ void uiSelectSaveFormatSlot(
|
||||
char_t dateText[32];
|
||||
timeEpochFormat(cache->time, "%Y-%m-%d %H:%M", dateText, sizeof(dateText));
|
||||
|
||||
errorret_t result = assetLocaleGetStringWithVA(
|
||||
&LOCALE.entry->data.locale, "ui.select_save.slot_format", 0,
|
||||
buffer, bufferSize,
|
||||
errorret_t result = localeManagerGetText(
|
||||
"ui.select_save.slot_format", buffer, bufferSize, 0,
|
||||
cache->name, cache->playerLevel, dateText
|
||||
);
|
||||
if(errorIsNotOk(result)) errorCatch(result);
|
||||
@@ -130,9 +128,8 @@ void uiSelectSaveNameEmptySlot(const uint8_t index) {
|
||||
UI_SELECT_SAVE.pendingNameIndex = index;
|
||||
|
||||
char_t title[UI_KEYBOARD_TITLE_TEXT_MAX];
|
||||
errorret_t result = assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale, "ui.select_save.name_title", 0,
|
||||
title, sizeof(title)
|
||||
errorret_t result = localeManagerGetString(
|
||||
"ui.select_save.name_title", title, sizeof(title), 0
|
||||
);
|
||||
if(errorIsNotOk(result)) errorCatch(result);
|
||||
|
||||
@@ -151,9 +148,8 @@ void uiSelectSaveSetActionLabel(const uiselectsavetype_t type) {
|
||||
const char_t *labelKey = type == UI_SELECT_SAVE_TYPE_LOAD ?
|
||||
"ui.select_save.delete_mode" : "ui.confirm.cancel";
|
||||
|
||||
errorret_t result = assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale, labelKey, 0,
|
||||
UI_SELECT_SAVE.actionText, UI_SELECT_SAVE_ACTION_LABEL_MAX
|
||||
errorret_t result = localeManagerGetString(
|
||||
labelKey, UI_SELECT_SAVE.actionText, UI_SELECT_SAVE_ACTION_LABEL_MAX, 0
|
||||
);
|
||||
if(errorIsNotOk(result)) errorCatch(result);
|
||||
|
||||
@@ -235,9 +231,11 @@ errorret_t uiSelectSaveInit(void) {
|
||||
&UI_SELECT_SAVE.titleLabel, UI_SELECT_SAVE.titleText,
|
||||
UI_SELECT_SAVE.titleSprites, UI_SELECT_SAVE_TITLE_SPRITES_MAX
|
||||
);
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale, "ui.select_save.title", 0,
|
||||
UI_SELECT_SAVE.titleText, UI_SELECT_SAVE_TITLE_TEXT_MAX
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.select_save.title",
|
||||
UI_SELECT_SAVE.titleText,
|
||||
UI_SELECT_SAVE_TITLE_TEXT_MAX,
|
||||
0
|
||||
));
|
||||
UI_SELECT_SAVE.titleLabel.dirty = true;
|
||||
uiLabelRebuffer(&UI_SELECT_SAVE.titleLabel);
|
||||
|
||||
@@ -21,12 +21,8 @@ uiloading_t UI_LOADING;
|
||||
errorret_t uiLoadingInit(void) {
|
||||
memoryZero(&UI_LOADING, sizeof(uiloading_t));
|
||||
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
"ui.loading.text",
|
||||
0,
|
||||
UI_LOADING.text,
|
||||
UI_LOADING_TEXT_MAX
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.loading.text", UI_LOADING.text, UI_LOADING_TEXT_MAX, 0
|
||||
));
|
||||
|
||||
errorOk();
|
||||
|
||||
@@ -43,12 +43,11 @@ void uiBackpackTabSelected(
|
||||
errorret_t uiBackpackInit(void) {
|
||||
memoryZero(&UI_BACKPACK, sizeof(uibackpack_t));
|
||||
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.backpack.category_format",
|
||||
0,
|
||||
UI_BACKPACK.categoryFormat,
|
||||
UI_BACKPACK_CATEGORY_FORMAT_MAX
|
||||
UI_BACKPACK_CATEGORY_FORMAT_MAX,
|
||||
0
|
||||
));
|
||||
|
||||
MENU_BEGIN(
|
||||
|
||||
@@ -25,19 +25,17 @@ uibattlehud_t UI_BATTLE_HUD;
|
||||
errorret_t uiBattleHudInit(void) {
|
||||
memoryZero(&UI_BATTLE_HUD, sizeof(uibattlehud_t));
|
||||
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.battle.hud.hp_format",
|
||||
0,
|
||||
UI_BATTLE_HUD.hpFormat,
|
||||
UI_BATTLE_HUD_FORMAT_MAX
|
||||
UI_BATTLE_HUD_FORMAT_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.battle.hud.mp_format",
|
||||
0,
|
||||
UI_BATTLE_HUD.mpFormat,
|
||||
UI_BATTLE_HUD_FORMAT_MAX
|
||||
UI_BATTLE_HUD_FORMAT_MAX,
|
||||
0
|
||||
));
|
||||
|
||||
errorOk();
|
||||
|
||||
@@ -83,26 +83,23 @@ void uiBattleMenuActionSelected(
|
||||
errorret_t uiBattleMenuInit(void) {
|
||||
memoryZero(&UI_BATTLE_MENU, sizeof(uibattlemenu_t));
|
||||
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.battle.menu.attack",
|
||||
0,
|
||||
UI_BATTLE_MENU.attackLabel,
|
||||
UI_BATTLE_MENU_ACTION_LABEL_MAX
|
||||
UI_BATTLE_MENU_ACTION_LABEL_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.battle.menu.flee",
|
||||
0,
|
||||
UI_BATTLE_MENU.fleeLabel,
|
||||
UI_BATTLE_MENU_ACTION_LABEL_MAX
|
||||
UI_BATTLE_MENU_ACTION_LABEL_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.battle.menu.target_format",
|
||||
0,
|
||||
UI_BATTLE_MENU.targetFormat,
|
||||
UI_BATTLE_MENU_TARGET_FORMAT_MAX
|
||||
UI_BATTLE_MENU_TARGET_FORMAT_MAX,
|
||||
0
|
||||
));
|
||||
|
||||
MENU_BEGIN(
|
||||
|
||||
@@ -41,75 +41,65 @@ void uiGameMenuSelected(
|
||||
errorret_t uiGameMenuInit(void) {
|
||||
memoryZero(&UI_GAME_MENU, sizeof(uigamemenu_t));
|
||||
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.characters",
|
||||
0,
|
||||
UI_GAME_MENU.charactersLabel,
|
||||
UI_GAME_MENU_LABEL_MAX
|
||||
UI_GAME_MENU_LABEL_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.items",
|
||||
0,
|
||||
UI_GAME_MENU.itemsLabel,
|
||||
UI_GAME_MENU_LABEL_MAX
|
||||
UI_GAME_MENU_LABEL_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.settings",
|
||||
0,
|
||||
UI_GAME_MENU.settingsLabel,
|
||||
UI_GAME_MENU_LABEL_MAX
|
||||
UI_GAME_MENU_LABEL_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.save_success",
|
||||
0,
|
||||
UI_GAME_MENU.saveSuccessText,
|
||||
UI_GAME_MENU_MESSAGE_MAX
|
||||
UI_GAME_MENU_MESSAGE_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.save_cancelled",
|
||||
0,
|
||||
UI_GAME_MENU.saveCancelledText,
|
||||
UI_GAME_MENU_MESSAGE_MAX
|
||||
UI_GAME_MENU_MESSAGE_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.save_unavailable",
|
||||
0,
|
||||
UI_GAME_MENU.saveUnavailableText,
|
||||
UI_GAME_MENU_MESSAGE_MAX
|
||||
UI_GAME_MENU_MESSAGE_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.save_temporary",
|
||||
0,
|
||||
UI_GAME_MENU.saveTemporaryText,
|
||||
UI_GAME_MENU_MESSAGE_MAX
|
||||
UI_GAME_MENU_MESSAGE_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.save_create_confirm",
|
||||
0,
|
||||
UI_GAME_MENU.saveCreateConfirmText,
|
||||
UI_GAME_MENU_MESSAGE_MAX
|
||||
UI_GAME_MENU_MESSAGE_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.save_failed_format",
|
||||
0,
|
||||
UI_GAME_MENU.saveFailedFormat,
|
||||
UI_GAME_MENU_MESSAGE_MAX
|
||||
UI_GAME_MENU_MESSAGE_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"ui.game_menu.save_check_failed_format",
|
||||
0,
|
||||
UI_GAME_MENU.saveCheckFailedFormat,
|
||||
UI_GAME_MENU_MESSAGE_MAX
|
||||
UI_GAME_MENU_MESSAGE_MAX,
|
||||
0
|
||||
));
|
||||
|
||||
MENU_BEGIN(
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "uimainmenu.h"
|
||||
#include "ui/widget/uiframe.h"
|
||||
#include "engine/engine.h"
|
||||
#include "rpg/battle/testbattle/testbattle.h"
|
||||
#include "util/memory.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
@@ -15,7 +14,6 @@
|
||||
#include "assert/assert.h"
|
||||
#include "locale/localemanager.h"
|
||||
#include "asset/loader/locale/assetlocaleloader.h"
|
||||
#include "ui/dialog/uiconfirm.h"
|
||||
#include "ui/dialog/save/uiselectsave.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "scene/scene.h"
|
||||
@@ -28,10 +26,6 @@
|
||||
uimainmenu_t UI_MAIN_MENU;
|
||||
assetentry_t *MAIN_MENU_CUTSCENE_ENTRY = NULL;
|
||||
|
||||
void uiMainMenuQuitConfirmed(const bool_t result, void *user) {
|
||||
if(result) ENGINE.running = false;
|
||||
}
|
||||
|
||||
cutscene_t *uiMainMenuLoadCutscene(void) {
|
||||
if(MAIN_MENU_CUTSCENE_ENTRY == NULL) {
|
||||
MAIN_MENU_CUTSCENE_ENTRY = assetLock(
|
||||
@@ -75,6 +69,24 @@ void uiMainMenuStartGame(void) {
|
||||
cutsceneSystemSetOnComplete(uiMainMenuOpenSelectSave);
|
||||
}
|
||||
|
||||
void uiMainMenuOptions(void) {
|
||||
if(CUTSCENE_SYSTEM.scene == NULL) {
|
||||
cutsceneSystemStartCutsceneAndGoToMarker(
|
||||
uiMainMenuLoadCutscene(), "OPTIONS"
|
||||
);
|
||||
} else {
|
||||
cutsceneGoTo("OPTIONS");
|
||||
}
|
||||
}
|
||||
|
||||
void uiMainMenuQuit(void) {
|
||||
if(CUTSCENE_SYSTEM.scene == NULL) {
|
||||
cutsceneSystemStartCutsceneAndGoToMarker(uiMainMenuLoadCutscene(), "QUIT");
|
||||
} else {
|
||||
cutsceneGoTo("QUIT");
|
||||
}
|
||||
}
|
||||
|
||||
void uiMainMenuSelected(
|
||||
const uimenu_t *menu,
|
||||
const uint8_t index,
|
||||
@@ -88,10 +100,12 @@ void uiMainMenuSelected(
|
||||
break;
|
||||
|
||||
case UI_MAIN_MENU_INDEX_OPTIONS:
|
||||
uiMainMenuOptions();
|
||||
break;
|
||||
|
||||
case UI_MAIN_MENU_INDEX_QUIT:
|
||||
uiConfirmOpen("main_menu.quit_confirm", uiMainMenuQuitConfirmed, NULL);
|
||||
uiMenuClose(&UI_MAIN_MENU.menu);
|
||||
uiMainMenuQuit();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -102,26 +116,23 @@ void uiMainMenuSelected(
|
||||
errorret_t uiMainMenuInit(void) {
|
||||
memoryZero(&UI_MAIN_MENU, sizeof(uimainmenu_t));
|
||||
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"main_menu.start_game",
|
||||
0,
|
||||
UI_MAIN_MENU.startGameLabel,
|
||||
UI_MAIN_MENU_LABEL_MAX
|
||||
UI_MAIN_MENU_LABEL_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"main_menu.options",
|
||||
0,
|
||||
UI_MAIN_MENU.optionsLabel,
|
||||
UI_MAIN_MENU_LABEL_MAX
|
||||
UI_MAIN_MENU_LABEL_MAX,
|
||||
0
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
errorChain(localeManagerGetString(
|
||||
"main_menu.quit",
|
||||
0,
|
||||
UI_MAIN_MENU.quitLabel,
|
||||
UI_MAIN_MENU_LABEL_MAX
|
||||
UI_MAIN_MENU_LABEL_MAX,
|
||||
0
|
||||
));
|
||||
|
||||
MENU_BEGIN(
|
||||
|
||||
@@ -131,9 +131,12 @@ void uiModalLocalize(
|
||||
char_t *buffer,
|
||||
const size_t bufferSize
|
||||
) {
|
||||
errorret_t result = assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale, key, 0, buffer, bufferSize
|
||||
);
|
||||
if(key[0] == '\0') {
|
||||
buffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
errorret_t result = localeManagerGetString(key, buffer, bufferSize, 0);
|
||||
if(errorIsNotOk(result)) {
|
||||
errorCatch(result);
|
||||
stringCopy(buffer, key, bufferSize - 1);
|
||||
|
||||
@@ -159,7 +159,11 @@ errorret_t uiModalDispose(void);
|
||||
/**
|
||||
* Resolves display text for uiModalOpen: if key matches a locale
|
||||
* message ID in the active locale file, buffer is filled with its
|
||||
* translated string, otherwise key is copied into buffer verbatim.
|
||||
* translated string, otherwise key is copied into buffer verbatim. An
|
||||
* empty key (e.g. an omitted JSON title/message, which defaults to ""
|
||||
* rather than NULL - see cutscenemodal_t/cutscenemodaloptionsmarkers_t)
|
||||
* is left as an empty buffer rather than looked up, since
|
||||
* assetLocaleGetString rejects an empty message ID outright.
|
||||
*
|
||||
* @param key Display string or locale message ID.
|
||||
* @param buffer Destination buffer.
|
||||
|
||||
Reference in New Issue
Block a user