Dialog fixing

This commit is contained in:
Vladislav Khorev 2026-05-22 22:32:05 +03:00
parent 8c200974c2
commit 84c8a7a27a
13 changed files with 169 additions and 43 deletions

View File

@ -55,6 +55,7 @@
{
"id": "aiperi_dialog001",
"start": "line_1",
"uninterruptible": true,
"nodes": [
{
"id": "line_1",
@ -325,6 +326,7 @@
{
"id": "teacher_dialog002",
"start": "line_1",
"uninterruptible": true,
"nodes": [
{
"id": "line_1",
@ -475,6 +477,7 @@
{
"id": "teacher_dialog005",
"start": "line_1",
"uninterruptible": true,
"nodes": [
{
"id": "line_1",
@ -513,7 +516,7 @@
"type": "Line",
"speaker": "Аида Дженибековна",
"portrait": "resources/w/avatar_teacher.png",
"text": "Вот держи зачетку с оценкой, положи ее обратно в шкаф.",
"text": "Вот держи зачетку с оценкой.",
"next": "line_6"
},
{
@ -521,7 +524,7 @@
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Хорошо!",
"text": "Спасибо!",
"next": "end_1"
},
{
@ -610,6 +613,7 @@
},
{
"id": "dialog_with_ghost001",
"uninterruptible": true,
"start": "line_1",
"nodes": [
{
@ -678,6 +682,7 @@
},
{
"id": "end_1",
"luaCallback" : "on_first_ghost_dialog_over",
"type": "End"
}
]
@ -786,6 +791,7 @@
},
{
"id": "end_1",
"luaCallback" : "on_quest_over",
"type": "End"
}
]
@ -1061,10 +1067,9 @@
]
},
{
"id": "test_cutscene_02",
"background": "resources/black.png",
"backgroundWidth" : 1280,
"backgroundHeight" : 720,
"id": "computer_cutscene001",
"onFadeInCallback": "on_sleep_cutscene",
"background": "resources/test_cutscene001.png",
"durationMs": 5000,
"fadeOutMs": 500,
"fadeInMs": 500,
@ -1108,9 +1113,8 @@
},
{
"id": "darklands_exit001",
"background": "resources/black.png",
"backgroundWidth" : 1280,
"backgroundHeight" : 720,
"onFadeInCallback": "on_darklands_over",
"background": "resources/test_cutscene001.png",
"durationMs": 5000,
"fadeOutMs": 500,
"fadeInMs": 500,

View File

@ -25,6 +25,7 @@ function lection_hall_zone001_enter_callback()
--game_api.start_dialogue("")
--Start cutscene
if (lection_is_over == false) then
game_api.player_stop()
game_api.start_cutscene("test_cutscene_01")
end
end
@ -180,25 +181,38 @@ function on_npc_interact(npc_index)
local report_card_signed = game_api.getIntValue("report_card_signed")
if (report_card_signed==1) then
game_api.start_dialogue("dialog_with_ghost003")
ghost_gone = true
game_api.set_npc_enabled(2, false)
game_api.set_npc_enabled(3, false)
game_api.set_npc_enabled(4, false)
game_api.set_npc_enabled(5, false)
else
game_api.start_dialogue("dialog_with_ghost002")
end
else
game_api.start_dialogue("dialog_with_ghost001")
end
end
end
function on_quest_over()
ghost_gone = true
game_api.set_npc_enabled(2, false)
game_api.set_npc_enabled(3, false)
game_api.set_npc_enabled(4, false)
game_api.set_npc_enabled(5, false)
end
function on_first_ghost_dialog_over()
player_ghost_aware = true
end
end
function on_darklands_over()
game_api.set_player_hp(10)
game_api.resetPlayerAfterDeath()
end
function teacher_zone001_enter_callback()
game_api.start_dialogue("teacher_dialog002")
game_api.set_trigger_zone_enabled(2, false)
teacher_told_about_book = true
game_api.player_stop()
end
function on_library_door_click()
@ -406,7 +420,7 @@ print("on_computer_clicked--")
print("on_computer_clicked--3")
if (player_hold_book) then
print("on_computer_clicked--4")
game_api.start_cutscene("test_cutscene_02")
game_api.start_cutscene("computer_cutscene001")
else
print("on_computer_clicked--5")
game_api.start_dialogue("book_dialog002")
@ -422,25 +436,7 @@ print("on_computer_clicked--")
end
end
function on_teacher_arrived_intermediate()
game_api.rotate_object("Room_N_2_Leaf001", -90, 0.5, nil)
game_api.fade_object("Room_Cover_North_3_001", 0, 0.5, function()
game_api.deactivate_interactive_object("Room_Cover_North_3_001")
end)
game_api.switch_navigation(2)
game_api.npc_walk_to(0, 3.19574, 0, 6.45595, on_teacher_arrived)
teacher_arrived = true
end
game_api.set_cutscene_callback("test_cutscene_01", function()
print("Cutscene done!")
lection_is_over = true
game_api.set_npc_enabled(1, true)
game_api.npc_walk_to(0, 0.758123, 0, 6.50063, on_teacher_arrived_intermediate)
end)
game_api.set_cutscene_callback("test_cutscene_02", function()
function on_sleep_cutscene()
print("Cutscene 2 done!")
night_time = true
--game_api.set_trigger_zone_enabled(1, false)
@ -462,8 +458,26 @@ game_api.set_cutscene_callback("test_cutscene_02", function()
game_api.set_object_alpha("Room_Cover_North_1_001", 1)
game_api.set_object_alpha("Room_Cover_Main_Hall_001", 1)
game_api.activate_interactive_object("Note001")
end
function on_teacher_arrived_intermediate()
game_api.rotate_object("Room_N_2_Leaf001", -90, 0.5, nil)
game_api.fade_object("Room_Cover_North_3_001", 0, 0.5, function()
game_api.deactivate_interactive_object("Room_Cover_North_3_001")
end)
game_api.switch_navigation(2)
game_api.npc_walk_to(0, 3.19574, 0, 6.45595, on_teacher_arrived)
teacher_arrived = true
end
game_api.set_cutscene_callback("test_cutscene_01", function()
print("Cutscene done!")
lection_is_over = true
game_api.set_npc_enabled(1, true)
game_api.npc_walk_to(0, 0.758123, 0, 6.50063, on_teacher_arrived_intermediate)
end)
function on_note_pickup()
game_api.start_dialogue("note_dialog001")
@ -542,11 +556,9 @@ game_api.set_darklands_callbacks(
game_api.set_npc_enabled(5, false)
game_api.setIntValue("day", 1)
game_api.setIntValue("need_sleep", 1)
game_api.set_player_hp(10)
player_left_darklands = true
morning_did_open_door_index = 0
setDay1MorningSetup()
--game_api.switch_navigation(7)
local px = game_api.get_player_x()

View File

@ -104,6 +104,19 @@ void Character::stopInPlace()
onArrivedCallback = nullptr;
}
void Character::resetPlayerAfterDeath()
{
if (!isPlayer)
{
return;
}
currentState = AnimationState::STAND;
resetAnim = true;
showWeapon = false;
battle_state = 0;
attack = 0;
}
void Character::forceReplan()
{
if (!pathPlanner) {
@ -456,11 +469,6 @@ void Character::update(int64_t deltaMs) {
//onDeathAnimComplete = nullptr;
onDeathAnimComplete();
//TODO: need to add some delay here
currentState = AnimationState::STAND;
resetAnim = true;
showWeapon = false;
battle_state = 0;
attack = 0;
}
}
}

View File

@ -66,6 +66,8 @@ public:
// character and we don't want it to walk back to where it was.
void stopInPlace();
void resetPlayerAfterDeath();
// attackDirection is a world-space horizontal vector pointing from the
// attacker toward this character — i.e. the direction the hit pushes

View File

@ -195,7 +195,6 @@ namespace ZL
uniInteriorParams.npcsJsonPath = "resources/config2/npcs_uni_interior.json";
uniInteriorParams.dialoguesJsonPath = "resources/dialogue/uni_interior_dialogues.json";
uniInteriorParams.navigationJsonPaths = {
"resources/navigation/uni_interior0_all_locked.txt",
"resources/navigation/uni_interior0_hall.txt",

View File

@ -146,6 +146,25 @@ namespace ZL
scriptEngine.callCutsceneCompleteCallback(cutsceneId);
});
dialogueSystem.setOnDialogueLineStarted([this](const std::string& fn) {
try { scriptEngine.callActivateFunction(fn); }
catch (const std::exception& e) {
std::cerr << "[dialogue] line callback error: " << e.what() << "\n";
}
});
dialogueSystem.setOnCutsceneLineStarted([this](const std::string& fn) {
try { scriptEngine.callActivateFunction(fn); }
catch (const std::exception& e) {
std::cerr << "[cutscene] line callback error: " << e.what() << "\n";
}
});
dialogueSystem.setOnCutsceneFadeInComplete([this](const std::string& fn) {
try { scriptEngine.callActivateFunction(fn); }
catch (const std::exception& e) {
std::cerr << "[cutscene] fade-in callback error: " << e.what() << "\n";
}
});
}
void Location::loadTeleportZones(const std::string& jsonPath, const char* zipFile)

View File

@ -460,6 +460,11 @@ namespace ZL {
std::cerr << "[script] scale_object: object '" << name << "' not found\n";
});
api.set_function("resetPlayerAfterDeath",
[game]() {
if (game->player) game->player->resetPlayerAfterDeath();
});
// player_stop() — cancels the player's current walk target and stops them immediately.
api.set_function("player_stop",
[game]() {

View File

@ -104,6 +104,7 @@ Node DialogueDatabase::parseNode(const json& j) {
node.trueNext = j.value("trueNext", "");
node.falseNext = j.value("falseNext", "");
node.cutsceneId = j.value("cutsceneId", "");
node.luaCallback = j.value("luaCallback", "");
if (j.contains("conditions") && j["conditions"].is_array()) {
for (const auto& item : j["conditions"]) {
@ -129,6 +130,7 @@ DialogueDefinition DialogueDatabase::parseDialogue(const json& j) {
result.id = j.value("id", "");
result.displayName = j.value("displayName", result.id);
result.startNode = j.value("start", "");
result.uninterruptible = j.value("uninterruptible", false);
if (j.contains("nodes") && j["nodes"].is_array()) {
for (const auto& item : j["nodes"]) {
@ -151,6 +153,7 @@ CutsceneLine DialogueDatabase::parseCutsceneLine(const json& j) {
line.background = j.value("background", "");
line.backgroundWidth = j.value("backgroundWidth", 0);
line.backgroundHeight = j.value("backgroundHeight", 0);
line.luaCallback = j.value("luaCallback", "");
line.durationMs = j.value("durationMs", 0);
line.waitForConfirm = j.value("waitForConfirm", false);
return line;
@ -205,6 +208,7 @@ StaticCutsceneDefinition DialogueDatabase::parseCutscene(const json& j) {
cutscene.fadeInMs = j.value("fadeInMs", 0);
cutscene.endFadeOutMs = j.value("endFadeOutMs", 0);
cutscene.endFadeInMs = j.value("endFadeInMs", 0);
cutscene.onFadeInCallback = j.value("onFadeInCallback", "");
if (j.contains("cameraTrack") && j["cameraTrack"].is_array()) {
for (const auto& item : j["cameraTrack"]) {

View File

@ -16,6 +16,11 @@ bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
return false;
}
if (mode != Mode::Inactive && activeDialogue && activeDialogue->uninterruptible) {
std::cerr << "[dialogue] Blocked: '" << activeDialogue->id << "' is uninterruptible\n";
return false;
}
const DialogueDefinition* dialogue = database->findDialogue(dialogueId);
if (!dialogue) {
std::cerr << "[dialogue] Dialogue not found: " << dialogueId << "\n";
@ -35,6 +40,7 @@ bool DialogueRuntime::startDialogue(const std::string& dialogueId) {
cutsceneTotalDurationMs = 0;
cutsceneContentDurationMs = 0;
currentCutsceneBackground.clear();
fadeInCallbackFired = false;
presentation = {};
presentation.dialogueId = dialogue->id;
@ -53,6 +59,7 @@ bool DialogueRuntime::startStandaloneCutscene(const std::string& cutsceneId) {
}
activeDialogue = nullptr;
activeCutsceneId = cutsceneId;
fadeInCallbackFired = false;
presentation = {};
startCutscene(cutsceneId, "");
return true;
@ -62,6 +69,18 @@ void DialogueRuntime::setOnCutsceneFinished(std::function<void(const std::string
onCutsceneFinished = std::move(cb);
}
void DialogueRuntime::setOnDialogueLineStarted(std::function<void(const std::string&)> cb) {
onDialogueLineStarted = std::move(cb);
}
void DialogueRuntime::setOnCutsceneLineStarted(std::function<void(const std::string&)> cb) {
onCutsceneLineStarted = std::move(cb);
}
void DialogueRuntime::setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb) {
onCutsceneFadeInComplete = std::move(cb);
}
void DialogueRuntime::stop() {
activeDialogue = nullptr;
activeCutscene = nullptr;
@ -76,6 +95,7 @@ void DialogueRuntime::stop() {
cutsceneTotalDurationMs = 0;
cutsceneContentDurationMs = 0;
currentCutsceneBackground.clear();
fadeInCallbackFired = false;
mode = Mode::Inactive;
presentation = {};
}
@ -128,6 +148,18 @@ void DialogueRuntime::update(int deltaMs) {
return;
}
if (!fadeInCallbackFired && onCutsceneFadeInComplete && activeCutscene) {
const int fadeInCompleteMs = activeCutscene->fadeOutMs + activeCutscene->fadeInMs;
if (cutsceneElapsedMs >= fadeInCompleteMs && !activeCutscene->onFadeInCallback.empty()) {
fadeInCallbackFired = true;
onCutsceneFadeInComplete(activeCutscene->onFadeInCallback);
}
}
if (!activeCutscene || mode != Mode::PlayingCutscene) {
return;
}
const bool subtitlesFinished =
activeCutscene->lines.empty() ||
currentCutsceneLine >= static_cast<int>(activeCutscene->lines.size());
@ -395,6 +427,10 @@ void DialogueRuntime::presentLine(const Node& node) {
presentation.visibleText = node.text;
revealCharacters = static_cast<float>(node.text.size());
}
if (!node.luaCallback.empty() && onDialogueLineStarted) {
onDialogueLineStarted(node.luaCallback);
}
}
void DialogueRuntime::presentChoices(const Node& node) {
@ -464,6 +500,7 @@ void DialogueRuntime::startCutscene(const std::string& cutsceneId, const std::st
mode = Mode::PlayingCutscene;
cutsceneElapsedMs = 0;
cutsceneTimerMs = 0;
fadeInCallbackFired = false;
currentCutsceneLine = activeCutscene->lines.empty() ? -1 : 0;
int imageTrackDurationMs = 0;
for (size_t i = 0; i < activeCutscene->images.size(); ++i) {
@ -486,6 +523,11 @@ void DialogueRuntime::startCutscene(const std::string& cutsceneId, const std::st
cutsceneTotalDurationMs = cutsceneContentDurationMs + activeCutscene->endFadeOutMs + activeCutscene->endFadeInMs;
refreshCutscenePresentation();
if (!activeCutscene->lines.empty() && onCutsceneLineStarted) {
const std::string& cb = activeCutscene->lines[0].luaCallback;
if (!cb.empty()) onCutsceneLineStarted(cb);
}
std::cout << "[CUTSCENE] start id=" << cutsceneId
<< " lines=" << activeCutscene->lines.size()
<< " totalDuration=" << cutsceneTotalDurationMs
@ -572,6 +614,11 @@ void DialogueRuntime::advanceCutsceneLine() {
return;
}
if (onCutsceneLineStarted) {
const std::string& cb = activeCutscene->lines[currentCutsceneLine].luaCallback;
if (!cb.empty()) onCutsceneLineStarted(cb);
}
refreshCutscenePresentation();
}

View File

@ -19,6 +19,9 @@ public:
bool startDialogue(const std::string& dialogueId);
bool startStandaloneCutscene(const std::string& cutsceneId);
void setOnCutsceneFinished(std::function<void(const std::string&)> cb);
void setOnDialogueLineStarted(std::function<void(const std::string&)> cb);
void setOnCutsceneLineStarted(std::function<void(const std::string&)> cb);
void setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb);
void stop();
void update(int deltaMs);
@ -50,7 +53,11 @@ private:
};
std::function<void(const std::string&)> onCutsceneFinished;
std::function<void(const std::string&)> onDialogueLineStarted;
std::function<void(const std::string&)> onCutsceneLineStarted;
std::function<void(const std::string&)> onCutsceneFadeInComplete;
std::string activeCutsceneId;
bool fadeInCallbackFired = false;
const DialogueDatabase* database = nullptr;
const DialogueDefinition* activeDialogue = nullptr;

View File

@ -127,6 +127,18 @@ void DialogueSystem::setOnCutsceneFinished(std::function<void(const std::string&
runtime.setOnCutsceneFinished(std::move(cb));
}
void DialogueSystem::setOnDialogueLineStarted(std::function<void(const std::string&)> cb) {
runtime.setOnDialogueLineStarted(std::move(cb));
}
void DialogueSystem::setOnCutsceneLineStarted(std::function<void(const std::string&)> cb) {
runtime.setOnCutsceneLineStarted(std::move(cb));
}
void DialogueSystem::setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb) {
runtime.setOnCutsceneFadeInComplete(std::move(cb));
}
void DialogueSystem::stopDialogue() {
runtime.stop();
}

View File

@ -24,6 +24,9 @@ public:
bool startDialogue(const std::string& dialogueId);
bool startCutscene(const std::string& cutsceneId);
void setOnCutsceneFinished(std::function<void(const std::string&)> cb);
void setOnDialogueLineStarted(std::function<void(const std::string&)> cb);
void setOnCutsceneLineStarted(std::function<void(const std::string&)> cb);
void setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb);
void stopDialogue();
bool isActive() const { return runtime.isActive(); }

View File

@ -83,6 +83,7 @@ struct Node {
std::string text;
std::string portrait;
std::string next;
std::string luaCallback;
// For Condition nodes
std::string trueNext;
@ -101,6 +102,7 @@ struct DialogueDefinition {
std::string id;
std::string displayName;
std::string startNode;
bool uninterruptible = false;
std::unordered_map<std::string, Node> nodes;
};
@ -110,6 +112,7 @@ struct CutsceneLine {
std::string portrait;
std::string sfx;
std::string background;
std::string luaCallback;
int backgroundWidth = 0; // 0 = inherit from cutscene
int backgroundHeight = 0; // 0 = inherit from cutscene
int durationMs = 0;
@ -151,6 +154,7 @@ struct StaticCutsceneDefinition {
int backgroundWidth = 1280;
int backgroundHeight = 720;
std::string music;
std::string onFadeInCallback;
bool skippable = true;
int durationMs = 0;
int fadeOutMs = 0;