Working on translations
This commit is contained in:
parent
9d8ca865c7
commit
e2d9105b02
@ -20,13 +20,6 @@
|
|||||||
"description": "Это ключ от учительской, который я получил от Айпери.",
|
"description": "Это ключ от учительской, который я получил от Айпери.",
|
||||||
"icon": "resources/w/ui/img/inv/ItemKey001.png",
|
"icon": "resources/w/ui/img/inv/ItemKey001.png",
|
||||||
"selectedIcon": "resources/w/ui/img/inv/ItemSelKey001.png"
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelKey001.png"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "all_keys",
|
|
||||||
"name": "Ключи от универа",
|
|
||||||
"description": "Это ключи от всех комнат в универе, которые я получил от Айпери.",
|
|
||||||
"icon": "resources/w/ui/img/inv/ItemKey001.png",
|
|
||||||
"selectedIcon": "resources/w/ui/img/inv/ItemSelKey001.png"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "knife",
|
"id": "knife",
|
||||||
@ -45,7 +38,7 @@
|
|||||||
{
|
{
|
||||||
"id": "note_spell",
|
"id": "note_spell",
|
||||||
"name": "Записка",
|
"name": "Записка",
|
||||||
"description": "Тут написано \"Прочитай вслух когда настанет ночь, сквозь двери сможешь ты пройти.\" и текст заклинания.",
|
"description": "Тут написано \"Привет собрату теней от собрата теней! Дарю тебе способ пройти через любую запертую дверь! Коснись рукой серебра, и прочитай вслух это заклинание. Заклинание работает только ночью.\" и текст заклинания.",
|
||||||
"icon": "resources/w/ui/img/inv/ItemNote001.png",
|
"icon": "resources/w/ui/img/inv/ItemNote001.png",
|
||||||
"selectedIcon": "resources/w/ui/img/inv/ItemSelNote001.png"
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelNote001.png"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"npcs": [
|
"npcs": [
|
||||||
{
|
{
|
||||||
"id": "npc_teacher01",
|
"id": "npc_teacher01",
|
||||||
"name": "Аида Дженибекова",
|
"name": "Аида Джаныбековна",
|
||||||
"animationIdlePath": "resources/w/new_anims/teacher_stand_idle001_small.anim",
|
"animationIdlePath": "resources/w/new_anims/teacher_stand_idle001_small.anim",
|
||||||
"animationWalkPath": "resources/w/new_anims/teacher_walk001_small.anim",
|
"animationWalkPath": "resources/w/new_anims/teacher_walk001_small.anim",
|
||||||
"meshTextures": {
|
"meshTextures": {
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
"objectives": [
|
"objectives": [
|
||||||
{ "id": "ghost_lore_aiperi", "text": "Поговорить с Айпери", "completed": false },
|
{ "id": "ghost_lore_aiperi", "text": "Поговорить с Айпери", "completed": false },
|
||||||
{ "id": "ghost_lore_alik", "text": "Поговорить с Аликом", "completed": false, "visible": false },
|
{ "id": "ghost_lore_alik", "text": "Поговорить с Аликом", "completed": false, "visible": false },
|
||||||
{ "id": "ghost_lore_teacher", "text": "Поговорить с Аидой Джаныбековой", "completed": false }
|
{ "id": "ghost_lore_teacher", "text": "Поговорить с Аидой Джаныбековной", "completed": false }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -31,7 +31,7 @@
|
|||||||
"description": "История Бегимай еще более загадочная. Во время сдачи курсовой работы в университете происходила генеральная уборка. По случайности, ее курсовая работа оказалась в стопке бумаг на выброс. В результате курсач оказался на помойке, а преподаватель, не обнаружив курсовую работу, поставил ей ноль баллов. По словам Алика, курсовая работа до сих пор лежит в куче мусора во дворе университета.",
|
"description": "История Бегимай еще более загадочная. Во время сдачи курсовой работы в университете происходила генеральная уборка. По случайности, ее курсовая работа оказалась в стопке бумаг на выброс. В результате курсач оказался на помойке, а преподаватель, не обнаружив курсовую работу, поставил ей ноль баллов. По словам Алика, курсовая работа до сих пор лежит в куче мусора во дворе университета.",
|
||||||
"objectives": [
|
"objectives": [
|
||||||
{ "id": "ghost_coursework_find", "text": "Найти курсовую работу Бегимай", "completed": false },
|
{ "id": "ghost_coursework_find", "text": "Найти курсовую работу Бегимай", "completed": false },
|
||||||
{ "id": "ghost_coursework_mark", "text": "Показать курсовую работу Аиде Джаныбековой", "completed": false }
|
{ "id": "ghost_coursework_mark", "text": "Показать курсовую работу Аиде Джаныбековной", "completed": false }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -51,7 +51,7 @@
|
|||||||
"title": "Манасоведение",
|
"title": "Манасоведение",
|
||||||
"status": "Available",
|
"status": "Available",
|
||||||
"autoComplete": true,
|
"autoComplete": true,
|
||||||
"description": "Скоро будет модуль по Манасоведению, а я еще ни разу не ходил на лекции. Сегодня у нас лекция по Манасоведению, надо ее посетить и получить у преподавательницы Аиды Дженибековной задание на модуль.",
|
"description": "Скоро будет модуль по Манасоведению, а я еще ни разу не ходил на лекции. Сегодня у нас лекция по Манасоведению, надо ее посетить и получить у преподавательницы Аиды Джаныбековной задание на модуль.",
|
||||||
"objectives": [
|
"objectives": [
|
||||||
{ "id": "study_beginning_lecture", "text": "Посетить лекцию", "completed": false },
|
{ "id": "study_beginning_lecture", "text": "Посетить лекцию", "completed": false },
|
||||||
{ "id": "study_beginning_task", "text": "Получить задание на модуль", "completed": false }
|
{ "id": "study_beginning_task", "text": "Получить задание на модуль", "completed": false }
|
||||||
@ -61,7 +61,7 @@
|
|||||||
"id": "study_project",
|
"id": "study_project",
|
||||||
"title": "Эссе манасчи",
|
"title": "Эссе манасчи",
|
||||||
"status": "Hidden",
|
"status": "Hidden",
|
||||||
"description": "Преподавательница по манасоведению Аида Джаныбекова дала мне задачу - найти в библиотеке книгу про манасчи Жусупа Мамая, и написать по этой книге эссе. Книгу нельзя выносить из библиотеки, но можно воспользоваться стоящим в библиотеке компьютером, чтобы написать эссе.",
|
"description": "Преподавательница по манасоведению Аида Джаныбековна дала мне задачу - найти в библиотеке книгу про манасчи Жусупа Мамая, и написать по этой книге эссе. Книгу нельзя выносить из библиотеки, но можно воспользоваться стоящим в библиотеке компьютером, чтобы написать эссе.",
|
||||||
"objectives": [
|
"objectives": [
|
||||||
{ "id": "study_project_book", "text": "Найти книгу", "completed": false },
|
{ "id": "study_project_book", "text": "Найти книгу", "completed": false },
|
||||||
{ "id": "study_project_write", "text": "Написать эссе", "completed": false },
|
{ "id": "study_project_write", "text": "Написать эссе", "completed": false },
|
||||||
|
|||||||
60
resources/config_en/items.json
Normal file
60
resources/config_en/items.json
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "phone",
|
||||||
|
"name": "Phone",
|
||||||
|
"description": "I can't imagine life without my phone.",
|
||||||
|
"icon": "resources/w/ui/img/inv/ItemPhone001.png",
|
||||||
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelPhone001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "journal",
|
||||||
|
"name": "Journal",
|
||||||
|
"description": "This is my journal where I write my notes.",
|
||||||
|
"icon": "resources/w/ui/img/inv/ItemJournal001.png",
|
||||||
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelJournal001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "teacher_room_key",
|
||||||
|
"name": "Key to the teachers' room",
|
||||||
|
"description": "This is the key to the teachers' room that I got from Aiperi.",
|
||||||
|
"icon": "resources/w/ui/img/inv/ItemKey001.png",
|
||||||
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelKey001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "knife",
|
||||||
|
"name": "Silver knife",
|
||||||
|
"description": "I borrowed this silver knife from Aiperi, and I must return it.",
|
||||||
|
"icon": "resources/w/ui/img/inv/ItemKnife001.png",
|
||||||
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelKnife001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "book",
|
||||||
|
"name": "Book",
|
||||||
|
"description": "This is a book about the manaschi Zhusup Mamai.",
|
||||||
|
"icon": "resources/w/ui/img/inv/ItemBook001.png",
|
||||||
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelBook001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "note_spell",
|
||||||
|
"name": "Note",
|
||||||
|
"description": "It says, \"Greetings to a fellow shadow from a fellow shadow! I give you a way to get through any locked door! Touch the silver with your hand and read this spell out loud! The spell only works at night.\" and the text of the spell.",
|
||||||
|
"icon": "resources/w/ui/img/inv/ItemNote001.png",
|
||||||
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelNote001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "report_card",
|
||||||
|
"name": "Begimai's grade book",
|
||||||
|
"description": "This is Begimai's grade book. This is where you need to put your grade for your coursework.",
|
||||||
|
"icon": "resources/w/ui/img/inv/ItemReportCard001.png",
|
||||||
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelReportCard001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "coursework",
|
||||||
|
"name": "Begimai's coursework",
|
||||||
|
"description": "This is Begimai's coursework, because of which she jumped out of the window.",
|
||||||
|
"icon": "resources/w/ui/img/inv/ItemCoursework001.png",
|
||||||
|
"selectedIcon": "resources/w/ui/img/inv/ItemSelCoursework001.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
83
resources/config_en/quests.json
Normal file
83
resources/config_en/quests.json
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"quests": [
|
||||||
|
{
|
||||||
|
"id": "tutorial_take_items",
|
||||||
|
"title": "The Phone and the Journal",
|
||||||
|
"status": "Available",
|
||||||
|
"autoComplete": true,
|
||||||
|
"description": "Whenever I go somewhere, I always take my phone and my journal with me.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "take_phone", "text": "Take the phone", "completed": false },
|
||||||
|
{ "id": "take_journal", "text": "Take the journal", "completed": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ghost_lore",
|
||||||
|
"title": "The Ghost",
|
||||||
|
"status": "Hidden",
|
||||||
|
"autoComplete": true,
|
||||||
|
"description": "A ghostly female student appears at the university every night. She claims her name is Begimai and that she has a coursework due. I don't remember a student by that name, but maybe she's from a different year? I need to find out more.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "ghost_lore_aiperi", "text": "Talk to Aiperi", "completed": false },
|
||||||
|
{ "id": "ghost_lore_alik", "text": "Talk to Alik", "completed": false, "visible": false },
|
||||||
|
{ "id": "ghost_lore_teacher", "text": "Talk to Aida Dzhanybekovna", "completed": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ghost_coursework",
|
||||||
|
"title": "The Ghost's coursework",
|
||||||
|
"status": "Hidden",
|
||||||
|
"autoComplete": true,
|
||||||
|
"description": "Begimai's story is even more mysterious. While her coursework was due, the university was undergoing a general cleaning. By chance, her coursework ended up in the trash bin. As a result, her coursework was thrown away, the professor was not able to find it, and gave her a zero grade. According to Alik, the coursework still lies in a pile of trash in the university courtyard.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "ghost_coursework_find", "text": "Find Begimai's coursework", "completed": false },
|
||||||
|
{ "id": "ghost_coursework_mark", "text": "Show coursework to Aida Dzhanybekovna", "completed": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ghost_release",
|
||||||
|
"title": "Liberation",
|
||||||
|
"status": "Hidden",
|
||||||
|
"autoComplete": true,
|
||||||
|
"description": "A student named Begimai actually attended university last year. She tried to submit a coursework, but due to a series of circumstances, she failed and received a zero grade. This coursework shattered all her plans and hopes, and Begimai jumped out the window. Now she's returned as a ghost, and to be liberated, she must ensure she receives a grade for the coursework.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "ghost_release_reportcard", "text": "Find Begimai's grade book", "completed": false },
|
||||||
|
{ "id": "ghost_release_mark", "text": "Put a grade for the coursework in the grade book", "completed": false },
|
||||||
|
{ "id": "ghost_release_show", "text": "Show the grade book to the ghost", "completed": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "study_beginning",
|
||||||
|
"title": "Manas studies",
|
||||||
|
"status": "Available",
|
||||||
|
"autoComplete": true,
|
||||||
|
"description": "There's a Manas Studies module coming up soon, and I've never attended a lecture before. We have a Manas Studies lecture today, so I need to attend it and get the module assignment from my teacher, Aida Dzhanybekovna.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "study_beginning_lecture", "text": "Attend the lecture", "completed": false },
|
||||||
|
{ "id": "study_beginning_task", "text": "Get an assignment for the module", "completed": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "study_project",
|
||||||
|
"title": "Essay on Manaschi",
|
||||||
|
"status": "Hidden",
|
||||||
|
"description": "My Manas studies teacher, Aida Dzhanybekovna, gave me an assignment: find a book about Zhusup Mamai's Manaschi in the library and write an essay about it. The book isn't allowed out of the library, but I can use the library's computer to write the essay.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "study_project_book", "text": "Find the book", "completed": false },
|
||||||
|
{ "id": "study_project_write", "text": "Write the essay", "completed": false },
|
||||||
|
{ "id": "study_project_give", "text": "Submit the essay", "completed": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aiperi_knife",
|
||||||
|
"title": "Silver knife",
|
||||||
|
"status": "Hidden",
|
||||||
|
"description": "Aiperi lent me the silver knife we used to cut the teachers' birthday cake. The knife is still somewhere in the staff room, and I have to get it and return it to Aiperi.",
|
||||||
|
"objectives": [
|
||||||
|
{ "id": "aiperi_knife_keys", "text": "Get the keys to the teachers' room", "completed": false },
|
||||||
|
{ "id": "aiperi_knife_take", "text": "Take the knife", "completed": false },
|
||||||
|
{ "id": "aiperi_knife_give", "text": "Give the knife to Aiperi", "completed": false }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -666,9 +666,9 @@
|
|||||||
"en": "Bekzat, what do you need?"
|
"en": "Bekzat, what do you need?"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "Сдать модуль",
|
"key": "Задание на модуль",
|
||||||
"ru": "Сдать модуль",
|
"ru": "Задание на модуль",
|
||||||
"en": "Submit the module"
|
"en": "Assignment for the module"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "Ничего",
|
"key": "Ничего",
|
||||||
|
|||||||
@ -478,7 +478,7 @@
|
|||||||
{
|
{
|
||||||
"id": "choice_1783179865107",
|
"id": "choice_1783179865107",
|
||||||
"kind": "Main",
|
"kind": "Main",
|
||||||
"text": "Сдать модуль",
|
"text": "Задание на модуль",
|
||||||
"next": "line_14"
|
"next": "line_14"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -499,7 +499,7 @@
|
|||||||
{
|
{
|
||||||
"id": "choice_1783179745949",
|
"id": "choice_1783179745949",
|
||||||
"kind": "Main",
|
"kind": "Main",
|
||||||
"text": "Сдать модуль",
|
"text": "Задание на модуль",
|
||||||
"next": "line_14"
|
"next": "line_14"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -316,7 +316,7 @@ namespace ZL
|
|||||||
gameState.chatPreviewMsg[i].clear();
|
gameState.chatPreviewMsg[i].clear();
|
||||||
gameState.chatHistory[i].clear();
|
gameState.chatHistory[i].clear();
|
||||||
}
|
}
|
||||||
gameState.inventory.items.clear();
|
gameState.inventory.clear();
|
||||||
gameState.questJournal.loadFromFile("resources/config/quests.json", CONST_ZIP_FILE);
|
gameState.questJournal.loadFromFile("resources/config/quests.json", CONST_ZIP_FILE);
|
||||||
createLocations();
|
createLocations();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,6 +21,39 @@ Language languageFromCode(const std::string& code) {
|
|||||||
return Language::Russian;
|
return Language::Russian;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string localizedConfigPath(const std::string& basePath, Language lang) {
|
||||||
|
static const std::string marker = "/config/";
|
||||||
|
const size_t pos = basePath.find(marker);
|
||||||
|
if (pos == std::string::npos) return basePath;
|
||||||
|
return basePath.substr(0, pos) + "/config_" + languageToCode(lang) + "/" + basePath.substr(pos + marker.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string readLocalizedConfigFile(const std::string& basePath, const std::string& zipFile) {
|
||||||
|
const std::string localizedPath = localizedConfigPath(basePath, g_currentLanguage);
|
||||||
|
if (localizedPath != basePath) {
|
||||||
|
std::string content;
|
||||||
|
if (zipFile.empty()) {
|
||||||
|
content = readTextFile(localizedPath);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const auto buf = readFileFromZIP(localizedPath, zipFile);
|
||||||
|
content.assign(buf.begin(), buf.end());
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
content.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!content.empty()) return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zipFile.empty()) return readTextFile(basePath);
|
||||||
|
try {
|
||||||
|
const auto buf = readFileFromZIP(basePath, zipFile);
|
||||||
|
return std::string(buf.begin(), buf.end());
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void loadLanguageSetting() {
|
void loadLanguageSetting() {
|
||||||
const std::string content = readTextFile("settings.json");
|
const std::string content = readTextFile("settings.json");
|
||||||
if (content.empty()) return;
|
if (content.empty()) return;
|
||||||
|
|||||||
@ -14,6 +14,15 @@ extern Language g_currentLanguage;
|
|||||||
std::string languageToCode(Language lang);
|
std::string languageToCode(Language lang);
|
||||||
Language languageFromCode(const std::string& code);
|
Language languageFromCode(const std::string& code);
|
||||||
|
|
||||||
|
// "resources/config/items.json" + English -> "resources/config_en/items.json".
|
||||||
|
// Returns basePath unchanged if it doesn't contain a "/config/" segment.
|
||||||
|
std::string localizedConfigPath(const std::string& basePath, Language lang);
|
||||||
|
|
||||||
|
// Reads basePath, preferring the config_<lang> variant for g_currentLanguage;
|
||||||
|
// falls back to basePath if the localized file/zip-entry is missing or empty.
|
||||||
|
// zipFile empty => loose files from disk; non-empty => entries inside that zip.
|
||||||
|
std::string readLocalizedConfigFile(const std::string& basePath, const std::string& zipFile);
|
||||||
|
|
||||||
// Reads the "language" field from settings.json (if present) and updates
|
// Reads the "language" field from settings.json (if present) and updates
|
||||||
// g_currentLanguage. Safe to call even if settings.json doesn't exist yet.
|
// g_currentLanguage. Safe to call even if settings.json doesn't exist yet.
|
||||||
// Must run before any location/dialogue content is loaded, since some
|
// Must run before any location/dialogue content is loaded, since some
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "MenuManager.h"
|
#include "MenuManager.h"
|
||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include "Localization.h"
|
#include "Localization.h"
|
||||||
|
#include "items/ItemRegistry.h"
|
||||||
#include "render/TextRenderer.h"
|
#include "render/TextRenderer.h"
|
||||||
#include "utils/Utils.h"
|
#include "utils/Utils.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -414,12 +415,19 @@ namespace ZL {
|
|||||||
|
|
||||||
uiManager.setTextButtonCallback("languageRussianButton", [this](const std::string&) {
|
uiManager.setTextButtonCallback("languageRussianButton", [this](const std::string&) {
|
||||||
ZL::g_currentLanguage = ZL::Language::Russian;
|
ZL::g_currentLanguage = ZL::Language::Russian;
|
||||||
|
reloadLocalizedGameContent();
|
||||||
});
|
});
|
||||||
uiManager.setTextButtonCallback("languageEnglishButton", [this](const std::string&) {
|
uiManager.setTextButtonCallback("languageEnglishButton", [this](const std::string&) {
|
||||||
ZL::g_currentLanguage = ZL::Language::English;
|
ZL::g_currentLanguage = ZL::Language::English;
|
||||||
|
reloadLocalizedGameContent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MenuManager::reloadLocalizedGameContent() {
|
||||||
|
ItemRegistry::instance().loadFromJson("resources/config/items.json", CONST_ZIP_FILE);
|
||||||
|
gameState_.questJournal.reloadDefinitionsPreservingState("resources/config/quests.json", CONST_ZIP_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
void MenuManager::showLoadGameScreen()
|
void MenuManager::showLoadGameScreen()
|
||||||
{
|
{
|
||||||
uiManager.pushMenuFromSavedRoot(loadSavedGameScreenRoot);
|
uiManager.pushMenuFromSavedRoot(loadSavedGameScreenRoot);
|
||||||
|
|||||||
@ -118,6 +118,7 @@ namespace ZL {
|
|||||||
void enterGameplay(bool isFromLoad = false);
|
void enterGameplay(bool isFromLoad = false);
|
||||||
void applyHudForCurrentState();
|
void applyHudForCurrentState();
|
||||||
void refreshQuestJournalUi();
|
void refreshQuestJournalUi();
|
||||||
|
void reloadLocalizedGameContent();
|
||||||
void selectQuestByIndex(int index);
|
void selectQuestByIndex(int index);
|
||||||
void refreshItemPickupHud();
|
void refreshItemPickupHud();
|
||||||
void setupStep5Callbacks();
|
void setupStep5Callbacks();
|
||||||
|
|||||||
@ -124,7 +124,7 @@ namespace ZL {
|
|||||||
api.set_function("pickup_item", [inventory](const std::string& itemId) {
|
api.set_function("pickup_item", [inventory](const std::string& itemId) {
|
||||||
const Item* item = ItemRegistry::instance().findById(itemId);
|
const Item* item = ItemRegistry::instance().findById(itemId);
|
||||||
if (item) {
|
if (item) {
|
||||||
inventory->addItem(*item);
|
inventory->addItem(item->id);
|
||||||
std::cout << "[script] pickup_item: " << item->name << std::endl;
|
std::cout << "[script] pickup_item: " << item->name << std::endl;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[script] pickup_item: item '" << itemId << "' not found in ItemRegistry\n";
|
std::cerr << "[script] pickup_item: item '" << itemId << "' not found in ItemRegistry\n";
|
||||||
|
|||||||
@ -1,56 +1,55 @@
|
|||||||
#include "Item.h"
|
#include "Item.h"
|
||||||
|
#include "ItemRegistry.h"
|
||||||
#include "external/nlohmann/json.hpp"
|
#include "external/nlohmann/json.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
void Inventory::addItem(const Item& item) {
|
void Inventory::addItem(const std::string& itemId) {
|
||||||
items.push_back(item);
|
itemIds.push_back(itemId);
|
||||||
std::cout << "Item added to inventory: " << item.name << std::endl;
|
std::cout << "Item added to inventory: " << itemId << std::endl;
|
||||||
if (onItemAdded) onItemAdded(item.id);
|
if (onItemAdded) onItemAdded(itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Inventory::removeItem(const std::string& itemId) {
|
void Inventory::removeItem(const std::string& itemId) {
|
||||||
auto it = std::find_if(items.begin(), items.end(),
|
auto it = std::find(itemIds.begin(), itemIds.end(), itemId);
|
||||||
[&itemId](const Item& item) { return item.id == itemId; });
|
if (it != itemIds.end()) {
|
||||||
if (it != items.end()) {
|
std::cout << "Item removed from inventory: " << itemId << std::endl;
|
||||||
std::cout << "Item removed from inventory: " << it->name << std::endl;
|
|
||||||
if (onItemRemoved) onItemRemoved(itemId);
|
if (onItemRemoved) onItemRemoved(itemId);
|
||||||
items.erase(it);
|
itemIds.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Inventory::hasItem(const std::string& itemId) const {
|
bool Inventory::hasItem(const std::string& itemId) const {
|
||||||
return std::find_if(items.begin(), items.end(),
|
return std::find(itemIds.begin(), itemIds.end(), itemId) != itemIds.end();
|
||||||
[&itemId](const Item& item) { return item.id == itemId; }) != items.end();
|
}
|
||||||
|
|
||||||
|
std::vector<Item> Inventory::getItems() const {
|
||||||
|
std::vector<Item> result;
|
||||||
|
result.reserve(itemIds.size());
|
||||||
|
for (const auto& id : itemIds) {
|
||||||
|
const Item* def = ItemRegistry::instance().findById(id);
|
||||||
|
if (def) result.push_back(*def);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Inventory::save(nlohmann::json& out) const {
|
void Inventory::save(nlohmann::json& out) const {
|
||||||
nlohmann::json arr = nlohmann::json::array();
|
out["items"] = itemIds;
|
||||||
for (const auto& item : items) {
|
|
||||||
arr.push_back({
|
|
||||||
{"id", item.id},
|
|
||||||
{"name", item.name},
|
|
||||||
{"description", item.description},
|
|
||||||
{"icon", item.icon},
|
|
||||||
{"selectedIcon", item.selectedIcon}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
out["items"] = std::move(arr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Inventory::load(const nlohmann::json& in) {
|
void Inventory::load(const nlohmann::json& in) {
|
||||||
items.clear();
|
itemIds.clear();
|
||||||
if (!in.contains("items") || !in["items"].is_array()) return;
|
if (!in.contains("items") || !in["items"].is_array()) return;
|
||||||
for (const auto& j : in["items"]) {
|
for (const auto& j : in["items"]) {
|
||||||
Item item;
|
if (j.is_string()) {
|
||||||
item.id = j.value("id", "");
|
itemIds.push_back(j.get<std::string>());
|
||||||
item.name = j.value("name", "");
|
} else if (j.is_object()) {
|
||||||
item.description = j.value("description", "");
|
// Backward-compat with old save files that stored full item structs.
|
||||||
item.icon = j.value("icon", "");
|
const std::string id = j.value("id", "");
|
||||||
item.selectedIcon = j.value("selectedIcon", "");
|
if (!id.empty()) itemIds.push_back(id);
|
||||||
items.push_back(std::move(item));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,13 +22,14 @@ namespace ZL {
|
|||||||
|
|
||||||
class Inventory : public ISaveable {
|
class Inventory : public ISaveable {
|
||||||
public:
|
public:
|
||||||
std::vector<Item> items;
|
void addItem(const std::string& itemId);
|
||||||
void addItem(const Item& item);
|
|
||||||
void removeItem(const std::string& itemId);
|
void removeItem(const std::string& itemId);
|
||||||
const std::vector<Item>& getItems() const { return items; }
|
// Resolves current items from ItemRegistry by id, so returned data
|
||||||
|
// always reflects the currently loaded (localized) definitions.
|
||||||
|
std::vector<Item> getItems() const;
|
||||||
bool hasItem(const std::string& itemId) const;
|
bool hasItem(const std::string& itemId) const;
|
||||||
void clear() { items.clear(); }
|
void clear() { itemIds.clear(); }
|
||||||
size_t getCount() const { return items.size(); }
|
size_t getCount() const { return itemIds.size(); }
|
||||||
|
|
||||||
void save(nlohmann::json& out) const override;
|
void save(nlohmann::json& out) const override;
|
||||||
void load(const nlohmann::json& in) override;
|
void load(const nlohmann::json& in) override;
|
||||||
@ -36,6 +37,9 @@ namespace ZL {
|
|||||||
// Called whenever an item is added/removed; receives the item id.
|
// Called whenever an item is added/removed; receives the item id.
|
||||||
std::function<void(const std::string&)> onItemAdded;
|
std::function<void(const std::string&)> onItemAdded;
|
||||||
std::function<void(const std::string&)> onItemRemoved;
|
std::function<void(const std::string&)> onItemRemoved;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> itemIds;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
@ -1,5 +1,6 @@
|
|||||||
#include "ItemRegistry.h"
|
#include "ItemRegistry.h"
|
||||||
#include "utils/Utils.h"
|
#include "utils/Utils.h"
|
||||||
|
#include "Localization.h"
|
||||||
#include "external/nlohmann/json.hpp"
|
#include "external/nlohmann/json.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -11,20 +12,9 @@ ItemRegistry& ItemRegistry::instance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ItemRegistry::loadFromJson(const std::string& jsonPath, const std::string& zipPath) {
|
void ItemRegistry::loadFromJson(const std::string& jsonPath, const std::string& zipPath) {
|
||||||
std::string content;
|
const std::string content = readLocalizedConfigFile(jsonPath, zipPath);
|
||||||
try {
|
if (content.empty()) {
|
||||||
if (zipPath.empty()) {
|
std::cerr << "[ItemRegistry] Failed to read " << jsonPath << "\n";
|
||||||
content = readTextFile(jsonPath);
|
|
||||||
} else {
|
|
||||||
auto buf = readFileFromZIP(jsonPath, zipPath);
|
|
||||||
if (buf.empty()) {
|
|
||||||
std::cerr << "[ItemRegistry] Failed to read " << jsonPath << " from zip\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
content.assign(buf.begin(), buf.end());
|
|
||||||
}
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "[ItemRegistry] Failed to open " << jsonPath << ": " << e.what() << "\n";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "quest/QuestJournal.h"
|
#include "quest/QuestJournal.h"
|
||||||
#include "external/nlohmann/json.hpp"
|
#include "external/nlohmann/json.hpp"
|
||||||
#include "utils/Utils.h"
|
#include "utils/Utils.h"
|
||||||
|
#include "Localization.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -26,26 +27,9 @@ static QuestStatus parseQuestStatus(const std::string& value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool QuestJournal::loadFromFile(const std::string& path, const std::string& zipFile) {
|
bool QuestJournal::loadFromFile(const std::string& path, const std::string& zipFile) {
|
||||||
quests.clear();
|
const std::string content = ZL::readLocalizedConfigFile(path, zipFile);
|
||||||
questOrder.clear();
|
|
||||||
|
|
||||||
std::string content;
|
|
||||||
try {
|
|
||||||
if (zipFile.empty()) {
|
|
||||||
content = ZL::readTextFile(path);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
auto data = ZL::readFileFromZIP(path, zipFile);
|
|
||||||
content.assign(data.begin(), data.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const std::exception& e) {
|
|
||||||
std::cerr << "[quest] Failed to read " << path << ": " << e.what() << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content.empty()) {
|
if (content.empty()) {
|
||||||
std::cerr << "[quest] Empty quest file: " << path << std::endl;
|
std::cerr << "[quest] Failed to read " << path << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +47,12 @@ bool QuestJournal::loadFromFile(const std::string& path, const std::string& zipF
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only clear existing definitions/state once the new file is confirmed
|
||||||
|
// valid, so a failed reload (e.g. a broken localized file) doesn't wipe
|
||||||
|
// the currently loaded quest journal.
|
||||||
|
quests.clear();
|
||||||
|
questOrder.clear();
|
||||||
|
|
||||||
for (const auto& item : root["quests"]) {
|
for (const auto& item : root["quests"]) {
|
||||||
QuestDefinition def;
|
QuestDefinition def;
|
||||||
def.id = item.value("id", "");
|
def.id = item.value("id", "");
|
||||||
@ -101,6 +91,14 @@ bool QuestJournal::loadFromFile(const std::string& path, const std::string& zipF
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QuestJournal::reloadDefinitionsPreservingState(const std::string& path, const std::string& zipFile) {
|
||||||
|
json snapshot;
|
||||||
|
save(snapshot);
|
||||||
|
if (!loadFromFile(path, zipFile)) return false;
|
||||||
|
load(snapshot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool QuestJournal::setStatus(const std::string& questId, QuestStatus status) {
|
bool QuestJournal::setStatus(const std::string& questId, QuestStatus status) {
|
||||||
QuestState* quest = findQuest(questId);
|
QuestState* quest = findQuest(questId);
|
||||||
if (!quest) return false;
|
if (!quest) return false;
|
||||||
|
|||||||
@ -14,6 +14,11 @@ class QuestJournal : public ZL::ISaveable {
|
|||||||
public:
|
public:
|
||||||
bool loadFromFile(const std::string& path, const std::string& zipFile = "");
|
bool loadFromFile(const std::string& path, const std::string& zipFile = "");
|
||||||
|
|
||||||
|
// Reloads quest definitions (e.g. after a language switch) while
|
||||||
|
// preserving current progress: snapshots status/objective state, reloads
|
||||||
|
// definitions from path, then re-applies the snapshot.
|
||||||
|
bool reloadDefinitionsPreservingState(const std::string& path, const std::string& zipFile = "");
|
||||||
|
|
||||||
void save(nlohmann::json& out) const override;
|
void save(nlohmann::json& out) const override;
|
||||||
void load(const nlohmann::json& in) override;
|
void load(const nlohmann::json& in) override;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user