Working on messenger

This commit is contained in:
Vladislav Khorev committed 2026-06-05 12:13:02 +03:00
1 parent 9221ab68ac
commit 1fc8120ee0
25 files changed
+441 -255

No files matched your search

+14 -14
View File
@@ -47,7 +47,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Бекзат, сынок, мы c мамой тебе отправили немного денег, постарайся прожить на эти деньги до конца недели!", "text": "Бекзат, сынок, мы c мамой тебе отправили немного денег, постарайся прожить на эти деньги до конца недели!",
"next": "line_2", "next": "line_2",
"bubbleSlot": "message01in" "chatBubble": "in"
}, },
{ {
"id": "line_2", "id": "line_2",
@@ -56,7 +56,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Спасибо!", "text": "Спасибо!",
"next": "end_1", "next": "end_1",
"bubbleSlot": "message02out" "chatBubble": "out"
}, },
{ {
"id": "end_1", "id": "end_1",
@@ -75,7 +75,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Жители Бишкека все чаще жалуются на депрессию и апатию. Смотрите свежее видео об этом на нашем канале!", "text": "Жители Бишкека все чаще жалуются на депрессию и апатию. Смотрите свежее видео об этом на нашем канале!",
"next": "end_1", "next": "end_1",
"bubbleSlot": "message01in" "chatBubble": "in"
}, },
{ {
"id": "end_1", "id": "end_1",
@@ -94,7 +94,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Бекзат, помнишь мы скидывались на торт для Аиды Джаныбековой? Я тогда еще приносила скатерть, тарелки и нож для торта. И я до сих пор не получила назад ничего.", "text": "Бекзат, помнишь мы скидывались на торт для Аиды Джаныбековой? Я тогда еще приносила скатерть, тарелки и нож для торта. И я до сих пор не получила назад ничего.",
"next": "line_2", "next": "line_2",
"bubbleSlot": "message01in" "chatBubble": "in"
}, },
{ {
"id": "line_2", "id": "line_2",
@@ -103,7 +103,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Скатерть и тарелки вроде бы лежат в студзоне.", "text": "Скатерть и тарелки вроде бы лежат в студзоне.",
"next": "line_3", "next": "line_3",
"bubbleSlot": "message02out" "chatBubble": "out"
}, },
{ {
"id": "line_3", "id": "line_3",
@@ -112,7 +112,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "А нож?", "text": "А нож?",
"next": "line_4", "next": "line_4",
"bubbleSlot": "message03in" "chatBubble": "in"
}, },
{ {
"id": "line_4", "id": "line_4",
@@ -121,7 +121,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Нож, наверное, так и остался в учительской.", "text": "Нож, наверное, так и остался в учительской.",
"next": "line_5", "next": "line_5",
"bubbleSlot": "message04out" "chatBubble": "out"
}, },
{ {
"id": "line_5", "id": "line_5",
@@ -130,7 +130,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "А давай не \"наверное\"?", "text": "А давай не \"наверное\"?",
"next": "line_6", "next": "line_6",
"bubbleSlot": "message05in" "chatBubble": "in"
}, },
{ {
"id": "line_6", "id": "line_6",
@@ -139,7 +139,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "А давай ты приедешь в универ, зайдешь в учительскую, заберешь нож и отдашь мне?", "text": "А давай ты приедешь в универ, зайдешь в учительскую, заберешь нож и отдашь мне?",
"next": "line_7", "next": "line_7",
"bubbleSlot": "message06in" "chatBubble": "in"
}, },
{ {
"id": "line_7", "id": "line_7",
@@ -148,7 +148,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "У вас сегодня как раз Аида ведет лекцию. После лекции попросишь у нее ключи от учительской и заберешь нож.", "text": "У вас сегодня как раз Аида ведет лекцию. После лекции попросишь у нее ключи от учительской и заберешь нож.",
"next": "line_8", "next": "line_8",
"bubbleSlot": "message07in" "chatBubble": "in"
}, },
{ {
"id": "line_8", "id": "line_8",
@@ -157,7 +157,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Почему ты сама не можешь забрать?", "text": "Почему ты сама не можешь забрать?",
"next": "line_9", "next": "line_9",
"bubbleSlot": "message08out" "chatBubble": "out"
}, },
{ {
"id": "line_9", "id": "line_9",
@@ -166,7 +166,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Ты же знаешь, если я встречу Аиду, она 100% даст мне какое-нибудь сложное задание.", "text": "Ты же знаешь, если я встречу Аиду, она 100% даст мне какое-нибудь сложное задание.",
"next": "line_10", "next": "line_10",
"bubbleSlot": "message09in" "chatBubble": "in"
}, },
{ {
"id": "line_10", "id": "line_10",
@@ -175,7 +175,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "И потом, это ты у меня брал нож, с чего я должна ходить искать его по всему универу?", "text": "И потом, это ты у меня брал нож, с чего я должна ходить искать его по всему универу?",
"next": "line_11", "next": "line_11",
"bubbleSlot": "message10in" "chatBubble": "in"
}, },
{ {
"id": "line_11", "id": "line_11",
@@ -184,7 +184,7 @@
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Так что жду тебя в универе! Не вздумай прогулять!", "text": "Так что жду тебя в универе! Не вздумай прогулять!",
"next": "end_1", "next": "end_1",
"bubbleSlot": "message11in", "chatBubble": "in",
"questUnlock" : "aiperi_knife" "questUnlock" : "aiperi_knife"
}, },
{ {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11 -142
View File
@@ -35,152 +35,26 @@
"pressed": "resources/w/ui/img/phone/PhoneChat001.png" "pressed": "resources/w/ui/img/phone/PhoneChat001.png"
} }
}, },
{ {
"type": "StaticImage", "type": "FrameLayout",
"name": "message01in", "name": "chatMessagesContainer",
"width": 320.6, "width": "match_parent",
"height": 148.4, "height": "match_parent"
"x" : 430,
"y" : 1097,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_01in.png"
}, },
{ {
"type": "StaticImage",
"name": "message02out",
"width": 320.6,
"height": 64.4,
"x" : 430,
"y" : 1022.6,
"horizontal_gravity": "right",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_02out.png"
},
{
"type": "StaticImage",
"name": "message03in",
"width": 103.6,
"height": 43.4,
"x" : 430,
"y" : 969.2,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_03in.png"
},
{
"type": "StaticImage",
"name": "message04out",
"width": 320.6,
"height": 64.4,
"x" : 430,
"y" : 894.8,
"horizontal_gravity": "right",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_04out.png"
},
{
"type": "StaticImage",
"name": "message05in",
"width": 243.6,
"height": 43.4,
"x" : 430,
"y" : 841.4,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_05in.png"
},
{
"type": "StaticImage",
"name": "message06in",
"width": 320.6,
"height": 85.4,
"x" : 430,
"y" : 746,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_06in.png"
},
{
"type": "StaticImage",
"name": "message07in",
"width": 320.6,
"height": 106.4,
"x" : 430,
"y" : 629.6,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_07in.png"
},
{
"type": "StaticImage",
"name": "message08out",
"width": 320.6,
"height": 64.4,
"x" : 430,
"y" : 555.2,
"horizontal_gravity": "right",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_08out.png"
},
{
"type": "StaticImage",
"name": "message09in",
"width": 320.6,
"height": 85.4,
"x" : 430,
"y" : 459.8,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_09in.png"
},
{
"type": "StaticImage",
"name": "message10in",
"width": 320.6,
"height": 85.4,
"x" : 430,
"y" : 364.4,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_10in.png"
},
{
"type": "StaticImage",
"name": "message11in",
"width": 320.6,
"height": 64.4,
"x" : 430,
"y" : 290,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat02_11in.png"
},
{
"type": "StaticImage", "type": "StaticImage",
"name": "chatTopCover", "name": "chatTopCover",
"width": 446.25, "width": 446.25,
"height": 70.82, "height": 70.82,
"x" : 0, "x": 0,
"y" : -50.82, "y": -50.82,
"horizontal_gravity": "center", "horizontal_gravity": "center",
"texture": "resources/w/ui/img/phone/chat_top_cover001.png" "texture": "resources/w/ui/img/phone/chat_top_cover001.png"
}, },
{ {
"type": "TextButton", "type": "TextButton",
"name": "chatTitleButton", "name": "chatTitleButton",
"horizontal_gravity": "center", "horizontal_gravity": "center",
"x": 0.0, "x": 0.0,
"y": 20.0, "y": 20.0,
"width": 446.25, "width": 446.25,
@@ -193,12 +67,7 @@
"textCentered": false, "textCentered": false,
"topAligned": true, "topAligned": true,
"wrap": true, "wrap": true,
"color": [ "color": [1.0, 1.0, 1.0, 1.0],
1.0,
1.0,
1.0,
1.0
],
"textures": { "textures": {
"normal": "resources/w/ui/img/phone/CharHeader001.png", "normal": "resources/w/ui/img/phone/CharHeader001.png",
"hover": "resources/w/ui/img/phone/CharHeader001.png", "hover": "resources/w/ui/img/phone/CharHeader001.png",
+12 -35
View File
@@ -35,20 +35,26 @@
"pressed": "resources/w/ui/img/phone/PhoneChat001.png" "pressed": "resources/w/ui/img/phone/PhoneChat001.png"
} }
}, },
{ {
"type": "FrameLayout",
"name": "chatMessagesContainer",
"width": "match_parent",
"height": "match_parent"
},
{
"type": "StaticImage", "type": "StaticImage",
"name": "chatTopCover", "name": "chatTopCover",
"width": 446.25, "width": 446.25,
"height": 70.82, "height": 70.82,
"x" : 0, "x": 0,
"y" : -50.82, "y": -50.82,
"horizontal_gravity": "center", "horizontal_gravity": "center",
"texture": "resources/w/ui/img/phone/chat_top_cover001.png" "texture": "resources/w/ui/img/phone/chat_top_cover001.png"
}, },
{ {
"type": "TextButton", "type": "TextButton",
"name": "chatTitleButton", "name": "chatTitleButton",
"horizontal_gravity": "center", "horizontal_gravity": "center",
"x": 0.0, "x": 0.0,
"y": 20.0, "y": 20.0,
"width": 446.25, "width": 446.25,
@@ -61,41 +67,12 @@
"textCentered": false, "textCentered": false,
"topAligned": true, "topAligned": true,
"wrap": true, "wrap": true,
"color": [ "color": [1.0, 1.0, 1.0, 1.0],
1.0,
1.0,
1.0,
1.0
],
"textures": { "textures": {
"normal": "resources/w/ui/img/phone/CharHeader002.png", "normal": "resources/w/ui/img/phone/CharHeader002.png",
"hover": "resources/w/ui/img/phone/CharHeader002.png", "hover": "resources/w/ui/img/phone/CharHeader002.png",
"pressed": "resources/w/ui/img/phone/CharHeader002.png" "pressed": "resources/w/ui/img/phone/CharHeader002.png"
} }
},
{
"type": "StaticImage",
"name": "message01in",
"width": 320.6,
"height": 103.6,
"x" : 430,
"y" : 506.4,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat01_01in.png"
},
{
"type": "StaticImage",
"name": "message02out",
"width": 116.2,
"height": 43.4,
"x" : 430,
"y" : 453,
"horizontal_gravity": "right",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat01_02out.png"
} }
] ]
} }
+18 -29
View File
@@ -35,10 +35,26 @@
"pressed": "resources/w/ui/img/phone/PhoneChat001.png" "pressed": "resources/w/ui/img/phone/PhoneChat001.png"
} }
}, },
{
"type": "FrameLayout",
"name": "chatMessagesContainer",
"width": "match_parent",
"height": "match_parent"
},
{
"type": "StaticImage",
"name": "chatTopCover",
"width": 446.25,
"height": 70.82,
"x": 0,
"y": -50.82,
"horizontal_gravity": "center",
"texture": "resources/w/ui/img/phone/chat_top_cover001.png"
},
{ {
"type": "TextButton", "type": "TextButton",
"name": "chatTitleButton", "name": "chatTitleButton",
"horizontal_gravity": "center", "horizontal_gravity": "center",
"x": 0.0, "x": 0.0,
"y": 20.0, "y": 20.0,
"width": 446.25, "width": 446.25,
@@ -51,39 +67,12 @@
"textCentered": false, "textCentered": false,
"topAligned": true, "topAligned": true,
"wrap": true, "wrap": true,
"color": [ "color": [1.0, 1.0, 1.0, 1.0],
1.0,
1.0,
1.0,
1.0
],
"textures": { "textures": {
"normal": "resources/w/ui/img/phone/CharHeader003.png", "normal": "resources/w/ui/img/phone/CharHeader003.png",
"hover": "resources/w/ui/img/phone/CharHeader003.png", "hover": "resources/w/ui/img/phone/CharHeader003.png",
"pressed": "resources/w/ui/img/phone/CharHeader003.png" "pressed": "resources/w/ui/img/phone/CharHeader003.png"
} }
},
{
"type": "StaticImage",
"name": "chatTopCover",
"width": 446.25,
"height": 70.82,
"x" : 0,
"y" : -50.82,
"horizontal_gravity": "center",
"texture": "resources/w/ui/img/phone/chat_top_cover001.png"
},
{
"type": "StaticImage",
"name": "message01in",
"width": 320.6,
"height": 103.6,
"x" : 430,
"y" : 506.4,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"visible": false,
"texture": "resources/w/ui/img/phone/chat03_01in.png"
} }
] ]
} }
+3 -3
View File
@@ -423,10 +423,10 @@ namespace ZL
startNightTransition(); startNightTransition();
}; };
// Wire bubble-slot callback so chat bubbles appear as dialogue lines are shown. // Wire chat-bubble callback so dynamic bubbles appear as dialogue lines are shown.
for (auto& [name, loc] : locations) { for (auto& [name, loc] : locations) {
loc->dialogueSystem.setOnBubbleSlotReady([this](const std::string& bubbleSlot) { loc->dialogueSystem.setOnChatBubbleReady([this](const std::string& text, bool incoming) {
menuManager.revealPhoneChatBubble(bubbleSlot); menuManager.onChatBubbleReady(text, incoming);
}); });
} }
+72 -18
View File
@@ -122,6 +122,18 @@ namespace ZL {
questJournal.loadFromFile("resources/quests/quests.json", zipFile); questJournal.loadFromFile("resources/quests/quests.json", zipFile);
const std::string imgDir = "resources/w/ui/img/phone/";
texBubbleInCenter_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_center.png", zipFile, true);
texBubbleInLT_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_corner_left_top.png", zipFile, true);
texBubbleInLB_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_corner_left_bottom.png", zipFile, true);
texBubbleInRT_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_corner_right_top.png", zipFile, true);
texBubbleInRB_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_corner_right_bottom.png",zipFile, true);
texBubbleOutCenter_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_center.png", zipFile, true);
texBubbleOutLT_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_corner_left_top.png", zipFile, true);
texBubbleOutLB_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_corner_left_bottom.png",zipFile, true);
texBubbleOutRT_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_corner_right_top.png", zipFile, true);
texBubbleOutRB_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_corner_right_bottom.png",zipFile, true);
enterGameplay(); enterGameplay();
} }
@@ -335,15 +347,15 @@ namespace ZL {
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {}); uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
uiManager.setTextButtonCallback("chat1button", [this](const std::string&) { uiManager.setTextButtonCallback("chat1button", [this](const std::string&) {
chatUnread_[0] = false; chatUnread_[0] = false;
openPhoneChatFromList(phoneChat1Root, "dialog_chat_aiperi001"); openPhoneChatFromList(0, phoneChat1Root, "dialog_chat_aiperi001");
}); });
uiManager.setTextButtonCallback("chat2button", [this](const std::string&) { uiManager.setTextButtonCallback("chat2button", [this](const std::string&) {
chatUnread_[1] = false; chatUnread_[1] = false;
openPhoneChatFromList(phoneChat2Root, "dialog_chat_parents001"); openPhoneChatFromList(1, phoneChat2Root, "dialog_chat_parents001");
}); });
uiManager.setTextButtonCallback("chat3button", [this](const std::string&) { uiManager.setTextButtonCallback("chat3button", [this](const std::string&) {
chatUnread_[2] = false; chatUnread_[2] = false;
openPhoneChatFromList(phoneChat3Root, "dialog_chat_news001"); openPhoneChatFromList(2, phoneChat3Root, "dialog_chat_news001");
}); });
} }
@@ -424,14 +436,14 @@ namespace ZL {
}); });
} }
void MenuManager::openPhoneChatFromList(std::shared_ptr<UiNode> chatRoot, const std::string& dialogueId) { void MenuManager::openPhoneChatFromList(int chatIndex, std::shared_ptr<UiNode> chatRoot, const std::string& dialogueId) {
activeChatIndex_ = chatIndex;
phoneChatVisibleBubbles_.clear(); phoneChatVisibleBubbles_.clear();
uiManager.pushMenuFromSavedRoot(chatRoot); uiManager.pushMenuFromSavedRoot(chatRoot);
const bool firstOpen = dialogueId.empty() || startedDialogues_.find(dialogueId) == startedDialogues_.end(); const bool firstOpen = dialogueId.empty() || startedDialogues_.find(dialogueId) == startedDialogues_.end();
if (firstOpen) {
resetPhoneChatNodes(); rebuildChatBubblesFromHistory(chatIndex);
}
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
closePhoneScreenFromChat(); closePhoneScreenFromChat();
@@ -448,12 +460,14 @@ namespace ZL {
} }
void MenuManager::returnToPhoneChatList() { void MenuManager::returnToPhoneChatList() {
activeChatIndex_ = -1;
phoneChatVisibleBubbles_.clear(); phoneChatVisibleBubbles_.clear();
uiManager.popMenu(); uiManager.popMenu();
refreshChatUnreadIndicators(); refreshChatUnreadIndicators();
} }
void MenuManager::closePhoneEntirely() { void MenuManager::closePhoneEntirely() {
activeChatIndex_ = -1;
state = GameState::Gameplay; state = GameState::Gameplay;
phoneChatVisibleBubbles_.clear(); phoneChatVisibleBubbles_.clear();
const int depth = uiManager.menuStackSize(); const int depth = uiManager.menuStackSize();
@@ -754,19 +768,59 @@ namespace ZL {
uiManager.updateAllLayouts(); uiManager.updateAllLayouts();
} }
void MenuManager::revealPhoneChatBubble(const std::string& slotName) { void MenuManager::rebuildChatBubblesFromHistory(int chatIndex) {
if (state != GameState::PhoneScreen) return; uiManager.clearChatBubbles("chatMessagesContainer");
auto node = uiManager.findNode(slotName); phoneChatVisibleBubbles_.clear();
if (!node) return;
// Zero scale before making visible to avoid a one-frame flash at full size if (chatIndex < 0 || chatIndex > 2) return;
node->scaleX = 0.0f; for (const auto& msg : chatHistory_[chatIndex]) {
node->scaleY = 0.0f; const bool inc = msg.incoming;
const std::string nodeName = uiManager.addChatBubble(
phoneChatVisibleBubbles_.push_back({slotName, node->height}); "chatMessagesContainer", msg.text, inc,
uiManager.setNodeVisible(slotName, true); inc ? texBubbleInCenter_ : texBubbleOutCenter_,
inc ? texBubbleInLT_ : texBubbleOutLT_,
inc ? texBubbleInLB_ : texBubbleOutLB_,
inc ? texBubbleInRT_ : texBubbleOutRT_,
inc ? texBubbleInRB_ : texBubbleOutRB_,
renderer, "resources/fonts/DroidSans.ttf", 20, zipFile_);
if (!nodeName.empty()) {
auto n = uiManager.findNode(nodeName);
phoneChatVisibleBubbles_.push_back({ nodeName, n ? n->height : 60.0f });
}
}
recomputePhoneChatPositions(); recomputePhoneChatPositions();
uiManager.startPopIn(slotName, 300.0f); }
void MenuManager::onChatBubbleReady(const std::string& text, bool incoming) {
if (activeChatIndex_ < 0) return;
auto& history = chatHistory_[activeChatIndex_];
if (static_cast<int>(history.size()) >= 5) {
history.erase(history.begin());
}
history.push_back({ text, incoming });
if (state != GameState::PhoneScreen) return;
const std::string nodeName = uiManager.addChatBubble(
"chatMessagesContainer", text, incoming,
incoming ? texBubbleInCenter_ : texBubbleOutCenter_,
incoming ? texBubbleInLT_ : texBubbleOutLT_,
incoming ? texBubbleInLB_ : texBubbleOutLB_,
incoming ? texBubbleInRT_ : texBubbleOutRT_,
incoming ? texBubbleInRB_ : texBubbleOutRB_,
renderer, "resources/fonts/DroidSans.ttf", 20, zipFile_);
if (nodeName.empty()) return;
auto node = uiManager.findNode(nodeName);
if (node) {
node->scaleX = 0.0f;
node->scaleY = 0.0f;
phoneChatVisibleBubbles_.push_back({ nodeName, node->height });
}
recomputePhoneChatPositions();
uiManager.startPopIn(nodeName, 300.0f);
} }
void MenuManager::setDarklandsMode(bool enabled) void MenuManager::setDarklandsMode(bool enabled)
+14 -2
View File
@@ -58,7 +58,6 @@ namespace ZL {
void openPhoneScreen(); void openPhoneScreen();
void closePhoneScreen(); void closePhoneScreen();
void revealPhoneChatBubble(const std::string& slotName);
bool isPhoneScreenOpen() const { return state == GameState::PhoneScreen; } bool isPhoneScreenOpen() const { return state == GameState::PhoneScreen; }
void closePhoneEntirely(); void closePhoneEntirely();
@@ -71,6 +70,9 @@ namespace ZL {
std::function<void()> startDarklandsTransitionFunc; std::function<void()> startDarklandsTransitionFunc;
std::function<void()> startNightTransitionFunc; std::function<void()> startNightTransitionFunc;
// Called when a chat message bubble should be shown (text + direction)
void onChatBubbleReady(const std::string& text, bool incoming);
void setDarklandsMode(bool enabled); void setDarklandsMode(bool enabled);
void advanceTutorialStep(); void advanceTutorialStep();
void onItemPickedUp(const std::string& itemId); void onItemPickedUp(const std::string& itemId);
@@ -102,10 +104,11 @@ namespace ZL {
void refreshChatUnreadIndicators(); void refreshChatUnreadIndicators();
void resetPhoneChatNodes(); void resetPhoneChatNodes();
void recomputePhoneChatPositions(); void recomputePhoneChatPositions();
void openPhoneChatFromList(std::shared_ptr<UiNode> chatRoot, const std::string& dialogueId); void openPhoneChatFromList(int chatIndex, std::shared_ptr<UiNode> chatRoot, const std::string& dialogueId);
void returnToPhoneChatList(); void returnToPhoneChatList();
void closePhoneScreenFromChat(); void closePhoneScreenFromChat();
void applyUniIntHud(); void applyUniIntHud();
void rebuildChatBubblesFromHistory(int chatIndex);
GameState state = GameState::Gameplay; GameState state = GameState::Gameplay;
Inventory* inventory = nullptr; Inventory* inventory = nullptr;
@@ -176,6 +179,15 @@ namespace ZL {
}; };
std::vector<PhoneChatBubbleInfo> phoneChatVisibleBubbles_; std::vector<PhoneChatBubbleInfo> phoneChatVisibleBubbles_;
// Per-chat message history (max 5 messages each)
struct StoredChatMessage { std::string text; bool incoming; };
std::vector<StoredChatMessage> chatHistory_[3];
int activeChatIndex_ = -1;
// Preloaded bubble textures
std::shared_ptr<Texture> texBubbleInCenter_, texBubbleInLT_, texBubbleInLB_, texBubbleInRT_, texBubbleInRB_;
std::shared_ptr<Texture> texBubbleOutCenter_, texBubbleOutLT_, texBubbleOutLB_, texBubbleOutRT_, texBubbleOutRB_;
static constexpr float CHAT_TOP_Y = 610.0f; static constexpr float CHAT_TOP_Y = 610.0f;
static constexpr float CHAT_BOTTOM_Y = 290.0f; static constexpr float CHAT_BOTTOM_Y = 290.0f;
static constexpr float CHAT_SPACING = 10.0f; static constexpr float CHAT_SPACING = 10.0f;
+200
View File
@@ -824,6 +824,107 @@ namespace ZL {
return root; return root;
} }
// ---- UiChatBubble helpers ----
static void buildQuadMesh(VertexRenderStruct& mesh, float x0, float y0, float x1, float y1) {
mesh.data.PositionData.clear();
mesh.data.TexCoordData.clear();
mesh.data.PositionData.push_back({ x0, y0, 0 });
mesh.data.TexCoordData.push_back({ 0, 0 });
mesh.data.PositionData.push_back({ x0, y1, 0 });
mesh.data.TexCoordData.push_back({ 0, 1 });
mesh.data.PositionData.push_back({ x1, y1, 0 });
mesh.data.TexCoordData.push_back({ 1, 1 });
mesh.data.PositionData.push_back({ x0, y0, 0 });
mesh.data.TexCoordData.push_back({ 0, 0 });
mesh.data.PositionData.push_back({ x1, y1, 0 });
mesh.data.TexCoordData.push_back({ 1, 1 });
mesh.data.PositionData.push_back({ x1, y0, 0 });
mesh.data.TexCoordData.push_back({ 1, 0 });
mesh.RefreshVBO();
}
float UiChatBubble::computeHeight() const {
if (!textRenderer) return CORNER_SIZE * 2 + PAD_Y * 2 + static_cast<float>(fontSize);
const float textW = MAX_WIDTH - CORNER_SIZE * 2.0f - PAD_X * 2.0f;
const std::string wrapped = wrapTextByPixels(text, *textRenderer, textW, 1.0f);
int lines = 1;
for (char c : wrapped) if (c == '\n') ++lines;
return CORNER_SIZE * 2.0f + PAD_Y * 2.0f + lines * static_cast<float>(fontSize) * 1.25f;
}
void UiChatBubble::buildMeshes() {
const float x = rect.x;
const float y = rect.y;
const float w = rect.w;
const float h = rect.h;
const float cs = CORNER_SIZE;
const float OFFSET = 0.5f;
// 1. Middle center (full width, between top and bottom corner rows)
buildQuadMesh(meshMiddle, x, y + cs- OFFSET, x + w, y + h - cs +OFFSET);
// 2. Top strip (between corners)
buildQuadMesh(meshTopStrip, x + cs- OFFSET, y + h - cs, x + w - cs+ OFFSET, y + h);
// 3. Bottom strip
buildQuadMesh(meshBotStrip, x + cs- OFFSET, y, x + w - cs+ OFFSET, y + cs);
// Corners
buildQuadMesh(meshCornerLT, x, y + h - cs-OFFSET, x + cs, y + h);
buildQuadMesh(meshCornerRT, x + w - cs, y + h - cs - OFFSET, x + w, y + h);
buildQuadMesh(meshCornerLB, x, y, x + cs, y + cs);
buildQuadMesh(meshCornerRB, x + w - cs, y, x + w, y + cs);
}
void UiChatBubble::draw(Renderer& renderer, float nodeScaleX, float nodeScaleY) const {
renderer.RenderUniform1i(textureUniformName, 0);
// Corners with blending (semi-transparent rounded corners)
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
auto drawCorner = [&](const std::shared_ptr<Texture>& tex, const VertexRenderStruct& mesh) {
if (tex) {
glBindTexture(GL_TEXTURE_2D, tex->getTexID());
renderer.DrawVertexRenderStruct(mesh);
}
};
drawCorner(texCornerLT, meshCornerLT);
drawCorner(texCornerRT, meshCornerRT);
drawCorner(texCornerLB, meshCornerLB);
drawCorner(texCornerRB, meshCornerRB);
glDisable(GL_BLEND);
// Center parts (no blending needed — opaque fill)
if (texCenter) {
glBindTexture(GL_TEXTURE_2D, texCenter->getTexID());
renderer.DrawVertexRenderStruct(meshMiddle);
renderer.DrawVertexRenderStruct(meshTopStrip);
renderer.DrawVertexRenderStruct(meshBotStrip);
}
// Text — position is transformed around the bubble centre to match the renderer's
// scale matrix (which scales meshes around the same centre point).
if (textRenderer && !text.empty()) {
const float textW = rect.w - CORNER_SIZE * 2.0f - PAD_X * 2.0f;
const std::string wrapped = wrapTextByPixels(text, *textRenderer, textW, 1.0f);
const float tx0 = rect.x + CORNER_SIZE + PAD_X;
const float ty0 = rect.y + rect.h - CORNER_SIZE - PAD_Y - static_cast<float>(fontSize);
// Scale around bubble centre, matching how the renderer's ScaleMatrix works
const float cx = rect.x + rect.w * 0.5f;
const float cy = rect.y + rect.h * 0.5f;
const float tx = cx + (tx0 - cx) * nodeScaleX;
const float ty = cy + (ty0 - cy) * nodeScaleY;
textRenderer->drawText(wrapped, tx, ty, nodeScaleX, false, { 1.f, 1.f, 1.f, 1.f });
}
}
// ---- end UiChatBubble helpers ----
void UiManager::replaceRoot(std::shared_ptr<UiNode> newRoot) { void UiManager::replaceRoot(std::shared_ptr<UiNode> newRoot) {
root = newRoot; root = newRoot;
layoutNode( layoutNode(
@@ -841,6 +942,7 @@ namespace ZL {
textViews.clear(); textViews.clear();
textFields.clear(); textFields.clear();
staticImages.clear(); staticImages.clear();
chatBubbles.clear();
pulsingNodes.clear(); pulsingNodes.clear();
popInNodes.clear(); popInNodes.clear();
collectButtonsAndSliders(root); collectButtonsAndSliders(root);
@@ -1052,6 +1154,12 @@ namespace ZL {
node->staticImage->rect = node->screenRect; node->staticImage->rect = node->screenRect;
node->staticImage->buildMesh(); node->staticImage->buildMesh();
} }
// 6. Chat bubble
if (node->chatBubble) {
node->chatBubble->rect = node->screenRect;
node->chatBubble->buildMeshes();
}
} }
void UiManager::updateAllLayouts() { void UiManager::updateAllLayouts() {
@@ -1106,6 +1214,9 @@ namespace ZL {
if (node->pulseEnabled && node->staticImage) { if (node->pulseEnabled && node->staticImage) {
pulsingNodes.push_back(node); pulsingNodes.push_back(node);
} }
if (node->chatBubble) {
chatBubbles.push_back(node->chatBubble);
}
for (auto& c : node->children) collectButtonsAndSliders(c); for (auto& c : node->children) collectButtonsAndSliders(c);
} }
@@ -1215,6 +1326,7 @@ namespace ZL {
prev.textViews = textViews; prev.textViews = textViews;
prev.textFields = textFields; prev.textFields = textFields;
prev.staticImages = staticImages; prev.staticImages = staticImages;
prev.chatBubbles = chatBubbles;
prev.pulsingNodes = pulsingNodes; prev.pulsingNodes = pulsingNodes;
prev.pressedButtons = pressedButtons; prev.pressedButtons = pressedButtons;
prev.pressedTextButtons = pressedTextButtons; prev.pressedTextButtons = pressedTextButtons;
@@ -1280,6 +1392,7 @@ namespace ZL {
textViews = s.textViews; textViews = s.textViews;
textFields = s.textFields; textFields = s.textFields;
staticImages = s.staticImages; staticImages = s.staticImages;
chatBubbles = s.chatBubbles;
pulsingNodes = s.pulsingNodes; pulsingNodes = s.pulsingNodes;
popInNodes = s.popInNodes; popInNodes = s.popInNodes;
pressedButtons = s.pressedButtons; pressedButtons = s.pressedButtons;
@@ -1348,6 +1461,11 @@ namespace ZL {
node->staticImage->draw(renderer); node->staticImage->draw(renderer);
} }
// 1b. Chat bubble (composite widget)
if (node->chatBubble) {
node->chatBubble->draw(renderer, node->scaleX, node->scaleY);
}
// 2. Потом кнопки // 2. Потом кнопки
if (node->button) { if (node->button) {
node->button->draw(renderer); node->button->draw(renderer);
@@ -1994,4 +2112,86 @@ namespace ZL {
if (!node) return false; if (!node) return false;
return node->visible; return node->visible;
} }
std::string UiManager::addChatBubble(
const std::string& parentNodeName,
const std::string& text, bool incoming,
std::shared_ptr<Texture> texCenter,
std::shared_ptr<Texture> texLT, std::shared_ptr<Texture> texLB,
std::shared_ptr<Texture> texRT, std::shared_ptr<Texture> texRB,
Renderer& renderer, const std::string& fontPath, int fontSize,
const std::string& zipFile)
{
if (!root) return "";
auto parent = findNodeByName(root, parentNodeName);
if (!parent) {
std::cerr << "UiManager::addChatBubble: parent '" << parentNodeName << "' not found\n";
return "";
}
static int bubbleCounter = 0;
const std::string nodeName = "chat_bubble_" + std::to_string(bubbleCounter++);
// Build the chat bubble component
auto bubble = std::make_shared<UiChatBubble>();
bubble->name = nodeName;
bubble->text = text;
bubble->incoming = incoming;
bubble->fontSize = fontSize;
bubble->texCenter = texCenter;
bubble->texCornerLT = texLT;
bubble->texCornerLB = texLB;
bubble->texCornerRT = texRT;
bubble->texCornerRB = texRB;
bubble->textRenderer = std::make_unique<TextRenderer>();
if (!bubble->textRenderer->init(renderer, fontPath, fontSize, zipFile)) {
std::cerr << "UiManager::addChatBubble: TextRenderer init failed\n";
}
// Build the UiNode
auto node = std::make_shared<UiNode>();
node->name = nodeName;
node->width = UiChatBubble::MAX_WIDTH;
node->height = bubble->computeHeight();
node->localX = 430.0f;
node->localY = 0.0f;
node->visible = true;
node->chatBubble = bubble;
if (incoming) {
node->layoutSettings.hGravity = HorizontalGravity::Left;
} else {
node->layoutSettings.hGravity = HorizontalGravity::Right;
}
// Bottom gravity means localY is used as-is for screenRect.y (no parentH transform).
// The original static chat nodes all had "vertical_gravity": "bottom" for the same reason.
node->layoutSettings.vGravity = VerticalGravity::Bottom;
parent->children.push_back(node);
// Re-layout and collect the new node
updateAllLayouts();
bubble->rect = node->screenRect;
bubble->buildMeshes();
chatBubbles.push_back(bubble);
return nodeName;
}
void UiManager::clearChatBubbles(const std::string& parentNodeName) {
if (!root) return;
auto parent = findNodeByName(root, parentNodeName);
if (!parent) return;
// Remove children that have a chatBubble component
auto& ch = parent->children;
ch.erase(std::remove_if(ch.begin(), ch.end(),
[](const std::shared_ptr<UiNode>& n) { return n && n->chatBubble != nullptr; }),
ch.end());
// Remove from chatBubbles collection
chatBubbles.clear();
}
} // namespace ZL } // namespace ZL
+55
View File
@@ -245,6 +245,45 @@ namespace ZL {
void draw(Renderer& renderer) const; void draw(Renderer& renderer) const;
}; };
struct UiChatBubble {
std::string name;
UiRect rect;
std::string text;
bool incoming = true;
std::shared_ptr<Texture> texCenter;
std::shared_ptr<Texture> texCornerLT;
std::shared_ptr<Texture> texCornerLB;
std::shared_ptr<Texture> texCornerRT;
std::shared_ptr<Texture> texCornerRB;
static constexpr float CORNER_SIZE = 22.4f;
static constexpr float MAX_WIDTH = 320.6f;
//static constexpr float PAD_X = 7.0f;
//static constexpr float PAD_Y = 5.0f;
static constexpr float PAD_X = -15.0f;
static constexpr float PAD_Y = -15.0f;
// 7 meshes: middle-center, top-strip, bottom-strip, LT, RT, LB, RB
VertexRenderStruct meshMiddle;
VertexRenderStruct meshTopStrip;
VertexRenderStruct meshBotStrip;
VertexRenderStruct meshCornerLT;
VertexRenderStruct meshCornerRT;
VertexRenderStruct meshCornerLB;
VertexRenderStruct meshCornerRB;
std::unique_ptr<TextRenderer> textRenderer;
int fontSize = 20;
// Must be called after rect is set and textRenderer is initialised
float computeHeight() const;
void buildMeshes();
// nodeScaleX/Y: the owning UiNode's current scale (for pop-in / animations).
// Text position and glyph size are transformed to match the renderer's scale matrix.
void draw(Renderer& renderer, float nodeScaleX = 1.0f, float nodeScaleY = 1.0f) const;
};
struct UiNode { struct UiNode {
std::string name; std::string name;
LayoutType layoutType = LayoutType::Frame; LayoutType layoutType = LayoutType::Frame;
@@ -290,6 +329,7 @@ namespace ZL {
std::shared_ptr<UiTextView> textView; std::shared_ptr<UiTextView> textView;
std::shared_ptr<UiTextField> textField; std::shared_ptr<UiTextField> textField;
std::shared_ptr<UiStaticImage> staticImage; std::shared_ptr<UiStaticImage> staticImage;
std::shared_ptr<UiChatBubble> chatBubble;
// Анимации // Анимации
struct AnimStep { struct AnimStep {
@@ -409,6 +449,19 @@ namespace ZL {
void clearMenuStack(); void clearMenuStack();
int menuStackSize() const; int menuStackSize() const;
// Dynamically create a chat bubble node and append it to a named parent node.
// Returns the generated node name, or "" on failure.
std::string addChatBubble(const std::string& parentNodeName,
const std::string& text, bool incoming,
std::shared_ptr<Texture> texCenter,
std::shared_ptr<Texture> texLT, std::shared_ptr<Texture> texLB,
std::shared_ptr<Texture> texRT, std::shared_ptr<Texture> texRB,
Renderer& renderer, const std::string& fontPath, int fontSize,
const std::string& zipFile);
// Remove all chat-bubble children from named parent and clear chatBubbles collection.
void clearChatBubbles(const std::string& parentNodeName);
void update(float deltaMs); void update(float deltaMs);
void startAnimation(const std::string& animName); void startAnimation(const std::string& animName);
bool startAnimationOnNode(const std::string& nodeName, const std::string& animName); bool startAnimationOnNode(const std::string& nodeName, const std::string& animName);
@@ -462,6 +515,7 @@ namespace ZL {
std::vector<std::shared_ptr<UiTextView>> textViews; std::vector<std::shared_ptr<UiTextView>> textViews;
std::vector<std::shared_ptr<UiTextField>> textFields; std::vector<std::shared_ptr<UiTextField>> textFields;
std::vector<std::shared_ptr<UiStaticImage>> staticImages; std::vector<std::shared_ptr<UiStaticImage>> staticImages;
std::vector<std::shared_ptr<UiChatBubble>> chatBubbles;
std::vector<std::shared_ptr<UiNode>> pulsingNodes; std::vector<std::shared_ptr<UiNode>> pulsingNodes;
std::vector<std::shared_ptr<UiNode>> popInNodes; std::vector<std::shared_ptr<UiNode>> popInNodes;
@@ -483,6 +537,7 @@ namespace ZL {
std::vector<std::shared_ptr<UiTextView>> textViews; std::vector<std::shared_ptr<UiTextView>> textViews;
std::vector<std::shared_ptr<UiTextField>> textFields; std::vector<std::shared_ptr<UiTextField>> textFields;
std::vector<std::shared_ptr<UiStaticImage>> staticImages; std::vector<std::shared_ptr<UiStaticImage>> staticImages;
std::vector<std::shared_ptr<UiChatBubble>> chatBubbles;
std::vector<std::shared_ptr<UiNode>> pulsingNodes; std::vector<std::shared_ptr<UiNode>> pulsingNodes;
std::vector<std::shared_ptr<UiNode>> popInNodes; std::vector<std::shared_ptr<UiNode>> popInNodes;
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons; std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
+1 -1
View File
@@ -105,7 +105,7 @@ Node DialogueDatabase::parseNode(const json& j) {
node.falseNext = j.value("falseNext", ""); node.falseNext = j.value("falseNext", "");
node.cutsceneId = j.value("cutsceneId", ""); node.cutsceneId = j.value("cutsceneId", "");
node.luaCallback = j.value("luaCallback", ""); node.luaCallback = j.value("luaCallback", "");
node.bubbleSlot = j.value("bubbleSlot", ""); node.chatBubble = j.value("chatBubble", "");
node.questUnlock = j.value("questUnlock", ""); node.questUnlock = j.value("questUnlock", "");
node.questComplete = j.value("questComplete", ""); node.questComplete = j.value("questComplete", "");
node.questFail = j.value("questFail", ""); node.questFail = j.value("questFail", "");
+4 -4
View File
@@ -87,8 +87,8 @@ void DialogueRuntime::setOnCutsceneFadeInComplete(std::function<void(const std::
onCutsceneFadeInComplete = std::move(cb); onCutsceneFadeInComplete = std::move(cb);
} }
void DialogueRuntime::setOnBubbleSlotReady(std::function<void(const std::string&)> cb) { void DialogueRuntime::setOnChatBubbleReady(std::function<void(const std::string&, bool)> cb) {
onBubbleSlotReady = std::move(cb); onChatBubbleReady = std::move(cb);
} }
void DialogueRuntime::stop() { void DialogueRuntime::stop() {
@@ -466,8 +466,8 @@ void DialogueRuntime::presentLine(const Node& node) {
if (!node.luaCallback.empty() && onDialogueLineStarted) { if (!node.luaCallback.empty() && onDialogueLineStarted) {
onDialogueLineStarted(node.luaCallback); onDialogueLineStarted(node.luaCallback);
} }
if (!node.bubbleSlot.empty() && onBubbleSlotReady) { if (!node.chatBubble.empty() && onChatBubbleReady) {
onBubbleSlotReady(node.bubbleSlot); onChatBubbleReady(node.text, node.chatBubble == "in");
} }
} }
+2 -2
View File
@@ -23,7 +23,7 @@ public:
void setOnDialogueLineStarted(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 setOnCutsceneLineStarted(std::function<void(const std::string&)> cb);
void setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb); void setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb);
void setOnBubbleSlotReady(std::function<void(const std::string&)> cb); void setOnChatBubbleReady(std::function<void(const std::string&, bool)> cb);
void stop(); void stop();
void update(int deltaMs); void update(int deltaMs);
@@ -60,7 +60,7 @@ private:
std::function<void(const std::string&)> onDialogueLineStarted; std::function<void(const std::string&)> onDialogueLineStarted;
std::function<void(const std::string&)> onCutsceneLineStarted; std::function<void(const std::string&)> onCutsceneLineStarted;
std::function<void(const std::string&)> onCutsceneFadeInComplete; std::function<void(const std::string&)> onCutsceneFadeInComplete;
std::function<void(const std::string&)> onBubbleSlotReady; std::function<void(const std::string&, bool)> onChatBubbleReady;
std::string activeCutsceneId; std::string activeCutsceneId;
bool fadeInCallbackFired = false; bool fadeInCallbackFired = false;
+2 -2
View File
@@ -140,8 +140,8 @@ void DialogueSystem::setOnCutsceneFadeInComplete(std::function<void(const std::s
runtime.setOnCutsceneFadeInComplete(std::move(cb)); runtime.setOnCutsceneFadeInComplete(std::move(cb));
} }
void DialogueSystem::setOnBubbleSlotReady(std::function<void(const std::string&)> cb) { void DialogueSystem::setOnChatBubbleReady(std::function<void(const std::string&, bool)> cb) {
runtime.setOnBubbleSlotReady(std::move(cb)); runtime.setOnChatBubbleReady(std::move(cb));
} }
void DialogueSystem::stopDialogue() { void DialogueSystem::stopDialogue() {
+1 -1
View File
@@ -28,7 +28,7 @@ public:
void setOnDialogueLineStarted(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 setOnCutsceneLineStarted(std::function<void(const std::string&)> cb);
void setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb); void setOnCutsceneFadeInComplete(std::function<void(const std::string&)> cb);
void setOnBubbleSlotReady(std::function<void(const std::string&)> cb); void setOnChatBubbleReady(std::function<void(const std::string&, bool)> cb);
void setOnDialogueAdvanced(std::function<void()> cb); void setOnDialogueAdvanced(std::function<void()> cb);
void stopDialogue(); void stopDialogue();
+2 -2
View File
@@ -97,8 +97,8 @@ struct Node {
// For CutsceneStart // For CutsceneStart
std::string cutsceneId; std::string cutsceneId;
// Name of the UI node (StaticImage) to reveal in the phone chat when this line is shown // "in" or "out" — creates a dynamic chat bubble; empty = not a chat message
std::string bubbleSlot; std::string chatBubble;
// Quest actions fired when this line is presented (empty = no action) // Quest actions fired when this line is presented (empty = no action)
std::string questUnlock; std::string questUnlock;