Working on phone messages
This commit is contained in:
1 parent
cdab525858
commit
9425f9b19a
20 files changed
+591
-28
No files matched your search
+26
-4
@@ -387,6 +387,18 @@ namespace ZL
|
||||
menuManager.onItemPickedUp(itemId);
|
||||
};
|
||||
|
||||
// Wire phone dialogue start function so MenuManager can trigger dialogues.
|
||||
menuManager.startDialogueFunc = [this](const std::string& id) {
|
||||
if (currentLocation) currentLocation->dialogueSystem.startDialogue(id);
|
||||
};
|
||||
|
||||
// Wire bubble-slot callback so chat bubbles appear as dialogue lines are shown.
|
||||
for (auto& [name, loc] : locations) {
|
||||
loc->dialogueSystem.setOnBubbleSlotReady([this](const std::string& bubbleSlot) {
|
||||
menuManager.revealPhoneChatBubble(bubbleSlot);
|
||||
});
|
||||
}
|
||||
|
||||
currentLocation = locations["location_dorm"];
|
||||
currentLocation->scriptEngine.callLocationEnterCallback();
|
||||
|
||||
@@ -705,6 +717,7 @@ namespace ZL
|
||||
break;
|
||||
case SDLK_f:
|
||||
currentLocation->dialogueSystem.startDialogue("dialog_start001");
|
||||
|
||||
break;
|
||||
case SDLK_e:
|
||||
currentLocation->dialogueSystem.startCutscene("test_cutscene_01"); //.startDialogue("test_cutscene_pan_dialogue");
|
||||
@@ -940,8 +953,11 @@ namespace ZL
|
||||
|
||||
const int uiX = mx;
|
||||
const int uiY = Environment::projectionHeight - my;
|
||||
menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);
|
||||
st.capturedByUi = menuManager.uiManager.isUiInteractionForFinger(fingerId);
|
||||
const bool dialogueActive = currentLocation && currentLocation->dialogueSystem.isActive();
|
||||
if (!dialogueActive) {
|
||||
menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);
|
||||
}
|
||||
st.capturedByUi = !dialogueActive && menuManager.uiManager.isUiInteractionForFinger(fingerId);
|
||||
|
||||
activePointers[fingerId] = st;
|
||||
|
||||
@@ -972,7 +988,10 @@ namespace ZL
|
||||
{
|
||||
const int uiX = mx;
|
||||
const int uiY = Environment::projectionHeight - my;
|
||||
menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);
|
||||
const bool dialogueActive = currentLocation && currentLocation->dialogueSystem.isActive();
|
||||
if (!dialogueActive) {
|
||||
menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);
|
||||
}
|
||||
|
||||
auto it = activePointers.find(fingerId);
|
||||
if (it == activePointers.end()) return;
|
||||
@@ -1013,7 +1032,10 @@ namespace ZL
|
||||
{
|
||||
const int uiX = mx;
|
||||
const int uiY = Environment::projectionHeight - my;
|
||||
menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);
|
||||
const bool dialogueActive = currentLocation && currentLocation->dialogueSystem.isActive();
|
||||
if (!dialogueActive) {
|
||||
menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);
|
||||
}
|
||||
|
||||
auto it = activePointers.find(fingerId);
|
||||
if (it != activePointers.end()) {
|
||||
|
||||
+59
-6
@@ -83,6 +83,7 @@ namespace ZL {
|
||||
hudStep5abRoot = loadUiFromFile("resources/w/ui/hud_step5ab.json", renderer, zipFile);
|
||||
//phoneScreenRoot = loadUiFromFile("resources/w/ui/screen_phone_chat_list.json", renderer, zipFile);
|
||||
phoneScreenRoot = loadUiFromFile("resources/w/ui/screen_phone_chat2.json", renderer, zipFile);
|
||||
//phoneScreenRoot = loadUiFromFile("resources/w/ui/screen_phone_chat1.json", renderer, zipFile);
|
||||
newInventoryRoot = loadUiFromFile("resources/w/ui/screen_inventory.json", renderer, zipFile);
|
||||
questJournalRoot = loadUiFromFile("resources/w/ui/screen_journal.json", renderer, zipFile);
|
||||
|
||||
@@ -202,17 +203,19 @@ namespace ZL {
|
||||
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
||||
closePhoneScreen();
|
||||
});
|
||||
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {
|
||||
//Keep the callback
|
||||
});
|
||||
uiManager.setTextButtonCallback("chat1button", [this](const std::string&) {
|
||||
std::cout << "Hello test " << std::endl;
|
||||
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
||||
|
||||
});
|
||||
// Reset chat state and start the dialogue
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
resetPhoneChatNodes();
|
||||
if (startDialogueFunc) {
|
||||
startDialogueFunc("dialog_chat_aiperi001");
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager::closePhoneScreen() {
|
||||
state = GameState::Gameplay;
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
uiManager.popMenu();
|
||||
}
|
||||
|
||||
@@ -412,4 +415,54 @@ namespace ZL {
|
||||
refreshQuestJournalUi();
|
||||
}
|
||||
|
||||
void MenuManager::resetPhoneChatNodes() {
|
||||
static const char* kChatNodes[] = {
|
||||
"message01in", "message02out", "message03in", "message04out",
|
||||
"message05in", "message06in", "message07in", "message08out",
|
||||
"message09in", "message10in", "message11in", nullptr
|
||||
};
|
||||
for (int i = 0; kChatNodes[i]; ++i) {
|
||||
uiManager.setNodeVisible(kChatNodes[i], false);
|
||||
auto n = uiManager.findNode(kChatNodes[i]);
|
||||
if (n) { n->scaleX = 1.0f; n->scaleY = 1.0f; }
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager::recomputePhoneChatPositions() {
|
||||
float totalHeight = 0.0f;
|
||||
for (size_t i = 0; i < phoneChatVisibleBubbles_.size(); ++i) {
|
||||
totalHeight += phoneChatVisibleBubbles_[i].height;
|
||||
if (i > 0) totalHeight += CHAT_SPACING;
|
||||
}
|
||||
|
||||
const float available = CHAT_TOP_Y - CHAT_BOTTOM_Y;
|
||||
const float topY = (totalHeight <= available)
|
||||
? CHAT_TOP_Y
|
||||
: CHAT_BOTTOM_Y + totalHeight;
|
||||
|
||||
float cursor = topY;
|
||||
for (auto& bubble : phoneChatVisibleBubbles_) {
|
||||
auto node = uiManager.findNode(bubble.nodeName);
|
||||
if (!node) continue;
|
||||
node->localY = cursor - bubble.height;
|
||||
cursor -= bubble.height + CHAT_SPACING;
|
||||
}
|
||||
uiManager.updateAllLayouts();
|
||||
}
|
||||
|
||||
void MenuManager::revealPhoneChatBubble(const std::string& slotName) {
|
||||
if (state != GameState::PhoneScreen) return;
|
||||
auto node = uiManager.findNode(slotName);
|
||||
if (!node) return;
|
||||
|
||||
// Zero scale before making visible to avoid a one-frame flash at full size
|
||||
node->scaleX = 0.0f;
|
||||
node->scaleY = 0.0f;
|
||||
|
||||
phoneChatVisibleBubbles_.push_back({slotName, node->height});
|
||||
uiManager.setNodeVisible(slotName, true);
|
||||
recomputePhoneChatPositions();
|
||||
uiManager.startPopIn(slotName, 300.0f);
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
@@ -50,6 +50,10 @@ namespace ZL {
|
||||
|
||||
void openPhoneScreen();
|
||||
void closePhoneScreen();
|
||||
void revealPhoneChatBubble(const std::string& slotName);
|
||||
bool isPhoneScreenOpen() const { return state == GameState::PhoneScreen; }
|
||||
|
||||
std::function<void(const std::string&)> startDialogueFunc;
|
||||
|
||||
void advanceTutorialStep();
|
||||
void onItemPickedUp(const std::string& itemId);
|
||||
@@ -66,6 +70,8 @@ namespace ZL {
|
||||
void selectInventoryItem(int index);
|
||||
void refreshItemPickupHud();
|
||||
void setupStep5Callbacks();
|
||||
void resetPhoneChatNodes();
|
||||
void recomputePhoneChatPositions();
|
||||
|
||||
GameState state = GameState::Gameplay;
|
||||
Inventory* inventory = nullptr;
|
||||
@@ -96,6 +102,17 @@ namespace ZL {
|
||||
|
||||
int selectedQuestIndex = -1;
|
||||
std::vector<std::string> visibleQuestIds;
|
||||
|
||||
// Phone chat state
|
||||
struct PhoneChatBubbleInfo {
|
||||
std::string nodeName;
|
||||
float height;
|
||||
};
|
||||
std::vector<PhoneChatBubbleInfo> phoneChatVisibleBubbles_;
|
||||
|
||||
static constexpr float CHAT_TOP_Y = 610.0f;
|
||||
static constexpr float CHAT_BOTTOM_Y = 290.0f;
|
||||
static constexpr float CHAT_SPACING = 10.0f;
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
@@ -429,6 +429,7 @@ namespace ZL {
|
||||
}
|
||||
|
||||
if (j.contains("name")) node->name = j["name"].get<std::string>();
|
||||
if (j.contains("visible")) node->visible = j["visible"].get<bool>();
|
||||
|
||||
// 2. Читаем размеры во временные "локальные" поля
|
||||
// Это критически важно: мы не пишем сразу в screenRect,
|
||||
@@ -825,6 +826,7 @@ namespace ZL {
|
||||
textFields.clear();
|
||||
staticImages.clear();
|
||||
pulsingNodes.clear();
|
||||
popInNodes.clear();
|
||||
collectButtonsAndSliders(root);
|
||||
|
||||
nodeActiveAnims.clear();
|
||||
@@ -1050,6 +1052,17 @@ namespace ZL {
|
||||
);
|
||||
}
|
||||
|
||||
void UiManager::startPopIn(const std::string& nodeName, float durationMs) {
|
||||
auto node = findNode(nodeName);
|
||||
if (!node) return;
|
||||
node->scaleX = 0.0f;
|
||||
node->scaleY = 0.0f;
|
||||
node->popInActive = true;
|
||||
node->popInProgress = 0.0f;
|
||||
node->popInDurationMs = durationMs;
|
||||
popInNodes.push_back(node);
|
||||
}
|
||||
|
||||
void UiManager::collectButtonsAndSliders(const std::shared_ptr<UiNode>& node) {
|
||||
if (node->button) {
|
||||
buttons.push_back(node->button);
|
||||
@@ -1188,6 +1201,7 @@ namespace ZL {
|
||||
prev.pressedSliders = pressedSliders;
|
||||
prev.focusedTextField = focusedTextField;
|
||||
prev.path = "";
|
||||
prev.popInNodes = popInNodes;
|
||||
|
||||
prev.animCallbacks = animCallbacks;
|
||||
|
||||
@@ -1246,6 +1260,7 @@ namespace ZL {
|
||||
textFields = s.textFields;
|
||||
staticImages = s.staticImages;
|
||||
pulsingNodes = s.pulsingNodes;
|
||||
popInNodes = s.popInNodes;
|
||||
pressedButtons = s.pressedButtons;
|
||||
pressedTextButtons = s.pressedTextButtons;
|
||||
pressedSliders = s.pressedSliders;
|
||||
@@ -1381,6 +1396,26 @@ namespace ZL {
|
||||
node->scaleY = s;
|
||||
}
|
||||
|
||||
// Pop-in scale animations (scale 0 → 1 on bubble reveal)
|
||||
for (auto& node : popInNodes) {
|
||||
if (!node || !node->popInActive) continue;
|
||||
node->popInProgress += deltaMs / node->popInDurationMs;
|
||||
if (node->popInProgress >= 1.0f) {
|
||||
node->popInProgress = 1.0f;
|
||||
node->popInActive = false;
|
||||
node->scaleX = 1.0f;
|
||||
node->scaleY = 1.0f;
|
||||
} else {
|
||||
// ease-out quad
|
||||
const float t = node->popInProgress;
|
||||
const float s = 1.0f - (1.0f - t) * (1.0f - t);
|
||||
node->scaleX = s;
|
||||
node->scaleY = s;
|
||||
}
|
||||
}
|
||||
popInNodes.erase(std::remove_if(popInNodes.begin(), popInNodes.end(),
|
||||
[](const auto& n) { return !n || !n->popInActive; }), popInNodes.end());
|
||||
|
||||
std::vector<std::pair<std::shared_ptr<UiNode>, size_t>> animationsToRemove;
|
||||
std::vector<std::function<void()>> pendingCallbacks;
|
||||
|
||||
|
||||
@@ -272,6 +272,11 @@ namespace ZL {
|
||||
float pulsePeriodMs = 1000.0f;
|
||||
float pulseElapsedMs = 0.0f; // runtime, not persisted in JSON
|
||||
|
||||
// Pop-in scale animation (scale 0→1 on first reveal)
|
||||
bool popInActive = false;
|
||||
float popInProgress = 0.0f; // 0..1
|
||||
float popInDurationMs = 300.0f;
|
||||
|
||||
// Иерархия
|
||||
std::vector<std::shared_ptr<UiNode>> children;
|
||||
|
||||
@@ -406,6 +411,7 @@ namespace ZL {
|
||||
bool stopAnimationOnNode(const std::string& nodeName, const std::string& animName);
|
||||
bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb);
|
||||
void updateAllLayouts();
|
||||
void startPopIn(const std::string& nodeName, float durationMs = 300.0f);
|
||||
|
||||
std::shared_ptr<UiNode> findNode(const std::string& name);
|
||||
|
||||
@@ -448,6 +454,7 @@ namespace ZL {
|
||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||
std::vector<std::shared_ptr<UiStaticImage>> staticImages;
|
||||
std::vector<std::shared_ptr<UiNode>> pulsingNodes;
|
||||
std::vector<std::shared_ptr<UiNode>> popInNodes;
|
||||
|
||||
std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims;
|
||||
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName)
|
||||
@@ -467,6 +474,7 @@ namespace ZL {
|
||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||
std::vector<std::shared_ptr<UiStaticImage>> staticImages;
|
||||
std::vector<std::shared_ptr<UiNode>> pulsingNodes;
|
||||
std::vector<std::shared_ptr<UiNode>> popInNodes;
|
||||
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
|
||||
std::map<int64_t, std::shared_ptr<UiTextButton>> pressedTextButtons;
|
||||
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
||||
|
||||
@@ -105,6 +105,7 @@ Node DialogueDatabase::parseNode(const json& j) {
|
||||
node.falseNext = j.value("falseNext", "");
|
||||
node.cutsceneId = j.value("cutsceneId", "");
|
||||
node.luaCallback = j.value("luaCallback", "");
|
||||
node.bubbleSlot = j.value("bubbleSlot", "");
|
||||
|
||||
if (j.contains("conditions") && j["conditions"].is_array()) {
|
||||
for (const auto& item : j["conditions"]) {
|
||||
|
||||
@@ -81,6 +81,10 @@ void DialogueRuntime::setOnCutsceneFadeInComplete(std::function<void(const std::
|
||||
onCutsceneFadeInComplete = std::move(cb);
|
||||
}
|
||||
|
||||
void DialogueRuntime::setOnBubbleSlotReady(std::function<void(const std::string&)> cb) {
|
||||
onBubbleSlotReady = std::move(cb);
|
||||
}
|
||||
|
||||
void DialogueRuntime::stop() {
|
||||
activeDialogue = nullptr;
|
||||
activeCutscene = nullptr;
|
||||
@@ -431,6 +435,9 @@ void DialogueRuntime::presentLine(const Node& node) {
|
||||
if (!node.luaCallback.empty() && onDialogueLineStarted) {
|
||||
onDialogueLineStarted(node.luaCallback);
|
||||
}
|
||||
if (!node.bubbleSlot.empty() && onBubbleSlotReady) {
|
||||
onBubbleSlotReady(node.bubbleSlot);
|
||||
}
|
||||
}
|
||||
|
||||
void DialogueRuntime::presentChoices(const Node& node) {
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
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 setOnBubbleSlotReady(std::function<void(const std::string&)> cb);
|
||||
void stop();
|
||||
|
||||
void update(int deltaMs);
|
||||
@@ -56,6 +57,7 @@ private:
|
||||
std::function<void(const std::string&)> onDialogueLineStarted;
|
||||
std::function<void(const std::string&)> onCutsceneLineStarted;
|
||||
std::function<void(const std::string&)> onCutsceneFadeInComplete;
|
||||
std::function<void(const std::string&)> onBubbleSlotReady;
|
||||
std::string activeCutsceneId;
|
||||
bool fadeInCallbackFired = false;
|
||||
|
||||
|
||||
@@ -140,6 +140,10 @@ void DialogueSystem::setOnCutsceneFadeInComplete(std::function<void(const std::s
|
||||
runtime.setOnCutsceneFadeInComplete(std::move(cb));
|
||||
}
|
||||
|
||||
void DialogueSystem::setOnBubbleSlotReady(std::function<void(const std::string&)> cb) {
|
||||
runtime.setOnBubbleSlotReady(std::move(cb));
|
||||
}
|
||||
|
||||
void DialogueSystem::stopDialogue() {
|
||||
runtime.stop();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
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 setOnBubbleSlotReady(std::function<void(const std::string&)> cb);
|
||||
void setOnDialogueAdvanced(std::function<void()> cb);
|
||||
void stopDialogue();
|
||||
|
||||
|
||||
@@ -96,6 +96,9 @@ struct Node {
|
||||
|
||||
// For CutsceneStart
|
||||
std::string cutsceneId;
|
||||
|
||||
// Name of the UI node (StaticImage) to reveal in the phone chat when this line is shown
|
||||
std::string bubbleSlot;
|
||||
};
|
||||
|
||||
struct DialogueDefinition {
|
||||
|
||||
@@ -24,4 +24,5 @@ namespace ZL {
|
||||
[&itemId](const Item& item) { return item.id == itemId; }) != items.end();
|
||||
}
|
||||
|
||||
|
||||
} // namespace ZL
|
||||
Reference in new issue
Block a user