Working on translations for dialogs and cutscenes
This commit is contained in:
parent
a98f71676d
commit
794a820be9
61
dialogue_translations_from_xlsx.py
Normal file
61
dialogue_translations_from_xlsx.py
Normal file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
dialogue_translations_from_xlsx.py - Import a translated XLSX spreadsheet back
|
||||
into dialogue_translations.json.
|
||||
|
||||
Reads a spreadsheet with a "key" header column followed by one column per
|
||||
language (as produced by dialogue_translations_to_xlsx.py, possibly edited by a
|
||||
translator), and writes it back out as {"dialogue_translations": [{"key":...,
|
||||
"<lang>":...}, ...]}. Rows with an empty key are skipped. Cell values are
|
||||
stripped; missing/empty cells become "".
|
||||
|
||||
Usage:
|
||||
python dialogue_translations_from_xlsx.py <input.xlsx> <translations.json>
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from openpyxl import load_workbook
|
||||
except ImportError:
|
||||
raise SystemExit("openpyxl is required: pip install openpyxl")
|
||||
|
||||
|
||||
def import_from_xlsx(xlsx_path: str, json_path: str) -> None:
|
||||
wb = load_workbook(xlsx_path, read_only=True, data_only=True)
|
||||
ws = wb.active
|
||||
|
||||
rows = ws.iter_rows(values_only=True)
|
||||
header = next(rows, None)
|
||||
if not header or header[0] != "key":
|
||||
raise SystemExit(f"Expected first column header to be 'key', got: {header}")
|
||||
|
||||
languages = [str(col) for col in header[1:] if col is not None]
|
||||
|
||||
entries = []
|
||||
for row in rows:
|
||||
key = row[0]
|
||||
if key is None or str(key).strip() == "":
|
||||
continue
|
||||
|
||||
entry = {"key": str(key)}
|
||||
for i, lang in enumerate(languages):
|
||||
value = row[i + 1] if i + 1 < len(row) else None
|
||||
entry[lang] = str(value).strip() if value is not None else ""
|
||||
entries.append(entry)
|
||||
|
||||
os.makedirs(os.path.dirname(os.path.abspath(json_path)), exist_ok=True)
|
||||
with open(json_path, 'w', encoding='utf-8') as f:
|
||||
json.dump({"dialogue_translations": entries}, f, indent=4, ensure_ascii=False)
|
||||
|
||||
print(f"Saved: {json_path} ({len(entries)} row(s), languages: {', '.join(languages)})")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
print(f"Usage: {sys.argv[0]} <input.xlsx> <translations.json>")
|
||||
sys.exit(1)
|
||||
|
||||
import_from_xlsx(sys.argv[1], sys.argv[2])
|
||||
66
dialogue_translations_to_xlsx.py
Normal file
66
dialogue_translations_to_xlsx.py
Normal file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
dialogue_translations_to_xlsx.py - Export dialogue_translations.json to an XLSX
|
||||
spreadsheet for translators.
|
||||
|
||||
Reads a translation file shaped as {"dialogue_translations": [{"key":..., "ru":...,
|
||||
"en":...}, ...]}, and writes one row per entry with a "key" column followed by one
|
||||
column per language found in the file (order: ru, en, then any others found,
|
||||
alphabetically). Missing values are written as empty cells.
|
||||
|
||||
Usage:
|
||||
python dialogue_translations_to_xlsx.py <translations.json> <output.xlsx>
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
try:
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.utils import get_column_letter
|
||||
except ImportError:
|
||||
raise SystemExit("openpyxl is required: pip install openpyxl")
|
||||
|
||||
PREFERRED_LANGUAGE_ORDER = ("ru", "en")
|
||||
|
||||
|
||||
def collect_languages(entries: list[dict]) -> list[str]:
|
||||
found = set()
|
||||
for entry in entries:
|
||||
found.update(k for k in entry.keys() if k != "key")
|
||||
|
||||
ordered = [lang for lang in PREFERRED_LANGUAGE_ORDER if lang in found]
|
||||
ordered += sorted(found - set(ordered))
|
||||
return ordered
|
||||
|
||||
|
||||
def export_to_xlsx(json_path: str, xlsx_path: str) -> None:
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
entries = data.get("dialogue_translations", [])
|
||||
languages = collect_languages(entries)
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "dialogue_translations"
|
||||
|
||||
ws.append(["key"] + languages)
|
||||
for entry in entries:
|
||||
ws.append([entry.get("key", "")] + [entry.get(lang, "") for lang in languages])
|
||||
|
||||
ws.freeze_panes = "A2"
|
||||
ws.column_dimensions["A"].width = 50
|
||||
for col_index in range(len(languages)):
|
||||
ws.column_dimensions[get_column_letter(col_index + 2)].width = 50
|
||||
|
||||
wb.save(xlsx_path)
|
||||
print(f"Saved: {xlsx_path} ({len(entries)} row(s), languages: {', '.join(languages)})")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
print(f"Usage: {sys.argv[0]} <translations.json> <output.xlsx>")
|
||||
sys.exit(1)
|
||||
|
||||
export_to_xlsx(sys.argv[1], sys.argv[2])
|
||||
@ -69,6 +69,8 @@ set(SOURCES
|
||||
../src/CharacterState.h
|
||||
../src/Environment.cpp
|
||||
../src/Environment.h
|
||||
../src/Localization.cpp
|
||||
../src/Localization.h
|
||||
../src/render/Renderer.cpp
|
||||
../src/render/Renderer.h
|
||||
../src/render/ShaderManager.cpp
|
||||
@ -133,6 +135,8 @@ set(SOURCES
|
||||
../src/dialogue/DialogueOverlay.cpp
|
||||
../src/dialogue/DialogueSystem.h
|
||||
../src/dialogue/DialogueSystem.cpp
|
||||
../src/dialogue/TranslationDatabase.h
|
||||
../src/dialogue/TranslationDatabase.cpp
|
||||
../src/quest/QuestTypes.h
|
||||
../src/quest/QuestJournal.h
|
||||
../src/quest/QuestJournal.cpp
|
||||
|
||||
@ -20,6 +20,8 @@ add_executable(witcher001
|
||||
../src/CharacterState.h
|
||||
../src/Environment.cpp
|
||||
../src/Environment.h
|
||||
../src/Localization.cpp
|
||||
../src/Localization.h
|
||||
../src/render/Renderer.cpp
|
||||
../src/render/Renderer.h
|
||||
../src/render/ShaderManager.cpp
|
||||
@ -84,6 +86,8 @@ add_executable(witcher001
|
||||
../src/dialogue/DialogueOverlay.cpp
|
||||
../src/dialogue/DialogueSystem.h
|
||||
../src/dialogue/DialogueSystem.cpp
|
||||
../src/dialogue/TranslationDatabase.h
|
||||
../src/dialogue/TranslationDatabase.cpp
|
||||
../src/quest/QuestTypes.h
|
||||
../src/quest/QuestJournal.h
|
||||
../src/quest/QuestJournal.cpp
|
||||
|
||||
@ -92,7 +92,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Айпери",
|
||||
"portrait": "resources/dialogue/portrait_phone.png",
|
||||
"text": "Бекзат, помнишь мы скидывались на торт для Аиды Джаныбековой? Я тогда еще приносила скатерть, тарелки и нож для торта. И я до сих пор не получила назад ничего.",
|
||||
"text": "Бекзат, помнишь мы скидывались на торт для Аиды Джаныбековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
|
||||
"next": "line_2",
|
||||
"chatBubble": "in"
|
||||
},
|
||||
|
||||
134
resources/dialogue/translate_cutscenes.json
Normal file
134
resources/dialogue/translate_cutscenes.json
Normal file
@ -0,0 +1,134 @@
|
||||
{
|
||||
"dialogue_translations": [
|
||||
{
|
||||
"key": "Аида Джаныбековна",
|
||||
"ru": "Аида Джаныбековна",
|
||||
"en": "Aida Dzhanybekovna"
|
||||
},
|
||||
{
|
||||
"key": "Опаздывающие, заходите скорее и занимайте свои места! Лекция начинается!",
|
||||
"ru": "Опаздывающие, заходите скорее и занимайте свои места! Лекция начинается!",
|
||||
"en": "Latecomers, come in quickly and take your seats! The lecture is about to begin!"
|
||||
},
|
||||
{
|
||||
"key": "В эпизоде \"Семетей\" трилогии \"Манас\", изменники Канчоро и Кыяз захватывают власть над кыргызами.",
|
||||
"ru": "В эпизоде \"Семетей\" трилогии \"Манас\", изменники Канчоро и Кыяз захватывают власть над кыргызами.",
|
||||
"en": "In the episode \"Semetey\" of the trilogy \"Manas\", the traitors Kanchoro and Kyyaz seize power over the Kyrgyz."
|
||||
},
|
||||
{
|
||||
"key": "Чтобы спасти раненого богатыря Семетея, фея Кёкмончок с помощью заклинания уводит его в иной мир.",
|
||||
"ru": "Чтобы спасти раненого богатыря Семетея, фея Кёкмончок с помощью заклинания уводит его в иной мир.",
|
||||
"en": "To save the wounded hero Semetey, the fairy Kyokmonchok uses a spell to take him to another world."
|
||||
},
|
||||
{
|
||||
"key": "Этот мир описан в эпосе Манас как Кайып или Аль-Гайб, но некоторые ученые называют его миром теней.",
|
||||
"ru": "Этот мир описан в эпосе Манас как Кайып или Аль-Гайб, но некоторые ученые называют его миром теней.",
|
||||
"en": "This world is described in the Manas epic as Kayip or Al-Ghayb, but some scholars call it the dark lands."
|
||||
},
|
||||
{
|
||||
"key": "В этом мире обитают феи, духи и джинны. Простым смертным в этот мир дорога закрыта.",
|
||||
"ru": "В этом мире обитают феи, духи и джинны. Простым смертным в этот мир дорога закрыта.",
|
||||
"en": "This world is inhabited by fairies, spirits, and genies. Mere mortals are barred from entering."
|
||||
},
|
||||
{
|
||||
"key": "Время там течет по другому - за один день в теневом мире могут пройти годы жизни обычного мира.",
|
||||
"ru": "Время там течет по другому - за один день в теневом мире могут пройти годы жизни обычного мира.",
|
||||
"en": "Time flows differently there - in one day in the dark lands, years of life in the ordinary world can pass."
|
||||
},
|
||||
{
|
||||
"key": "Обычно, мир теней никак не пересекается с нашим миром живых людей.",
|
||||
"ru": "Обычно, мир теней никак не пересекается с нашим миром живых людей.",
|
||||
"en": "Usually, the world of dark lands does not intersect with our world of living people."
|
||||
},
|
||||
{
|
||||
"key": "Но в критические моменты для народа, обитатели теневого мира могут приходить в наш мир.",
|
||||
"ru": "Но в критические моменты для народа, обитатели теневого мира могут приходить в наш мир.",
|
||||
"en": "But in critical moments for the people, the inhabitants of the dark lands can come to our world."
|
||||
},
|
||||
{
|
||||
"key": "Совсем недавно закончилась пандемия, а сегодня мир захлестнули кровавые войны.",
|
||||
"ru": "Совсем недавно закончилась пандемия, а сегодня мир захлестнули кровавые войны.",
|
||||
"en": "The pandemic just ended, and today the world is engulfed in bloody wars."
|
||||
},
|
||||
{
|
||||
"key": "В такие кризисные моменты истории, грань между мирами становится особенно тонкой.",
|
||||
"ru": "В такие кризисные моменты истории, грань между мирами становится особенно тонкой.",
|
||||
"en": "At such critical moments in history, the line between worlds becomes especially thin."
|
||||
},
|
||||
{
|
||||
"key": "На сегодня лекция завершена. Все свободны! Задания на этот модуль вы получите индивидуально!",
|
||||
"ru": "На сегодня лекция завершена. Все свободны! Задания на этот модуль вы получите индивидуально!",
|
||||
"en": "This lecture is over for today. Everyone is free to go! You will receive assignments for this module individually!"
|
||||
},
|
||||
{
|
||||
"key": "Я сел за компьютер и начал писать эссе по книге",
|
||||
"ru": "Я сел за компьютер и начал писать эссе по книге",
|
||||
"en": "I sat down at the computer and started writing an essay on the book."
|
||||
},
|
||||
{
|
||||
"key": "Книга была очень скучной. Как будто кто-то наложил на книгу чары сна.",
|
||||
"ru": "Книга была очень скучной. Как будто кто-то наложил на книгу чары сна.",
|
||||
"en": "The book was very boring. It was as if someone had cast a sleeping spell on it."
|
||||
},
|
||||
{
|
||||
"key": "Неудивительно что я стал понемногу засыпать...",
|
||||
"ru": "Неудивительно что я стал понемногу засыпать...",
|
||||
"en": "It's no wonder that I started to fall asleep little by little..."
|
||||
},
|
||||
{
|
||||
"key": "Мгновенно как я упал без сил, что-то сверкнуло.",
|
||||
"ru": "Мгновенно как я упал без сил, что-то сверкнуло.",
|
||||
"en": "The moment I fell down without strength, something flashed."
|
||||
},
|
||||
{
|
||||
"key": "Я очнулся и понял, что я по-прежнему в универе.",
|
||||
"ru": "Я очнулся и понял, что я по-прежнему в универе.",
|
||||
"en": "I woke up and realized that I was still at university."
|
||||
},
|
||||
{
|
||||
"key": "Все тело болело, как будто я всю ночь таскал мешки с цементом.",
|
||||
"ru": "Все тело болело, как будто я всю ночь таскал мешки с цементом.",
|
||||
"en": "My whole body ached, as if I had been carrying bags of cement all night."
|
||||
},
|
||||
{
|
||||
"key": "А еще мне сильно хотелось спать...",
|
||||
"ru": "А еще мне сильно хотелось спать...",
|
||||
"en": "And I also really wanted to sleep..."
|
||||
},
|
||||
{
|
||||
"key": "Я лег спать и уснул.",
|
||||
"ru": "Я лег спать и уснул.",
|
||||
"en": "I went to bed and fell asleep."
|
||||
},
|
||||
{
|
||||
"key": "Долгожданный сон полностью восстановил мои силы.",
|
||||
"ru": "Долгожданный сон полностью восстановил мои силы.",
|
||||
"en": "The long-awaited sleep completely restored my strength."
|
||||
},
|
||||
{
|
||||
"key": "Я проснулся на следующий день уже после обеда.",
|
||||
"ru": "Я проснулся на следующий день уже после обеда.",
|
||||
"en": "I woke up the next day after lunch."
|
||||
},
|
||||
{
|
||||
"key": "Все призраки исчезли, вокруг стало тихо.",
|
||||
"ru": "Все призраки исчезли, вокруг стало тихо.",
|
||||
"en": "All the ghosts disappeared and it became quiet around."
|
||||
},
|
||||
{
|
||||
"key": "Кажется, у меня получилось изгнать призрака.",
|
||||
"ru": "Кажется, у меня получилось изгнать призрака.",
|
||||
"en": "I think I've exorcised the ghost."
|
||||
},
|
||||
{
|
||||
"key": "Теперь надо добавить этот пункт к себе в резюме.",
|
||||
"ru": "Теперь надо добавить этот пункт к себе в резюме.",
|
||||
"en": "Now I need to add this item to my resume."
|
||||
},
|
||||
{
|
||||
"key": "В тот момент я еще не знал, что мои приключения еще только начинаются...",
|
||||
"ru": "В тот момент я еще не знал, что мои приключения еще только начинаются...",
|
||||
"en": "At that moment, I didn't yet know that my adventures were just beginning..."
|
||||
}
|
||||
]
|
||||
}
|
||||
2259
resources/dialogue/translate_dialogues.json
Normal file
2259
resources/dialogue/translate_dialogues.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -271,7 +271,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Айпери",
|
||||
"portrait": "resources/dialogue/portrait_phone.png",
|
||||
"text": "Бекзат, помнишь мы скидывались на торт для Аиды Джаныбековой? Я тогда еще приносила скатерть, тарелки и нож для торта. И я до сих пор не получила назад ничего.",
|
||||
"text": "Бекзат, помнишь мы скидывались на торт для Аиды Джаныбековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
|
||||
"next": "line_2",
|
||||
"chatBubble": "in"
|
||||
},
|
||||
|
||||
@ -156,7 +156,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Айпери",
|
||||
"portrait": "resources/dialogue/portrait_aiperi.png",
|
||||
"text": "Помнишь мы скидывались на торт для Аиды Джаныбековой? Я тогда еще приносила скатерть, тарелки и нож для торта. И я до сих пор не получила назад ничего.",
|
||||
"text": "Помнишь мы скидывались на торт для Аиды Джаныбековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
|
||||
"next": "line_7"
|
||||
},
|
||||
{
|
||||
@ -212,7 +212,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Айпери",
|
||||
"portrait": "resources/dialogue/portrait_aiperi.png",
|
||||
"text": "А во вторых, мне как-то не хочется попадаться на глаза Аиде. Я даже лекцию пропустила.",
|
||||
"text": "А во вторых, мне как-то не хочется попадаться на глаза Аиде.",
|
||||
"next": "line_14"
|
||||
},
|
||||
{
|
||||
@ -236,7 +236,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Айпери",
|
||||
"portrait": "resources/dialogue/portrait_aiperi.png",
|
||||
"text": "Да... Только не говори Аиде Джаныбековой что я здесь.",
|
||||
"text": "Да... Только не говори Аиде Джаныбековной что я здесь.",
|
||||
"next": "line_18"
|
||||
},
|
||||
{
|
||||
@ -268,7 +268,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Айпери",
|
||||
"portrait": "resources/dialogue/portrait_aiperi.png",
|
||||
"text": "Постарайся только Аиде Джаныбековой особо на глаза не попадаться. ",
|
||||
"text": "Постарайся только Аиде Джаныбековной особо на глаза не попадаться. ",
|
||||
"next": "line_23"
|
||||
},
|
||||
{
|
||||
@ -527,7 +527,7 @@
|
||||
{
|
||||
"id": "line_8",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Зачем тебе в учительскую?",
|
||||
"next": "line_9"
|
||||
@ -543,15 +543,15 @@
|
||||
{
|
||||
"id": "line_10",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Это разве твой нож? Этот нож мне одолжила Айпери.",
|
||||
"text": "Это разве твой нож? Этот нож принадлежит Айпери.",
|
||||
"next": "line_11"
|
||||
},
|
||||
{
|
||||
"id": "line_11",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Пусть она сама лично и приходит.",
|
||||
"next": "line_12"
|
||||
@ -559,7 +559,7 @@
|
||||
{
|
||||
"id": "line_12",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "А ты даже не смей заходить в учительскую без моего разрешения!",
|
||||
"next": "condition_3"
|
||||
@ -569,7 +569,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Бекзат",
|
||||
"portrait": "resources/dialogue/portrait_hero_neutral.png",
|
||||
"text": "Хорошо....",
|
||||
"text": "Хорошо...",
|
||||
"next": "line_5"
|
||||
},
|
||||
{
|
||||
@ -596,7 +596,7 @@
|
||||
{
|
||||
"id": "line_3",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Тогда не отвлекай меня, я занята!",
|
||||
"next": "condition_2"
|
||||
@ -617,7 +617,7 @@
|
||||
{
|
||||
"id": "line_5",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Кстати, если встретишь Айпери, передай ей, чтобы она подошла ко мне.",
|
||||
"next": "line_6"
|
||||
@ -625,9 +625,9 @@
|
||||
{
|
||||
"id": "line_6",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я ей отдам нож, и заодно я дам ей новоен задание на модуль.",
|
||||
"text": "Я ей отдам нож, и заодно я дам ей новое задание на модуль.",
|
||||
"next": "setflag_1"
|
||||
},
|
||||
{
|
||||
@ -646,7 +646,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Бекзат",
|
||||
"portrait": "resources/dialogue/portrait_hero_neutral.png",
|
||||
"text": "Хорошо, извините, я пойду....",
|
||||
"text": "Хорошо, извините, я пойду...",
|
||||
"next": "end_1"
|
||||
},
|
||||
{
|
||||
@ -677,7 +677,7 @@
|
||||
{
|
||||
"id": "line_20",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я же сказала, дай мне сначала найти книги в библиотеке.",
|
||||
"next": "line_21"
|
||||
@ -685,7 +685,7 @@
|
||||
{
|
||||
"id": "line_21",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Когда я закончу с книжными делами, подойдешь ко мне и получишь задание.",
|
||||
"next": "line_4"
|
||||
@ -693,7 +693,7 @@
|
||||
{
|
||||
"id": "line_15",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я тебя сегодня впервые вижу на парах.",
|
||||
"next": "line_16"
|
||||
@ -709,7 +709,7 @@
|
||||
{
|
||||
"id": "line_17",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Хорошо, я дам тебе какое-нибудь задание, но не сейчас.",
|
||||
"next": "line_18"
|
||||
@ -717,7 +717,7 @@
|
||||
{
|
||||
"id": "line_18",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я занята, как видишь, я пытаюсь разобраться в каталоге книг в этой библиотеке.",
|
||||
"next": "line_19"
|
||||
@ -725,7 +725,7 @@
|
||||
{
|
||||
"id": "line_19",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Когда я закончу, можешь подойти.",
|
||||
"next": "setflag_2"
|
||||
@ -759,7 +759,7 @@
|
||||
{
|
||||
"id": "line_7",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Тебя не предупреждали, что без моего разрешения в учительскую заходить нельзя?",
|
||||
"next": "line_8"
|
||||
@ -767,7 +767,7 @@
|
||||
{
|
||||
"id": "line_8",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Как ты вообще сюда попал?",
|
||||
"next": "line_9"
|
||||
@ -783,7 +783,7 @@
|
||||
{
|
||||
"id": "line_10",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Вот лиса! Я же сказала ей, чтобы она лично забрала нож. А она взяла и тебя отправила.",
|
||||
"next": "line_11"
|
||||
@ -791,7 +791,7 @@
|
||||
{
|
||||
"id": "line_11",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Ты точно отдашь этот нож Айпери?",
|
||||
"next": "line_21"
|
||||
@ -807,7 +807,7 @@
|
||||
{
|
||||
"id": "line_22",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Хорошо. Когда встретишь ее, отдай нож и передай ей, что я ее жду.",
|
||||
"next": "line_12"
|
||||
@ -823,7 +823,7 @@
|
||||
{
|
||||
"id": "line_13",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Нет, Бекзат. Ты вломился в учительскую, и теперь хочешь просто так уйти? Не получится.",
|
||||
"next": "line_14"
|
||||
@ -831,7 +831,7 @@
|
||||
{
|
||||
"id": "line_14",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я придумала для тебя особенное задание на этот модуль.",
|
||||
"next": "line_2"
|
||||
@ -847,7 +847,7 @@
|
||||
{
|
||||
"id": "line_15",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Ты не сможешь найти ничего по этой теме в интернете. Даже и не пытайся. Тебе придется писать эссе по книге.",
|
||||
"next": "line_3"
|
||||
@ -873,7 +873,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Там в библиотеке есть компьютер, напиши презентацию прямо на нем.",
|
||||
"text": "Там в библиотеке есть компьютер, напиши эссе прямо на нем.",
|
||||
"next": "line_16"
|
||||
},
|
||||
{
|
||||
@ -887,7 +887,7 @@
|
||||
{
|
||||
"id": "line_17",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "До завтра.",
|
||||
"next": "line_18"
|
||||
@ -903,7 +903,7 @@
|
||||
{
|
||||
"id": "line_19",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "В твоих интересах успеть. Завтра покажешь мне, что у тебя получилось.",
|
||||
"next": "line_20"
|
||||
@ -911,7 +911,7 @@
|
||||
{
|
||||
"id": "line_20",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Не сдашь модуль - пойдешь на второй год. В другой раз не будешь трогать вещи в учительской без разрешения.",
|
||||
"next": "line_6"
|
||||
@ -974,7 +974,7 @@
|
||||
{
|
||||
"id": "line_1",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Бекзат, зачетка Бегимай у тебя. Отправляйся в теневой мир и покажи зачетку призраку Бегимай, чтобы она освободилась.",
|
||||
"objectiveComplete": "",
|
||||
@ -1046,7 +1046,7 @@
|
||||
{
|
||||
"id": "line_7",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Теперь иди в теневой мир и покажи зачетку Бегимай. Расскажи ей, что она сдала курсовую работу.",
|
||||
"next": "line_6"
|
||||
@ -1214,7 +1214,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Бегимай",
|
||||
"portrait": "resources/dialogue/portrait_ghost.png",
|
||||
"text": "Я уйду на покой только когда увиду оценку по курсовой в своей зачетке.",
|
||||
"text": "Я уйду на покой только когда увижу оценку по курсовой в своей зачетке.",
|
||||
"next": "line_7"
|
||||
},
|
||||
{
|
||||
@ -1222,7 +1222,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Бегимай",
|
||||
"portrait": "resources/dialogue/portrait_ghost.png",
|
||||
"text": "А до тех пор, я буду появлятся здесь каждую ночь.",
|
||||
"text": "А до тех пор, я буду появляться здесь каждую ночь.",
|
||||
"next": "line_8"
|
||||
},
|
||||
{
|
||||
@ -2228,7 +2228,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Айпери",
|
||||
"portrait": "resources/dialogue/portrait_aiperi.png",
|
||||
"text": "Тебе еще и не такое привидется. Езжай в общагу и выспись.",
|
||||
"text": "Тебе еще и не такое привидится. Езжай в общагу и выспись.",
|
||||
"next": "setflag_1"
|
||||
},
|
||||
{
|
||||
@ -2252,7 +2252,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Бекзат",
|
||||
"portrait": "resources/dialogue/portrait_hero_neutral.png",
|
||||
"text": "Аида Джаныбекова увидела меня в учительской, и заставила писать эссе по этой тупой книге.",
|
||||
"text": "Аида Джаныбековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.",
|
||||
"next": "line_9"
|
||||
},
|
||||
{
|
||||
@ -2508,7 +2508,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Айпери",
|
||||
"portrait": "resources/dialogue/portrait_phone.png",
|
||||
"text": "Бекзат, помнишь мы скидывались на торт для Аиды Джаныбековой? Я тогда еще приносила скатерть, тарелки и нож для торта. И я до сих пор не получила назад ничего.",
|
||||
"text": "Бекзат, помнишь мы скидывались на торт для Аиды Джаныбековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
|
||||
"next": "line_2",
|
||||
"chatBubble": "in"
|
||||
},
|
||||
@ -3058,7 +3058,7 @@
|
||||
{
|
||||
"id": "line_1",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Бекзат, ты подготовил эссе на тему \"Манас в изложении Жусупа Мамая\"?",
|
||||
"objectiveComplete": "",
|
||||
@ -3075,7 +3075,7 @@
|
||||
{
|
||||
"id": "line_4",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Найди книгу про Жусупа Мамая в библиотеке, а потом садись за компьютер и пиши!",
|
||||
"next": "line_3"
|
||||
@ -3123,7 +3123,7 @@
|
||||
{
|
||||
"id": "line_99",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Ну что Бекзат, эссе про Жусупа Мамая готово к сдаче?",
|
||||
"next": "line_2"
|
||||
@ -3133,7 +3133,7 @@
|
||||
"type": "Line",
|
||||
"speaker": "Бекзат",
|
||||
"portrait": "resources/dialogue/portrait_hero_neutral.png",
|
||||
"text": "Прощу прощения Аида Джаныбековна, я правда старался, но у меня возникли обстоятельства непреодолимой силы.",
|
||||
"text": "Прошу прощения Аида Джаныбековна, я правда старался, но у меня возникли обстоятельства непреодолимой силы.",
|
||||
"next": "line_3"
|
||||
},
|
||||
{
|
||||
@ -3171,7 +3171,7 @@
|
||||
{
|
||||
"id": "line_6",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Какое еще заклинание? У тебя эта записка с собой?",
|
||||
"next": "line_7"
|
||||
@ -3187,7 +3187,7 @@
|
||||
{
|
||||
"id": "line_8",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Надеюсь, ты не читал это заклинание?",
|
||||
"next": "line_9"
|
||||
@ -3211,7 +3211,7 @@
|
||||
{
|
||||
"id": "line_12",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Это заклинание переместило тебя в мир теней. ",
|
||||
"next": "line_13"
|
||||
@ -3219,7 +3219,7 @@
|
||||
{
|
||||
"id": "line_13",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Этот мир предназначен для фей и джиннов. Обычные люди туда попасть не могут. Но у тебя это как-то получилось.",
|
||||
"next": "line_14"
|
||||
@ -3235,7 +3235,7 @@
|
||||
{
|
||||
"id": "line_15",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Не забывай, я изучаю фольклор в целом и Манас в частности. Если бы ты не засыпал над книгами, ты бы тоже много интересного узнал про мир от наших предков.",
|
||||
"next": "line_16"
|
||||
@ -3243,7 +3243,7 @@
|
||||
{
|
||||
"id": "line_16",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я освобождаю тебя от задания по эссе. И взамен я дам тебе другое задание. Если хочешь сдать модуль, тебе придется выполнить его.",
|
||||
"next": "line_17"
|
||||
@ -3259,7 +3259,7 @@
|
||||
{
|
||||
"id": "line_18",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "В универе завелся призрак. Конечно, его не получится увидеть вживую.",
|
||||
"next": "line_19"
|
||||
@ -3267,7 +3267,7 @@
|
||||
{
|
||||
"id": "line_19",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Но иногда по вечерам я слышу плач из лекционного зала.",
|
||||
"next": "line_20"
|
||||
@ -3275,7 +3275,7 @@
|
||||
{
|
||||
"id": "line_20",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "А на утро бывает так, что все зачетки в шкафу раскиданы как попало.",
|
||||
"next": "line_21"
|
||||
@ -3283,7 +3283,7 @@
|
||||
{
|
||||
"id": "line_21",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Призрак обитает где-то в теневом мире. Попади туда и наладь с ним диалог. Узнай, кто он и что ему нужно.",
|
||||
"next": "line_22"
|
||||
@ -3291,7 +3291,7 @@
|
||||
{
|
||||
"id": "line_22",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Если ты успешно избавишься от призрака, я поставлю тебе максимальный балл за модуль.",
|
||||
"next": "setflag_4"
|
||||
@ -3310,7 +3310,7 @@
|
||||
{
|
||||
"id": "line_23",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "У тебя есть еще вопросы, Бекзат?",
|
||||
"next": "choice_1"
|
||||
@ -3359,7 +3359,7 @@
|
||||
{
|
||||
"id": "line_25",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Теневой мир это мир, в котором обитают феи, духи и джинны. Он существовал всегда.",
|
||||
"next": "line_26"
|
||||
@ -3367,7 +3367,7 @@
|
||||
{
|
||||
"id": "line_26",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Обычные люди, не воители, туда не попадают обычно. Кроме тебя, тебе это как-то удалось.",
|
||||
"next": "line_31"
|
||||
@ -3375,7 +3375,7 @@
|
||||
{
|
||||
"id": "line_31",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Спрашивай что хочешь, я попробую объяснить.",
|
||||
"next": "choice_2"
|
||||
@ -3430,7 +3430,7 @@
|
||||
{
|
||||
"id": "line_30",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я думаю, в мир теней проецируются только фундаментальные объекты, типа зданий, деревьев, рельефа.",
|
||||
"next": "line_27"
|
||||
@ -3438,7 +3438,7 @@
|
||||
{
|
||||
"id": "line_27",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Это происходит потому, что в теневом мире время течет сильно медленнее, чем в нашем мире.",
|
||||
"next": "line_32"
|
||||
@ -3446,7 +3446,7 @@
|
||||
{
|
||||
"id": "line_32",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Двери у нас слишком часто открываются и закрываются, поэтому в теневом мире они \"размываются\" или попадают в суперпозицию.",
|
||||
"next": "line_28"
|
||||
@ -3454,7 +3454,7 @@
|
||||
{
|
||||
"id": "line_28",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Поэтому в мире теней ты можешь спокойно пройти через дверной проем. Но сквозь стены ты пройти не сможешь, они более плотные.",
|
||||
"next": "line_49"
|
||||
@ -3474,7 +3474,7 @@
|
||||
{
|
||||
"id": "line_34",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Это их мир, и они там живут. Я думаю что увидев тебя, обитатели теневого мира тоже были шокированы.",
|
||||
"next": "line_35"
|
||||
@ -3482,7 +3482,7 @@
|
||||
{
|
||||
"id": "line_35",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Но кое в чем ты прав. Здесь в универе, призраков раньше не было. Всякая чертовщина начала происходить совсем недавно.",
|
||||
"next": "line_36"
|
||||
@ -3490,7 +3490,7 @@
|
||||
{
|
||||
"id": "line_36",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Ты должен узнать, откуда здесь появился призрак, и заставить его исчезнуть.",
|
||||
"next": "line_37"
|
||||
@ -3498,7 +3498,7 @@
|
||||
{
|
||||
"id": "line_37",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Помимо призрака, есть еще и тени. Но ты не беспокойся, это всего лишь проекции негативных мыслей.",
|
||||
"next": "line_38"
|
||||
@ -3506,7 +3506,7 @@
|
||||
{
|
||||
"id": "line_38",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Просто избавляйся от теней, когда встретишь их. А когда ты изгонишь призрака, тени исчезнут сами. ",
|
||||
"next": "line_49"
|
||||
@ -3522,7 +3522,7 @@
|
||||
{
|
||||
"id": "line_40",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Время в мире теней течет значительно медленнее, чем в нашем.",
|
||||
"next": "line_41"
|
||||
@ -3530,7 +3530,7 @@
|
||||
{
|
||||
"id": "line_41",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "И похоже, что течение времени там нелинейное.",
|
||||
"next": "line_42"
|
||||
@ -3538,7 +3538,7 @@
|
||||
{
|
||||
"id": "line_42",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Это объясняет, почему в теневом мире есть деревья и строения, но отсутствуют двери, автомобили, и прочее.",
|
||||
"next": "line_43"
|
||||
@ -3546,7 +3546,7 @@
|
||||
{
|
||||
"id": "line_43",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Объекты в нашем мире должны долго находится на одном месте, прежде чем их проекция появится в мире теней.",
|
||||
"next": "line_49"
|
||||
@ -3562,7 +3562,7 @@
|
||||
{
|
||||
"id": "line_45",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Если ты потеряешь сознание в теневом мире, то ты принудительно вернешься в наш мир.",
|
||||
"next": "line_46"
|
||||
@ -3570,7 +3570,7 @@
|
||||
{
|
||||
"id": "line_46",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Также избегай света луны, луна тоже может вернуть тебя в мир людей. Лучше вообще не выходи из помещения. ",
|
||||
"next": "line_47"
|
||||
@ -3578,7 +3578,7 @@
|
||||
{
|
||||
"id": "line_47",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Если ты задержишься, и в человеческом мире настанет утро, тебя тоже выбросит из теневого мира.",
|
||||
"next": "line_48"
|
||||
@ -3586,7 +3586,7 @@
|
||||
{
|
||||
"id": "line_48",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Но ты не беспокойся. Даже если тебя выбросит из теневого мира, ты всегда сможешь повторить свою попытку на следующую ночь.",
|
||||
"next": "line_49"
|
||||
@ -3594,7 +3594,7 @@
|
||||
{
|
||||
"id": "line_49",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "У тебя есть еще вопросы про теневой мир?",
|
||||
"next": "choice_2"
|
||||
@ -3610,7 +3610,7 @@
|
||||
{
|
||||
"id": "line_51",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Ты хочешь что-нибудь еще обсудить?",
|
||||
"next": "choice_1"
|
||||
@ -3626,7 +3626,7 @@
|
||||
{
|
||||
"id": "line_53",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Нет, это не я. И на самом деле, я немного обеспокоена этим.",
|
||||
"next": "line_56"
|
||||
@ -3634,7 +3634,7 @@
|
||||
{
|
||||
"id": "line_56",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Когда я узнала, что в университете начались паранормальные явления, я сразу поняла что это призрак в теневом мире.",
|
||||
"next": "line_54"
|
||||
@ -3642,7 +3642,7 @@
|
||||
{
|
||||
"id": "line_54",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я знаю заклинание, которое тебе дали. У этого заклинания есть еще одно условие - оно сработает только на людей, которые чисты сердцем.",
|
||||
"next": "line_57"
|
||||
@ -3650,7 +3650,7 @@
|
||||
{
|
||||
"id": "line_57",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я взяла серебряный нож Айпери и пробовала прочитать заклинание, и у меня не получилось.",
|
||||
"next": "line_55"
|
||||
@ -3658,7 +3658,7 @@
|
||||
{
|
||||
"id": "line_55",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Тогда я предположила, что Айпери уж точно сможет прочитать заклинание и попасть в теневой мир.",
|
||||
"next": "line_58"
|
||||
@ -3666,7 +3666,7 @@
|
||||
{
|
||||
"id": "line_58",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Как раз поэтому я хотела вернуть нож ей лично. И заодно отправить ее в теневой мир.",
|
||||
"next": "line_59"
|
||||
@ -3674,7 +3674,7 @@
|
||||
{
|
||||
"id": "line_59",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Но ты смешал все мои планы. Сначала ты забрал нож, а потом кто-то подсунул тебе записку.",
|
||||
"next": "line_60"
|
||||
@ -3682,7 +3682,7 @@
|
||||
{
|
||||
"id": "line_60",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Это очень странно. Как будто кто-то играет против меня, и кто-то специально ввел тебя в игру заместо Айпери.",
|
||||
"next": "line_61"
|
||||
@ -3706,7 +3706,7 @@
|
||||
{
|
||||
"id": "line_63",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Узнай что нужно призраку, и постарайся уговорить его покинуть университет.",
|
||||
"next": "line_64"
|
||||
@ -3714,7 +3714,7 @@
|
||||
{
|
||||
"id": "line_64",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Или ты хочешь вместо этого писать эссе про Жусупа Мамая?",
|
||||
"next": "line_65"
|
||||
@ -3780,7 +3780,7 @@
|
||||
{
|
||||
"id": "line_72",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Я не знаю ни одной студентки по имени Бегимай.",
|
||||
"next": "line_73"
|
||||
@ -3788,7 +3788,7 @@
|
||||
{
|
||||
"id": "line_73",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Но с другой стороны, я здесь преподаю только с этого года. Возможно, Бегимай училась у предыдущего преподавателя?",
|
||||
"next": "line_74"
|
||||
@ -3804,7 +3804,7 @@
|
||||
{
|
||||
"id": "line_75",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Опроси студентов и узнай подробнее, кто эта призрак Бегимай и откуда она взялась.",
|
||||
"next": "line_76"
|
||||
@ -3812,7 +3812,7 @@
|
||||
{
|
||||
"id": "line_76",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Потом возвращайся ко мне, и мы обсудим, что делать дальше.",
|
||||
"next": "setflag_1"
|
||||
@ -3839,7 +3839,7 @@
|
||||
{
|
||||
"id": "line_78",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "У тебя есть еще вопросы?",
|
||||
"next": "choice_4"
|
||||
@ -3884,7 +3884,7 @@
|
||||
{
|
||||
"id": "line_67",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Переходи в теневой мир, а затем иди в лекционный зал.",
|
||||
"next": "line_68"
|
||||
@ -3892,7 +3892,7 @@
|
||||
{
|
||||
"id": "line_68",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Постарайся поговорить с призраком, и узнать, что ему нужно.",
|
||||
"next": "line_69"
|
||||
@ -3900,7 +3900,7 @@
|
||||
{
|
||||
"id": "line_69",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Потом возвращайся ко мне, и мы обсудим, как можно его изгнать.",
|
||||
"next": "line_77"
|
||||
@ -3963,7 +3963,7 @@
|
||||
{
|
||||
"id": "line_95",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Найди курсовую работу Бегимай и принеси мне ее. Я проверю курсовую вместо предыдущего преподавателя и выставлю ей оценку.",
|
||||
"next": "line_77"
|
||||
@ -3984,7 +3984,7 @@
|
||||
{
|
||||
"id": "line_97",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Иди к шкафу в учительской. Среди зачетных книжек найти зачетку Бегимай и принеси мне. Я поставлю ей оценку за курсовую работу.",
|
||||
"next": "line_77"
|
||||
@ -3992,7 +3992,7 @@
|
||||
{
|
||||
"id": "line_96",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Ты уже нашел курсовую работу Бегимай?",
|
||||
"next": "line_92"
|
||||
@ -4040,7 +4040,7 @@
|
||||
{
|
||||
"id": "line_87",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Печальная история.",
|
||||
"next": "line_88"
|
||||
@ -4048,7 +4048,7 @@
|
||||
{
|
||||
"id": "line_88",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Судя по всему, чтобы освободить ее, нужно найти ее курсовую работу и поставить ей оценку.",
|
||||
"next": "line_89"
|
||||
@ -4056,7 +4056,7 @@
|
||||
{
|
||||
"id": "line_89",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Ты нашел ее курсовую работу?",
|
||||
"next": "condition_4"
|
||||
@ -4085,7 +4085,7 @@
|
||||
{
|
||||
"id": "line_93",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Да, вижу. Курсовая работа написана очень хорошо. Пожалуй, я могу выставить за нее максимальный балл.",
|
||||
"next": "line_98"
|
||||
@ -4093,7 +4093,7 @@
|
||||
{
|
||||
"id": "line_98",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Теперь поищи в шкафу зачетку Бегимай, и принеси мне, я выставлю ей оценку.",
|
||||
"next": "setflag_3"
|
||||
@ -4120,7 +4120,7 @@
|
||||
{
|
||||
"id": "line_91",
|
||||
"type": "Line",
|
||||
"speaker": "Аида Джаныбекова",
|
||||
"speaker": "Аида Джаныбековна",
|
||||
"portrait": "resources/dialogue/portrait_teacher.png",
|
||||
"text": "Найди и принеси мне ее. Я проверю работу за предыдущего преподавателя и выставлю ей оценку.",
|
||||
"next": "setflag_2"
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include "Game.h"
|
||||
#include "AnimatedModel.h"
|
||||
#include "Localization.h"
|
||||
#include "utils/Utils.h"
|
||||
#include <fstream>
|
||||
#include "items/ItemRegistry.h"
|
||||
@ -150,6 +151,11 @@ namespace ZL
|
||||
{
|
||||
std::cout << "Load resurces step 12x1" << std::endl;
|
||||
|
||||
// Must happen before createLocations() below: some location scripts
|
||||
// (e.g. resources/start_dorm.lua) start a dialogue as soon as they're
|
||||
// loaded, so the language needs to be known before that first load.
|
||||
ZL::loadLanguageSetting();
|
||||
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE);
|
||||
|
||||
38
src/Localization.cpp
Normal file
38
src/Localization.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "Localization.h"
|
||||
|
||||
#include "external/nlohmann/json.hpp"
|
||||
#include "utils/Utils.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
Language g_currentLanguage = Language::Russian;
|
||||
|
||||
std::string languageToCode(Language lang) {
|
||||
switch (lang) {
|
||||
case Language::English: return "en";
|
||||
case Language::Russian:
|
||||
default: return "ru";
|
||||
}
|
||||
}
|
||||
|
||||
Language languageFromCode(const std::string& code) {
|
||||
if (code == "en") return Language::English;
|
||||
return Language::Russian;
|
||||
}
|
||||
|
||||
void loadLanguageSetting() {
|
||||
const std::string content = readTextFile("settings.json");
|
||||
if (content.empty()) return;
|
||||
|
||||
try {
|
||||
const nlohmann::json root = nlohmann::json::parse(content);
|
||||
if (root.contains("language")) {
|
||||
g_currentLanguage = languageFromCode(root["language"].get<std::string>());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
23
src/Localization.h
Normal file
23
src/Localization.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
enum class Language {
|
||||
Russian,
|
||||
English
|
||||
};
|
||||
|
||||
// Currently selected language for in-game text. Defaults to Russian.
|
||||
extern Language g_currentLanguage;
|
||||
|
||||
std::string languageToCode(Language lang);
|
||||
Language languageFromCode(const std::string& code);
|
||||
|
||||
// Reads the "language" field from settings.json (if present) and updates
|
||||
// g_currentLanguage. Safe to call even if settings.json doesn't exist yet.
|
||||
// Must run before any location/dialogue content is loaded, since some
|
||||
// location scripts start dialogues eagerly as soon as they're loaded.
|
||||
void loadLanguageSetting();
|
||||
|
||||
} // namespace ZL
|
||||
@ -164,12 +164,13 @@ namespace ZL
|
||||
std::cout << "Shadow map initialized" << std::endl;
|
||||
//#endif
|
||||
|
||||
|
||||
setupNavigation(params.navigationJsonPaths);
|
||||
|
||||
dialogueSystem.init(renderer, CONST_ZIP_FILE);
|
||||
dialogueSystem.loadDatabase(params.dialoguesJsonPath);
|
||||
dialogueSystem.loadCutsceneDatabase("resources/dialogue/cutscenes008.json");
|
||||
dialogueSystem.loadTranslations("resources/dialogue/translate_dialogues.json");
|
||||
dialogueSystem.loadCutsceneTranslations("resources/dialogue/translate_cutscenes.json");
|
||||
dialogueSystem.setQuestJournal(journal);
|
||||
|
||||
npcNameText = std::make_unique<TextRenderer>();
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include "MenuManager.h"
|
||||
#include "Game.h"
|
||||
#include "Localization.h"
|
||||
#include "render/TextRenderer.h"
|
||||
#include "utils/Utils.h"
|
||||
#include <iostream>
|
||||
@ -412,10 +413,10 @@ namespace ZL {
|
||||
});
|
||||
|
||||
uiManager.setTextButtonCallback("languageRussianButton", [this](const std::string&) {
|
||||
|
||||
ZL::g_currentLanguage = ZL::Language::Russian;
|
||||
});
|
||||
uiManager.setTextButtonCallback("languageEnglishButton", [this](const std::string&) {
|
||||
|
||||
ZL::g_currentLanguage = ZL::Language::English;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1821,6 +1822,7 @@ namespace ZL {
|
||||
root["musicEnabled"] = audioPlayer_.isMusicEnabled();
|
||||
root["soundEnabled"] = audioPlayer_.isSoundEnabled();
|
||||
root["fullscreen"] = Environment::isFullscreen;
|
||||
root["language"] = ZL::languageToCode(ZL::g_currentLanguage);
|
||||
|
||||
std::ofstream file("settings.json");
|
||||
if (file.is_open()) {
|
||||
@ -1835,6 +1837,11 @@ namespace ZL {
|
||||
// directly in the right fullscreen state (see WIN32 main() in main.cpp) to avoid
|
||||
// a windowed -> fullscreen flash. The block below just keeps Environment::isFullscreen
|
||||
// in sync in case this is ever called from a path that didn't pre-apply it.
|
||||
// Language is loaded separately, even earlier (see ZL::loadLanguageSetting()
|
||||
// callers), because some location scripts start a dialogue as soon as they're
|
||||
// loaded, before this function ever runs.
|
||||
ZL::loadLanguageSetting();
|
||||
|
||||
const std::string content = ZL::readTextFile("settings.json");
|
||||
if (content.empty()) return;
|
||||
try {
|
||||
|
||||
@ -10,6 +10,14 @@ void CutsceneRuntime::setDatabase(const CutsceneDatabase* value) {
|
||||
database = value;
|
||||
}
|
||||
|
||||
void CutsceneRuntime::setTranslationDatabase(const ZL::Dialogue::TranslationDatabase* value) {
|
||||
translations = value;
|
||||
}
|
||||
|
||||
std::string CutsceneRuntime::tr(const std::string& s) const {
|
||||
return translations ? translations->translate(s, ZL::g_currentLanguage) : s;
|
||||
}
|
||||
|
||||
void CutsceneRuntime::setOnFinished(std::function<void(const std::string&)> cb) {
|
||||
onFinished = std::move(cb);
|
||||
}
|
||||
@ -329,9 +337,9 @@ void CutsceneRuntime::refreshPresentation() {
|
||||
}
|
||||
|
||||
const CutsceneLine& line = activeCutscene->lines[currentCutsceneLine];
|
||||
presentation.speaker = line.speaker;
|
||||
presentation.fullText = line.text;
|
||||
presentation.visibleText = line.text;
|
||||
presentation.speaker = tr(line.speaker);
|
||||
presentation.fullText = tr(line.text);
|
||||
presentation.visibleText = presentation.fullText;
|
||||
presentation.selectedChoice = 0;
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#include "cutscene/CutsceneDatabase.h"
|
||||
#include "dialogue/DialogueTypes.h"
|
||||
#include "dialogue/TranslationDatabase.h"
|
||||
#include "Localization.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
@ -10,6 +12,7 @@ namespace ZL::Cutscene {
|
||||
class CutsceneRuntime {
|
||||
public:
|
||||
void setDatabase(const CutsceneDatabase* value);
|
||||
void setTranslationDatabase(const ZL::Dialogue::TranslationDatabase* value);
|
||||
|
||||
void setOnFinished(std::function<void(const std::string&)> cb);
|
||||
void setOnLineStarted(std::function<void(const std::string&)> cb);
|
||||
@ -27,6 +30,7 @@ public:
|
||||
|
||||
private:
|
||||
const CutsceneDatabase* database = nullptr;
|
||||
const ZL::Dialogue::TranslationDatabase* translations = nullptr;
|
||||
|
||||
const StaticCutsceneDefinition* activeCutscene = nullptr;
|
||||
std::string activeCutsceneId;
|
||||
@ -50,6 +54,7 @@ private:
|
||||
void syncLineToElapsedTime();
|
||||
void advanceLine();
|
||||
void refreshPresentation();
|
||||
std::string tr(const std::string& s) const;
|
||||
|
||||
std::vector<PresentedCutsceneImage> evaluateImages() const;
|
||||
|
||||
|
||||
@ -15,6 +15,14 @@ void DialogueRuntime::setDatabase(const DialogueDatabase* value) {
|
||||
database = value;
|
||||
}
|
||||
|
||||
void DialogueRuntime::setTranslationDatabase(const TranslationDatabase* value) {
|
||||
translations = value;
|
||||
}
|
||||
|
||||
std::string DialogueRuntime::tr(const std::string& s) const {
|
||||
return translations ? translations->translate(s, g_currentLanguage) : s;
|
||||
}
|
||||
|
||||
bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
|
||||
if (!database) {
|
||||
std::cerr << "[dialogue] No database assigned to runtime\n";
|
||||
@ -295,14 +303,14 @@ void DialogueRuntime::presentLine(const Node& node) {
|
||||
mode = Mode::PresentingLine;
|
||||
revealCharacters = 0.0f;
|
||||
presentation.mode = PresentationMode::Dialogue;
|
||||
presentation.speaker = node.speaker;
|
||||
presentation.fullText = node.text;
|
||||
presentation.speaker = tr(node.speaker);
|
||||
presentation.fullText = tr(node.text);
|
||||
presentation.visibleText.clear();
|
||||
presentation.portraitPath = node.portrait;
|
||||
|
||||
presentation.choices.clear();
|
||||
presentation.selectedChoice = -1;
|
||||
presentation.revealCompleted = node.text.empty();
|
||||
presentation.revealCompleted = presentation.fullText.empty();
|
||||
presentation.showCutsceneSubtitle = false;
|
||||
presentation.cutsceneSkippable = false;
|
||||
|
||||
@ -311,8 +319,8 @@ void DialogueRuntime::presentLine(const Node& node) {
|
||||
presentation.cutsceneBlackAlpha = 0.0f;
|
||||
|
||||
if (presentation.revealCompleted) {
|
||||
presentation.visibleText = node.text;
|
||||
revealCharacters = static_cast<float>(node.text.size());
|
||||
presentation.visibleText = presentation.fullText;
|
||||
revealCharacters = static_cast<float>(presentation.fullText.size());
|
||||
}
|
||||
|
||||
applyQuestActions(node.questUnlock, node.questComplete, node.questFail,
|
||||
@ -321,7 +329,7 @@ void DialogueRuntime::presentLine(const Node& node) {
|
||||
onDialogueLineStarted(node.luaCallback);
|
||||
}
|
||||
if (!node.chatBubble.empty() && onChatBubbleReady) {
|
||||
onChatBubbleReady(node.text, node.chatBubble == "in");
|
||||
onChatBubbleReady(presentation.fullText, node.chatBubble == "in");
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,7 +341,7 @@ void DialogueRuntime::presentChoices(const Node& node) {
|
||||
if (!choice.id.empty() && consumedChoices.count(choice.id) > 0) continue;
|
||||
if (!evaluateConditions(choice.conditions)) continue;
|
||||
visibleChoices.push_back(choice);
|
||||
presentation.choices.push_back({ choice.id, choice.text, choice.kind });
|
||||
presentation.choices.push_back({ choice.id, tr(choice.text), choice.kind });
|
||||
}
|
||||
|
||||
if (visibleChoices.empty()) {
|
||||
@ -349,9 +357,9 @@ void DialogueRuntime::presentChoices(const Node& node) {
|
||||
mode = Mode::WaitingForChoice;
|
||||
selectedChoice = -1;
|
||||
presentation.mode = PresentationMode::Choice;
|
||||
presentation.speaker = node.speaker;
|
||||
presentation.fullText = node.text;
|
||||
presentation.visibleText = node.text;
|
||||
presentation.speaker = tr(node.speaker);
|
||||
presentation.fullText = tr(node.text);
|
||||
presentation.visibleText = presentation.fullText;
|
||||
presentation.portraitPath = node.portrait;
|
||||
|
||||
presentation.selectedChoice = -1;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "dialogue/DialogueDatabase.h"
|
||||
#include "dialogue/TranslationDatabase.h"
|
||||
#include "quest/QuestJournal.h"
|
||||
#include "external/nlohmann/json.hpp"
|
||||
#include "ISaveable.h"
|
||||
@ -15,6 +16,7 @@ namespace ZL::Dialogue {
|
||||
class DialogueRuntime : public ZL::ISaveable {
|
||||
public:
|
||||
void setDatabase(const DialogueDatabase* value);
|
||||
void setTranslationDatabase(const TranslationDatabase* value);
|
||||
|
||||
bool startDialogue(const std::string& dialogueId);
|
||||
void stop();
|
||||
@ -62,6 +64,7 @@ private:
|
||||
std::function<void(const std::string&, bool)> onChatBubbleReady;
|
||||
|
||||
const DialogueDatabase* database = nullptr;
|
||||
const TranslationDatabase* translations = nullptr;
|
||||
Quest::QuestJournal* questJournal = nullptr;
|
||||
const DialogueDefinition* activeDialogue = nullptr;
|
||||
|
||||
@ -86,6 +89,7 @@ private:
|
||||
bool enterNode(const std::string& nodeId);
|
||||
void presentLine(const Node& node);
|
||||
void presentChoices(const Node& node);
|
||||
std::string tr(const std::string& s) const;
|
||||
};
|
||||
|
||||
} // namespace ZL::Dialogue
|
||||
|
||||
@ -4,7 +4,9 @@ namespace ZL::Dialogue {
|
||||
|
||||
bool DialogueSystem::init(Renderer& renderer, const std::string& zipFile) {
|
||||
dialogueRuntime.setDatabase(&database);
|
||||
dialogueRuntime.setTranslationDatabase(&translationDatabase);
|
||||
cutsceneRuntime.setDatabase(&cutsceneDatabase);
|
||||
cutsceneRuntime.setTranslationDatabase(&cutsceneTranslationDatabase);
|
||||
|
||||
// When dialogue hits a CutsceneStart node, hand off to cutsceneRuntime.
|
||||
dialogueRuntime.setOnCutsceneStartNeeded([this](const std::string& cutsceneId, const std::string& nextNodeId) {
|
||||
@ -30,6 +32,14 @@ bool DialogueSystem::loadCutsceneDatabase(const std::string& path) {
|
||||
return cutsceneDatabase.loadFromFile(path);
|
||||
}
|
||||
|
||||
bool DialogueSystem::loadTranslations(const std::string& path) {
|
||||
return translationDatabase.loadFromFile(path);
|
||||
}
|
||||
|
||||
bool DialogueSystem::loadCutsceneTranslations(const std::string& path) {
|
||||
return cutsceneTranslationDatabase.loadFromFile(path);
|
||||
}
|
||||
|
||||
void DialogueSystem::update(int deltaMs) {
|
||||
dialogueRuntime.update(deltaMs);
|
||||
cutsceneRuntime.update(deltaMs);
|
||||
|
||||
@ -20,6 +20,8 @@ public:
|
||||
|
||||
bool loadDatabase(const std::string& path);
|
||||
bool loadCutsceneDatabase(const std::string& path);
|
||||
bool loadTranslations(const std::string& path);
|
||||
bool loadCutsceneTranslations(const std::string& path);
|
||||
|
||||
void update(int deltaMs);
|
||||
void draw(Renderer& renderer);
|
||||
@ -64,10 +66,12 @@ public:
|
||||
|
||||
private:
|
||||
DialogueDatabase database;
|
||||
TranslationDatabase translationDatabase;
|
||||
DialogueRuntime dialogueRuntime;
|
||||
DialogueOverlay dialogueOverlay;
|
||||
|
||||
ZL::Cutscene::CutsceneDatabase cutsceneDatabase;
|
||||
TranslationDatabase cutsceneTranslationDatabase;
|
||||
ZL::Cutscene::CutsceneRuntime cutsceneRuntime;
|
||||
ZL::Cutscene::CutsceneOverlay cutsceneOverlay;
|
||||
|
||||
|
||||
80
src/dialogue/TranslationDatabase.cpp
Normal file
80
src/dialogue/TranslationDatabase.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include "dialogue/TranslationDatabase.h"
|
||||
|
||||
#include "utils/Utils.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace ZL
|
||||
{
|
||||
extern const char* CONST_ZIP_FILE;
|
||||
}
|
||||
|
||||
namespace ZL::Dialogue {
|
||||
|
||||
bool TranslationDatabase::loadFromFile(const std::string& path) {
|
||||
entries.clear();
|
||||
|
||||
std::string raw;
|
||||
try {
|
||||
if (strlen(CONST_ZIP_FILE) == 0) {
|
||||
raw = readTextFile(path);
|
||||
}
|
||||
else {
|
||||
auto buf = readFileFromZIP(path, CONST_ZIP_FILE);
|
||||
if (buf.empty()) {
|
||||
std::cerr << "[translation] Failed to read " << path << " from zip\n";
|
||||
return false;
|
||||
}
|
||||
raw.assign(buf.begin(), buf.end());
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "[translation] Failed to open " << path << ": " << e.what() << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
json root;
|
||||
try {
|
||||
root = json::parse(raw);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "[translation] JSON parse error in " << path << ": " << e.what() << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (root.contains("dialogue_translations") && root["dialogue_translations"].is_array()) {
|
||||
for (const auto& item : root["dialogue_translations"]) {
|
||||
const std::string key = item.value("key", "");
|
||||
if (key.empty()) continue;
|
||||
|
||||
std::unordered_map<std::string, std::string> langMap;
|
||||
for (auto it = item.begin(); it != item.end(); ++it) {
|
||||
if (it.key() == "key") continue;
|
||||
if (it.value().is_string()) {
|
||||
langMap[it.key()] = it.value().get<std::string>();
|
||||
}
|
||||
}
|
||||
entries[key] = std::move(langMap);
|
||||
}
|
||||
}
|
||||
|
||||
return !entries.empty();
|
||||
}
|
||||
|
||||
std::string TranslationDatabase::translate(const std::string& key, Language lang) const {
|
||||
if (key.empty()) return key;
|
||||
|
||||
auto it = entries.find(key);
|
||||
if (it == entries.end()) return key;
|
||||
|
||||
const auto& langMap = it->second;
|
||||
|
||||
auto langIt = langMap.find(languageToCode(lang));
|
||||
if (langIt != langMap.end() && !langIt->second.empty()) return langIt->second;
|
||||
|
||||
auto ruIt = langMap.find("ru");
|
||||
if (ruIt != langMap.end() && !ruIt->second.empty()) return ruIt->second;
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
} // namespace ZL::Dialogue
|
||||
26
src/dialogue/TranslationDatabase.h
Normal file
26
src/dialogue/TranslationDatabase.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "Localization.h"
|
||||
#include "external/nlohmann/json.hpp"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ZL::Dialogue {
|
||||
|
||||
// Loads a shared translation file mapping dialogue "key" strings (the literal
|
||||
// speaker/text/choice text found in dialogue config files) to per-language text.
|
||||
class TranslationDatabase {
|
||||
public:
|
||||
using json = nlohmann::json;
|
||||
|
||||
bool loadFromFile(const std::string& path);
|
||||
|
||||
// Returns the translated text for `key` in `lang`, falling back to Russian,
|
||||
// then to `key` itself if no entry or translation exists.
|
||||
std::string translate(const std::string& key, Language lang) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> entries;
|
||||
};
|
||||
|
||||
} // namespace ZL::Dialogue
|
||||
54
sync_cutscene_translations.py
Normal file
54
sync_cutscene_translations.py
Normal file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
sync_cutscene_translations.py - Sync a cutscene config's strings into a translation file.
|
||||
|
||||
Reads <cutscene_config.json>, collects every non-empty "speaker" and "text" string
|
||||
from its lines, and appends any string not already present as a "key" in
|
||||
<translations.json> as a new entry with an empty "en" slot ready for a translator
|
||||
to fill in. New entries are always appended at the end of the list so missing
|
||||
translations are easy to locate.
|
||||
|
||||
Cutscene translations are intentionally kept in their own file/database, separate
|
||||
from dialogue translations - point <translations.json> at a dedicated file such as
|
||||
resources/dialogue/cutscene_translations.json.
|
||||
|
||||
Usage:
|
||||
python sync_cutscene_translations.py <cutscene_config.json> <translations.json>
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
from translation_sync_common import sync_translations
|
||||
|
||||
|
||||
def collect_strings(cutscene_config: dict) -> list[str]:
|
||||
seen = set()
|
||||
ordered = []
|
||||
|
||||
def add(value):
|
||||
if value and value not in seen:
|
||||
seen.add(value)
|
||||
ordered.append(value)
|
||||
|
||||
for cutscene in cutscene_config.get("cutscenes", []):
|
||||
for line in cutscene.get("lines", []):
|
||||
add(line.get("speaker", ""))
|
||||
add(line.get("text", ""))
|
||||
|
||||
return ordered
|
||||
|
||||
|
||||
def sync(cutscene_path: str, translations_path: str) -> None:
|
||||
with open(cutscene_path, 'r', encoding='utf-8') as f:
|
||||
cutscene_config = json.load(f)
|
||||
|
||||
sync_translations(collect_strings(cutscene_config), translations_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
print(f"Usage: {sys.argv[0]} <cutscene_config.json> <translations.json>")
|
||||
sys.exit(1)
|
||||
|
||||
sync(sys.argv[1], sys.argv[2])
|
||||
52
sync_dialogue_translations.py
Normal file
52
sync_dialogue_translations.py
Normal file
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
sync_dialogue_translations.py - Sync a dialogue config's strings into a translation file.
|
||||
|
||||
Reads <dialogue_config.json>, collects every non-empty "speaker" and "text" string
|
||||
from its nodes (and "text" from any node's "choices"), and appends any string not
|
||||
already present as a "key" in <translations.json> as a new entry with an empty
|
||||
"en" slot ready for a translator to fill in. New entries are always appended at
|
||||
the end of the list so missing translations are easy to locate.
|
||||
|
||||
Usage:
|
||||
python sync_dialogue_translations.py <dialogue_config.json> <translations.json>
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
from translation_sync_common import sync_translations
|
||||
|
||||
|
||||
def collect_strings(dialogue_config: dict) -> list[str]:
|
||||
seen = set()
|
||||
ordered = []
|
||||
|
||||
def add(value):
|
||||
if value and value not in seen:
|
||||
seen.add(value)
|
||||
ordered.append(value)
|
||||
|
||||
for dialogue in dialogue_config.get("dialogues", []):
|
||||
for node in dialogue.get("nodes", []):
|
||||
add(node.get("speaker", ""))
|
||||
add(node.get("text", ""))
|
||||
for choice in node.get("choices", []):
|
||||
add(choice.get("text", ""))
|
||||
|
||||
return ordered
|
||||
|
||||
|
||||
def sync(dialogue_path: str, translations_path: str) -> None:
|
||||
with open(dialogue_path, 'r', encoding='utf-8') as f:
|
||||
dialogue_config = json.load(f)
|
||||
|
||||
sync_translations(collect_strings(dialogue_config), translations_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
print(f"Usage: {sys.argv[0]} <dialogue_config.json> <translations.json>")
|
||||
sys.exit(1)
|
||||
|
||||
sync(sys.argv[1], sys.argv[2])
|
||||
38
translation_sync_common.py
Normal file
38
translation_sync_common.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""
|
||||
translation_sync_common.py - Shared merge/write logic for the *_translations sync
|
||||
scripts (sync_dialogue_translations.py, sync_cutscene_translations.py).
|
||||
|
||||
Each sync script collects strings from its own JSON shape (dialogue nodes/choices,
|
||||
cutscene lines, ...) and hands the ordered list of unique strings to
|
||||
sync_translations(), which merges them into a translations JSON file shaped as
|
||||
{"dialogue_translations": [{"key":..., "ru":..., "en":...}, ...]}. New entries are
|
||||
always appended at the end so missing translations are easy to locate.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
def sync_translations(strings: list[str], translations_path: str) -> None:
|
||||
if os.path.isfile(translations_path):
|
||||
with open(translations_path, 'r', encoding='utf-8') as f:
|
||||
translations = json.load(f)
|
||||
else:
|
||||
translations = {}
|
||||
|
||||
entries = translations.setdefault("dialogue_translations", [])
|
||||
existing_keys = {entry.get("key") for entry in entries}
|
||||
|
||||
added = 0
|
||||
for text in strings:
|
||||
if text in existing_keys:
|
||||
continue
|
||||
entries.append({"key": text, "ru": text, "en": ""})
|
||||
existing_keys.add(text)
|
||||
added += 1
|
||||
|
||||
os.makedirs(os.path.dirname(os.path.abspath(translations_path)), exist_ok=True)
|
||||
with open(translations_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(translations, f, indent=4, ensure_ascii=False)
|
||||
|
||||
print(f"Saved: {translations_path} ({added} new key(s) added, {len(entries)} total)")
|
||||
Loading…
Reference in New Issue
Block a user