Clean up UI, work on tutorial
This commit is contained in:
1 parent
cfbd29d5b1
commit
619de45221
79 files changed
+5122
-316
No files matched your search
+43
-10
@@ -342,6 +342,26 @@ namespace ZL
|
||||
if (locations["location_dorm"]->player)
|
||||
locations["location_dorm"]->player->onDeathAnimComplete = [this]() { startDarklandsTransition(); };
|
||||
|
||||
locations["location_dorm"]->onPlayerTaxiRequired = [this]() {
|
||||
menuManager.tutorialShowTaxiHint();
|
||||
};
|
||||
|
||||
menuManager.callTaxiFunc = [this]()
|
||||
{
|
||||
if (locations["location_dorm"])
|
||||
{
|
||||
locations["location_dorm"]->onPlayerTaxiRequired = nullptr;
|
||||
}
|
||||
|
||||
if (currentLocation)
|
||||
{
|
||||
if (currentLocation->teleportZones.size() > 0)
|
||||
{
|
||||
currentLocation->teleportZones[0].active = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Teleport callbacks: destination name and position come from the teleport zone data.
|
||||
auto teleportCallback = [this](const std::string& destName, const Eigen::Vector3f& destPos, float destRotY) {
|
||||
std::cout << "[TELEPORT] " << " -> " << destName << std::endl;
|
||||
@@ -388,9 +408,20 @@ namespace ZL
|
||||
};
|
||||
}
|
||||
|
||||
// Wire inventory item-pickup callback for tutorial step4/5 item tracking.
|
||||
renderer.textureManager.LoadFromPng("resources/w/ui/img/toast/item_received001.png", CONST_ZIP_FILE, true);
|
||||
renderer.textureManager.LoadFromPng("resources/w/ui/img/toast/item_removed001.png", CONST_ZIP_FILE, true);
|
||||
|
||||
|
||||
|
||||
// Wire inventory callbacks: tutorial tracking + toast notifications.
|
||||
inventory.onItemAdded = [this](const std::string& itemId) {
|
||||
menuManager.onItemPickedUp(itemId);
|
||||
const Item* item = ItemRegistry::instance().findById(itemId);
|
||||
if (item) menuManager.showToast("resources/w/ui/img/toast/item_received001.png", item->name);
|
||||
};
|
||||
inventory.onItemRemoved = [this](const std::string& itemId) {
|
||||
const Item* item = ItemRegistry::instance().findById(itemId);
|
||||
if (item) menuManager.showToast("resources/w/ui/img/toast/item_removed001.png", item->name);
|
||||
};
|
||||
|
||||
// Wire phone dialogue start function so MenuManager can trigger dialogues.
|
||||
@@ -431,9 +462,6 @@ namespace ZL
|
||||
if (currentLocation) currentLocation->dialogueSystem.skipCutscene();
|
||||
};
|
||||
|
||||
currentLocation = locations["location_dorm"];
|
||||
currentLocation->scriptEngine.callLocationEnterCallback();
|
||||
|
||||
std::cout << "Load resurces step 5" << std::endl;
|
||||
/*
|
||||
std::cout << "Load resurces step 12" << std::endl;
|
||||
@@ -478,6 +506,11 @@ namespace ZL
|
||||
std::cerr << "Failed to load UI: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
menuManager.startGameFunc = [this]() {
|
||||
currentLocation = locations["location_dorm"];
|
||||
currentLocation->scriptEngine.callLocationEnterCallback();
|
||||
};
|
||||
|
||||
// Wire HP-change callbacks so all player instances update the health bar HUD.
|
||||
for (auto& [name, loc] : locations) {
|
||||
if (loc->player) {
|
||||
@@ -564,10 +597,6 @@ namespace ZL
|
||||
CheckGlError(__FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// ??? Main menu???
|
||||
}
|
||||
drawUI();
|
||||
drawDarklandsFlash();
|
||||
}
|
||||
@@ -632,10 +661,12 @@ namespace ZL
|
||||
lastTickCount = newTickCount;
|
||||
|
||||
updateDarklandsFlash(delta);
|
||||
|
||||
menuManager.uiManager.update(static_cast<float>(delta));
|
||||
menuManager.topUiManager.update(static_cast<float>(delta));
|
||||
|
||||
if (!menuManager.isMainMenuOpen())
|
||||
menuManager.update(static_cast<float>(delta));
|
||||
|
||||
if (currentLocation)
|
||||
{
|
||||
currentLocation->update(delta);
|
||||
@@ -797,7 +828,7 @@ namespace ZL
|
||||
}
|
||||
break;
|
||||
case SDLK_f:
|
||||
currentLocation->dialogueSystem.startDialogue("dialog_start001");
|
||||
currentLocation->dialogueSystem.startDialogue("phone_night_aiperi001");
|
||||
|
||||
break;
|
||||
case SDLK_e:
|
||||
@@ -836,6 +867,8 @@ namespace ZL
|
||||
break;
|
||||
|
||||
case SDLK_k:
|
||||
x = x - 1;
|
||||
std::cout << "current x: " << x << std::endl;
|
||||
//y = y + 1;
|
||||
//std::cout << "current y: " << y << std::endl;
|
||||
//y = y - 0.002;
|
||||
|
||||
+3
-2
@@ -75,9 +75,10 @@ namespace ZL
|
||||
player->loadBinaryAnimation(AnimationState::ACTION_TO_DEATH, "resources/w/gg/new/gg_die001.anim", CONST_ZIP_FILE);
|
||||
player->loadBinaryAnimation(AnimationState::DEATH_IDLE, "resources/w/gg/new/gg_die_idle001.anim", CONST_ZIP_FILE);
|
||||
|
||||
player->weaponTexture = renderer.textureManager.LoadFromPng("resources/w/white.png", CONST_ZIP_FILE);
|
||||
player->weaponTexture = renderer.textureManager.LoadFromPng("resources/w/gg/Props.png", CONST_ZIP_FILE);
|
||||
player->weaponMesh.data = LoadFromTextFile02("resources/w/gg/knife002.txt", CONST_ZIP_FILE);
|
||||
player->weaponMesh.data.Scale(0.1f);
|
||||
player->weaponMesh.data.Move(Vector3f(0, -0.3, 0));
|
||||
player->weaponMesh.data.Scale(0.2f);
|
||||
player->weaponMesh.RefreshVBO();
|
||||
player->weaponAttachBoneName = "RightHand";
|
||||
player->weaponInitialRotation = Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitZ()).toRotationMatrix();
|
||||
|
||||
@@ -106,6 +106,8 @@ namespace ZL
|
||||
// Used by the tutorial system to detect the "tap to walk" gesture.
|
||||
std::function<void()> onPlayerFloorWalk;
|
||||
|
||||
std::function<void()> onPlayerTaxiRequired;
|
||||
|
||||
// Set by Game after setup(). Lua and onDeathAnimComplete call this to
|
||||
// request the transition without coupling Location to Game directly.
|
||||
std::function<bool()> requestDarklandsTransition;
|
||||
|
||||
+297
-11
@@ -100,20 +100,29 @@ namespace ZL {
|
||||
hudUniIntFullRoot = loadUiFromFile("resources/w/ui/hud_uni_int_full.json", renderer, zipFile);
|
||||
hudUniIntDarkFullRoot = loadUiFromFile("resources/w/ui/hud_uni_int_dark_full.json", renderer, zipFile);
|
||||
hudUniExtDarkRoot = loadUiFromFile("resources/w/ui/hud_uni_ext_dark.json", renderer, zipFile);
|
||||
|
||||
hudCutsceneRoot_ = loadUiFromFile("resources/w/ui/hud_cutscene.json", renderer, zipFile);
|
||||
hudTopHintRoot_ = loadUiFromFile("resources/w/ui/hud_top_hint01.json", renderer, zipFile);
|
||||
|
||||
phoneMainRoot = loadUiFromFile("resources/w/ui/screen_phone.json", renderer, zipFile);
|
||||
phoneMainHintARoot = loadUiFromFile("resources/w/ui/screen_phone_hint001.json", renderer, zipFile);
|
||||
phoneMainHintBRoot = loadUiFromFile("resources/w/ui/screen_phone_hint002.json", renderer, zipFile);
|
||||
phoneMainHintABRoot = loadUiFromFile("resources/w/ui/screen_phone_hint001_002.json", renderer, zipFile);
|
||||
|
||||
phoneBankRoot = loadUiFromFile("resources/w/ui/screen_phone_bank.json", renderer, zipFile);
|
||||
phoneVideoRoot = loadUiFromFile("resources/w/ui/screen_phone_video.json", renderer, zipFile);
|
||||
phoneMapDormRoot = loadUiFromFile("resources/w/ui/screen_phone_map_dorm.json", renderer, zipFile);
|
||||
phoneMapUniRoot = loadUiFromFile("resources/w/ui/screen_phone_map_uni.json", renderer, zipFile);
|
||||
phoneChatListRoot = loadUiFromFile("resources/w/ui/screen_phone_chat_list.json", renderer, zipFile);
|
||||
phoneChatListHintRoot = loadUiFromFile("resources/w/ui/screen_phone_chat_list_hint001.json", renderer, zipFile);
|
||||
phoneChat1Root = loadUiFromFile("resources/w/ui/screen_phone_chat1.json", renderer, zipFile);
|
||||
phoneChat2Root = loadUiFromFile("resources/w/ui/screen_phone_chat2.json", renderer, zipFile);
|
||||
phoneChat3Root = loadUiFromFile("resources/w/ui/screen_phone_chat3.json", renderer, zipFile);
|
||||
|
||||
newInventoryRoot = loadUiFromFile("resources/w/ui/screen_inventory.json", renderer, zipFile);
|
||||
questJournalRoot = loadUiFromFile("resources/w/ui/screen_journal.json", renderer, zipFile);
|
||||
newInventoryRoot = loadUiFromFile("resources/w/ui/screen_inventory.json", renderer, zipFile);
|
||||
questJournalRoot = loadUiFromFile("resources/w/ui/screen_journal.json", renderer, zipFile);
|
||||
mainMenuRoot = loadUiFromFile("resources/w/ui/screen_main_menu.json", renderer, zipFile);
|
||||
aboutScreenRoot = loadUiFromFile("resources/w/ui/screen_about.json", renderer, zipFile);
|
||||
|
||||
texObjectiveCompleted_ = renderer.textureManager.LoadFromPng("resources/w/ui/img/journal/quest_objective_completed.png", zipFile, true);
|
||||
texObjectiveBlank_ = renderer.textureManager.LoadFromPng("resources/w/ui/img/journal/quest_objective_blank.png", zipFile, true);
|
||||
@@ -122,6 +131,29 @@ namespace ZL {
|
||||
|
||||
questJournal.loadFromFile("resources/quests/quests.json", zipFile);
|
||||
|
||||
renderer.textureManager.LoadFromPng("resources/w/ui/img/toast/quest_new001.png", zipFile, true);
|
||||
renderer.textureManager.LoadFromPng("resources/w/ui/img/toast/quest_completed001.png", zipFile, true);
|
||||
renderer.textureManager.LoadFromPng("resources/w/ui/img/toast/quest_failed001.png", zipFile, true);
|
||||
|
||||
|
||||
questJournal.onQuestUnlocked = [this](const std::string& id) {
|
||||
const Quest::QuestState* q = questJournal.findQuest(id);
|
||||
if (q) showToast("resources/w/ui/img/toast/quest_new001.png", q->definition.title);
|
||||
};
|
||||
questJournal.onQuestCompleted = [this](const std::string& id) {
|
||||
const Quest::QuestState* q = questJournal.findQuest(id);
|
||||
if (q) showToast("resources/w/ui/img/toast/quest_completed001.png", q->definition.title);
|
||||
};
|
||||
questJournal.onQuestFailed = [this](const std::string& id) {
|
||||
const Quest::QuestState* q = questJournal.findQuest(id);
|
||||
if (q) showToast("resources/w/ui/img/toast/quest_failed001.png", q->definition.title);
|
||||
};
|
||||
/*
|
||||
questJournal.onObjectiveCompleted = [this](const std::string& id, const std::string&) {
|
||||
const Quest::QuestState* q = questJournal.findQuest(id);
|
||||
if (q) showToast("resources/w/ui/img/toast/objective_completed001.png", q->definition.title);
|
||||
};*/
|
||||
|
||||
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);
|
||||
@@ -134,12 +166,16 @@ namespace ZL {
|
||||
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();
|
||||
showMainMenu();
|
||||
}
|
||||
|
||||
void MenuManager::enterGameplay() {
|
||||
if (state == GameState::MainMenu && startGameFunc) startGameFunc();
|
||||
state = GameState::Gameplay;
|
||||
uiManager.replaceRoot(hudRoot);
|
||||
topUiManager.replaceRoot(hudTopHintRoot_);
|
||||
|
||||
|
||||
applyCurrentHealthBar();
|
||||
/*
|
||||
uiManager.setTextButtonCallback("inventory_button", [this](const std::string&) {
|
||||
@@ -156,6 +192,33 @@ namespace ZL {
|
||||
//openInventory()
|
||||
}
|
||||
|
||||
void MenuManager::showMainMenu() {
|
||||
state = GameState::MainMenu;
|
||||
uiManager.replaceRoot(mainMenuRoot);
|
||||
|
||||
uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) {
|
||||
enterGameplay();
|
||||
});
|
||||
uiManager.setTextButtonCallback("menuAboutButton", [this](const std::string&) {
|
||||
showAboutScreen();
|
||||
});
|
||||
|
||||
#ifndef EMSCRIPTEN
|
||||
uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) {
|
||||
Environment::exitGameLoop = true;
|
||||
});
|
||||
#else
|
||||
uiManager.setNodeVisible("menuExitButton", false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MenuManager::showAboutScreen() {
|
||||
uiManager.pushMenuFromSavedRoot(aboutScreenRoot);
|
||||
uiManager.setTextButtonCallback("aboutBackButton", [this](const std::string&) {
|
||||
uiManager.popMenu();
|
||||
});
|
||||
}
|
||||
|
||||
void MenuManager::openInventory() {
|
||||
state = GameState::Inventory;
|
||||
uiManager.pushMenuFromSavedRoot(newInventoryRoot);
|
||||
@@ -248,7 +311,9 @@ namespace ZL {
|
||||
void MenuManager::openQuestJournal() {
|
||||
state = GameState::QuestJournal;
|
||||
tutorialJournalScreenOpened = true;
|
||||
|
||||
uiManager.setNodeVisible("hint6b", false);
|
||||
uiManager.setNodeVisible("hint6barrow", false);
|
||||
uiManager.pushMenuFromSavedRoot(questJournalRoot);
|
||||
|
||||
uiManager.setButtonCallback("journalExitButton", [this](const std::string&) {
|
||||
@@ -294,10 +359,30 @@ namespace ZL {
|
||||
|
||||
void MenuManager::openPhoneScreen() {
|
||||
state = GameState::PhoneScreen;
|
||||
tutorialPhoneScreenOpened = true;
|
||||
uiManager.setNodeVisible("hint6a", false);
|
||||
uiManager.pushMenuFromSavedRoot(phoneMainRoot);
|
||||
|
||||
//uiManager.setNodeVisible("hint6a", false);
|
||||
//uiManager.setNodeVisible("hint6aarrow", false);
|
||||
|
||||
//tutorialPhoneScreenOpened = true;
|
||||
|
||||
|
||||
if (tutorialNeedOpenTaxiScreen && (tutorialPhoneChatScreenOpened == false))
|
||||
{
|
||||
uiManager.pushMenuFromSavedRoot(phoneMainHintABRoot);
|
||||
}
|
||||
else if (tutorialNeedOpenTaxiScreen)
|
||||
{
|
||||
uiManager.pushMenuFromSavedRoot(phoneMainHintBRoot);
|
||||
}
|
||||
else if (tutorialPhoneChatScreenOpened == false)
|
||||
{
|
||||
uiManager.pushMenuFromSavedRoot(phoneMainHintARoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
uiManager.pushMenuFromSavedRoot(phoneMainRoot);
|
||||
}
|
||||
|
||||
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
||||
closePhoneEntirely();
|
||||
});
|
||||
@@ -338,7 +423,16 @@ namespace ZL {
|
||||
}
|
||||
|
||||
void MenuManager::openPhoneMessenger() {
|
||||
uiManager.pushMenuFromSavedRoot(phoneChatListRoot);
|
||||
|
||||
if (tutorialPhoneChatScreenOpened)
|
||||
{
|
||||
uiManager.pushMenuFromSavedRoot(phoneChatListRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
uiManager.pushMenuFromSavedRoot(phoneChatListHintRoot);
|
||||
}
|
||||
//uiManager.pushMenuFromSavedRoot(phoneChatListRoot);
|
||||
refreshChatUnreadIndicators();
|
||||
|
||||
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
||||
@@ -430,6 +524,12 @@ namespace ZL {
|
||||
uiManager.popMenu();
|
||||
});
|
||||
uiManager.setButtonCallback("mapGo", [this](const std::string&) {
|
||||
tutorialNeedOpenTaxiScreen = false;
|
||||
|
||||
if (callTaxiFunc)
|
||||
{
|
||||
callTaxiFunc();
|
||||
}
|
||||
money_ -= 500;
|
||||
closePhoneEntirely();
|
||||
if (startDialogueFunc) startDialogueFunc("dialog_taxi002");
|
||||
@@ -439,6 +539,7 @@ namespace ZL {
|
||||
void MenuManager::openPhoneChatFromList(int chatIndex, std::shared_ptr<UiNode> chatRoot) {
|
||||
activeChatIndex_ = chatIndex;
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
tutorialPhoneChatScreenOpened = true;
|
||||
uiManager.pushMenuFromSavedRoot(chatRoot);
|
||||
|
||||
rebuildChatBubblesFromHistory(chatIndex);
|
||||
@@ -460,6 +561,14 @@ namespace ZL {
|
||||
activeChatIndex_ = -1;
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
uiManager.popMenu();
|
||||
if (tutorialPhoneChatScreenOpened)
|
||||
{
|
||||
uiManager.setNodeVisible("hint_m002", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
uiManager.setNodeVisible("hint_m002", true);
|
||||
}
|
||||
refreshChatUnreadIndicators();
|
||||
}
|
||||
|
||||
@@ -469,6 +578,32 @@ namespace ZL {
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
const int depth = uiManager.menuStackSize();
|
||||
for (int i = 0; i < depth; ++i) uiManager.popMenu();
|
||||
|
||||
if (tutorialNeedOpenTaxiScreen)
|
||||
{
|
||||
uiManager.setNodeVisible("hint7", true);
|
||||
uiManager.setNodeVisible("hint7arrow", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
uiManager.setNodeVisible("hint7", false);
|
||||
uiManager.setNodeVisible("hint7arrow", false);
|
||||
|
||||
if (tutorialPhoneChatScreenOpened)
|
||||
{
|
||||
uiManager.setNodeVisible("hint6a", false);
|
||||
uiManager.setNodeVisible("hint6aarrow", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
uiManager.setNodeVisible("hint6a", true);
|
||||
uiManager.setNodeVisible("hint6aarrow", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MenuManager::closePhoneScreenFromChat() {
|
||||
@@ -479,6 +614,21 @@ namespace ZL {
|
||||
closePhoneEntirely();
|
||||
}
|
||||
|
||||
void MenuManager::tutorialShowTaxiHint()
|
||||
{
|
||||
std::cout << "tutorialShowTaxiHint" << std::endl;
|
||||
tutorialNeedOpenTaxiScreen = true;
|
||||
if (state == GameState::Gameplay)
|
||||
{
|
||||
uiManager.setNodeVisible("hint6a", false);
|
||||
uiManager.setNodeVisible("hint6aarrow", false);
|
||||
|
||||
uiManager.setNodeVisible("hint7", true);
|
||||
uiManager.setNodeVisible("hint7arrow", true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Registers phoneButton / journalButton callbacks on the current HUD root
|
||||
// and hides any hints that have already been completed.
|
||||
// Called after every replaceRoot during step 5.
|
||||
@@ -498,9 +648,30 @@ namespace ZL {
|
||||
openQuestJournal();
|
||||
});
|
||||
}
|
||||
if (tutorialPhoneScreenOpened) uiManager.setNodeVisible("hint6a", false);
|
||||
if (tutorialJournalScreenOpened) uiManager.setNodeVisible("hint6b", false);
|
||||
if (tutorialPhoneScreenOpened && tutorialJournalScreenOpened) {
|
||||
if (tutorialNeedOpenTaxiScreen)
|
||||
{
|
||||
uiManager.setNodeVisible("hint6a", false);
|
||||
uiManager.setNodeVisible("hint6aarrow", false);
|
||||
|
||||
uiManager.setNodeVisible("hint7", true);
|
||||
uiManager.setNodeVisible("hint7arrow", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
uiManager.setNodeVisible("hint7", false);
|
||||
uiManager.setNodeVisible("hint7arrow", false);
|
||||
}
|
||||
|
||||
if (tutorialPhoneChatScreenOpened) {
|
||||
uiManager.setNodeVisible("hint6a", false);
|
||||
uiManager.setNodeVisible("hint6aarrow", false);
|
||||
}
|
||||
if (tutorialJournalScreenOpened)
|
||||
{
|
||||
uiManager.setNodeVisible("hint6b", false);
|
||||
uiManager.setNodeVisible("hint6barrow", false);
|
||||
}
|
||||
if (tutorialPhoneChatScreenOpened && tutorialJournalScreenOpened) {
|
||||
tutorialStep = TutorialStep::Step6;
|
||||
}
|
||||
}
|
||||
@@ -514,6 +685,8 @@ namespace ZL {
|
||||
uiManager.setButtonCallback("journalButton", [this](const std::string&) { openQuestJournal(); });
|
||||
if (uiManager.findButton("darklandsButton"))
|
||||
uiManager.setButtonCallback("darklandsButton", [this](const std::string&) { startDarklandsTransitionFunc(); });
|
||||
hideAllToastWidgets();
|
||||
applyToastsToUi();
|
||||
}
|
||||
|
||||
void MenuManager::onLocationChanged(const std::string& locationName) {
|
||||
@@ -541,6 +714,7 @@ namespace ZL {
|
||||
case TutorialStep::Step0:
|
||||
tutorialStep = TutorialStep::Step1;
|
||||
nextRoot = hudStep1Root;
|
||||
topUiManager.replaceRoot(nullptr);
|
||||
break;
|
||||
case TutorialStep::Step1:
|
||||
tutorialStep = TutorialStep::Step2;
|
||||
@@ -860,7 +1034,7 @@ namespace ZL {
|
||||
}
|
||||
uiManager.replaceRoot(root);
|
||||
applyCurrentHealthBar();
|
||||
setupGameplayHudCallbacks();
|
||||
setupGameplayHudCallbacks(); // already calls hideAllToastWidgets + applyToastsToUi
|
||||
}
|
||||
|
||||
void MenuManager::onPlayerStartedWalking()
|
||||
@@ -921,4 +1095,116 @@ namespace ZL {
|
||||
uiManager.setText("healthValue", hpText);
|
||||
}
|
||||
|
||||
// ---- Toast notification system ----
|
||||
|
||||
float MenuManager::ToastEntry::currentAlpha() const {
|
||||
switch (state) {
|
||||
case State::FadeIn: return min(timer / TOAST_FADE_MS, 1.0f);
|
||||
case State::Visible: return 1.0f;
|
||||
case State::FadeOut: return max(1.0f - timer / TOAST_FADE_MS, 0.0f);
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void MenuManager::showToast(const std::string& iconPath, const std::string& text) {
|
||||
if (activeToasts_.size() < 3) {
|
||||
ToastEntry e;
|
||||
e.iconPath = iconPath;
|
||||
e.text = text;
|
||||
activeToasts_.push_back(std::move(e));
|
||||
} else {
|
||||
toastQueue_.push_back({ iconPath, text });
|
||||
}
|
||||
applyToastsToUi();
|
||||
}
|
||||
|
||||
void MenuManager::updateToasts(float deltaMs) {
|
||||
if (activeToasts_.empty() && toastQueue_.empty()) return;
|
||||
|
||||
bool changed = false;
|
||||
|
||||
for (auto& e : activeToasts_) {
|
||||
e.timer += deltaMs;
|
||||
// Advance state machine
|
||||
if (e.state == ToastEntry::State::FadeIn && e.timer >= TOAST_FADE_MS) {
|
||||
e.state = ToastEntry::State::Visible;
|
||||
e.timer = 0.0f;
|
||||
changed = true;
|
||||
} else if (e.state == ToastEntry::State::Visible && e.timer >= TOAST_VISIBLE_MS) {
|
||||
e.state = ToastEntry::State::FadeOut;
|
||||
e.timer = 0.0f;
|
||||
changed = true;
|
||||
} else if (e.state == ToastEntry::State::FadeOut && e.timer >= TOAST_FADE_MS) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove finished entries (FadeOut complete)
|
||||
auto removed = std::remove_if(activeToasts_.begin(), activeToasts_.end(),
|
||||
[](const ToastEntry& e) {
|
||||
return e.state == ToastEntry::State::FadeOut && e.timer >= TOAST_FADE_MS;
|
||||
});
|
||||
if (removed != activeToasts_.end()) {
|
||||
activeToasts_.erase(removed, activeToasts_.end());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Pull queued messages into now-free slots (with FadeIn)
|
||||
while (!toastQueue_.empty() && activeToasts_.size() < 3) {
|
||||
ToastEntry e;
|
||||
e.iconPath = toastQueue_.front().iconPath;
|
||||
e.text = toastQueue_.front().text;
|
||||
toastQueue_.pop_front();
|
||||
activeToasts_.push_back(std::move(e));
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) applyToastsToUi();
|
||||
// Also refresh alpha smoothly every frame while any slot is fading
|
||||
else if (!activeToasts_.empty()) applyToastsToUi();
|
||||
}
|
||||
|
||||
void MenuManager::applyToastsToUi() {
|
||||
static const char* imgNames[3] = { "toast001", "toast002", "toast003" };
|
||||
static const char* txtNames[3] = { "toast001text", "toast002text", "toast003text" };
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (i < static_cast<int>(activeToasts_.size())) {
|
||||
const ToastEntry& e = activeToasts_[i];
|
||||
const float a = e.currentAlpha();
|
||||
|
||||
auto img = uiManager.findStaticImage(imgNames[i]);
|
||||
auto tv = uiManager.findTextView(txtNames[i]);
|
||||
|
||||
if (img) {
|
||||
img->texture = renderer.textureManager.LoadFromPng(e.iconPath, CONST_ZIP_FILE, true);
|
||||
img->alpha = a;
|
||||
}
|
||||
if (tv) {
|
||||
tv->color[3] = a;
|
||||
}
|
||||
uiManager.setText(txtNames[i], e.text);
|
||||
uiManager.setNodeVisible(imgNames[i], true);
|
||||
uiManager.setNodeVisible(txtNames[i], true);
|
||||
} else {
|
||||
uiManager.setNodeVisible(imgNames[i], false);
|
||||
uiManager.setNodeVisible(txtNames[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager::hideAllToastWidgets() {
|
||||
static const char* names[6] = {
|
||||
"toast001", "toast001text",
|
||||
"toast002", "toast002text",
|
||||
"toast003", "toast003text"
|
||||
};
|
||||
for (const char* name : names)
|
||||
uiManager.setNodeVisible(name, false);
|
||||
}
|
||||
|
||||
void MenuManager::update(float deltaMs) {
|
||||
updateToasts(deltaMs);
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
+54
-1
@@ -6,6 +6,7 @@
|
||||
#include "items/Item.h"
|
||||
#include "quest/QuestJournal.h"
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
@@ -14,6 +15,8 @@ namespace ZL {
|
||||
extern const char* CONST_ZIP_FILE;
|
||||
|
||||
enum class GameState {
|
||||
MainMenu,
|
||||
About,
|
||||
Gameplay,
|
||||
Inventory,
|
||||
QuestJournal,
|
||||
@@ -56,21 +59,28 @@ namespace ZL {
|
||||
bool isInventoryOpen() const { return state == GameState::Inventory; }
|
||||
bool isQuestJournalOpen() const { return state == GameState::QuestJournal; }
|
||||
|
||||
void showMainMenu();
|
||||
bool isMainMenuOpen() const { return state == GameState::MainMenu; }
|
||||
|
||||
void openPhoneScreen();
|
||||
void closePhoneScreen();
|
||||
bool isPhoneScreenOpen() const { return state == GameState::PhoneScreen; }
|
||||
void closePhoneEntirely();
|
||||
|
||||
void tutorialShowTaxiHint();
|
||||
|
||||
void setChatUnread(int chatIndex, bool unread);
|
||||
|
||||
void spendMoney(int amount);
|
||||
int getMoney() const { return money_; }
|
||||
|
||||
std::function<void()> startGameFunc;
|
||||
std::function<void(const std::string&)> startDialogueFunc;
|
||||
std::function<void()> startDarklandsTransitionFunc;
|
||||
std::function<void()> startNightTransitionFunc;
|
||||
std::function<void(int)> chatOpenCallback;
|
||||
std::function<void()> skipCutsceneFunc;
|
||||
std::function<void()> callTaxiFunc;
|
||||
|
||||
// Called when a chat message bubble should be shown (text + direction)
|
||||
void onChatBubbleReady(const std::string& text, bool incoming);
|
||||
@@ -90,6 +100,10 @@ namespace ZL {
|
||||
void onCutsceneFinished();
|
||||
bool cutsceneHudActive_ = false;
|
||||
|
||||
// Toast notification system
|
||||
void showToast(const std::string& iconPath, const std::string& text);
|
||||
void update(float deltaMs);
|
||||
|
||||
TutorialStep tutorialStep = TutorialStep::Step0;
|
||||
|
||||
protected:
|
||||
@@ -115,6 +129,27 @@ namespace ZL {
|
||||
void closePhoneScreenFromChat();
|
||||
void applyUniIntHud();
|
||||
void rebuildChatBubblesFromHistory(int chatIndex);
|
||||
void showAboutScreen();
|
||||
|
||||
// Toast internals
|
||||
struct ToastEntry {
|
||||
std::string iconPath;
|
||||
std::string text;
|
||||
float timer = 0.0f;
|
||||
enum class State { FadeIn, Visible, FadeOut } state = State::FadeIn;
|
||||
float currentAlpha() const;
|
||||
};
|
||||
struct ToastMessage { std::string iconPath; std::string text; };
|
||||
|
||||
void updateToasts(float deltaMs);
|
||||
void applyToastsToUi();
|
||||
void hideAllToastWidgets();
|
||||
|
||||
std::vector<ToastEntry> activeToasts_;
|
||||
std::deque<ToastMessage> toastQueue_;
|
||||
|
||||
static constexpr float TOAST_FADE_MS = 1000.0f;
|
||||
static constexpr float TOAST_VISIBLE_MS = 2000.0f;
|
||||
|
||||
GameState state = GameState::Gameplay;
|
||||
Inventory* inventory = nullptr;
|
||||
@@ -128,8 +163,13 @@ namespace ZL {
|
||||
|
||||
bool tutorialPhonePickedUp = false;
|
||||
bool tutorialJournalPickedUp = false;
|
||||
bool tutorialPhoneScreenOpened = false;
|
||||
//bool tutorialPhoneScreenOpened = false;
|
||||
bool tutorialPhoneChatScreenOpened = false;
|
||||
bool tutorialJournalScreenOpened = false;
|
||||
bool tutorialMessengerScreenOpened = false;
|
||||
//bool tutorialTaxiScreenOpened = false;
|
||||
|
||||
bool tutorialNeedOpenTaxiScreen = false;
|
||||
|
||||
std::shared_ptr<UiNode> hudRoot;
|
||||
std::shared_ptr<UiNode> hudStep1Root;
|
||||
@@ -147,17 +187,30 @@ namespace ZL {
|
||||
std::shared_ptr<UiNode> hudUniIntStep13Root;
|
||||
std::shared_ptr<UiNode> hudUniIntDarkFullRoot;
|
||||
std::shared_ptr<UiNode> hudUniExtDarkRoot;
|
||||
|
||||
|
||||
std::shared_ptr<UiNode> hudCutsceneRoot_;
|
||||
std::shared_ptr<UiNode> hudTopHintRoot_;
|
||||
|
||||
|
||||
std::shared_ptr<UiNode> phoneMainRoot;
|
||||
std::shared_ptr<UiNode> phoneMainHintARoot;
|
||||
std::shared_ptr<UiNode> phoneMainHintBRoot;
|
||||
std::shared_ptr<UiNode> phoneMainHintABRoot;
|
||||
|
||||
std::shared_ptr<UiNode> phoneBankRoot;
|
||||
std::shared_ptr<UiNode> phoneVideoRoot;
|
||||
std::shared_ptr<UiNode> phoneMapDormRoot;
|
||||
std::shared_ptr<UiNode> phoneMapUniRoot;
|
||||
std::shared_ptr<UiNode> phoneChatListRoot;
|
||||
std::shared_ptr<UiNode> phoneChatListHintRoot;
|
||||
std::shared_ptr<UiNode> phoneChat1Root;
|
||||
std::shared_ptr<UiNode> phoneChat2Root;
|
||||
std::shared_ptr<UiNode> phoneChat3Root;
|
||||
|
||||
std::shared_ptr<UiNode> mainMenuRoot;
|
||||
std::shared_ptr<UiNode> aboutScreenRoot;
|
||||
|
||||
std::shared_ptr<UiNode> newInventoryRoot;
|
||||
std::shared_ptr<UiNode> questJournalRoot;
|
||||
|
||||
|
||||
@@ -657,6 +657,18 @@ namespace ZL {
|
||||
return this_impl->questJournal->setActiveObjective(questId, index);
|
||||
});
|
||||
|
||||
api.set_function("call_tutorial_taxi_required",
|
||||
[game]() -> void {
|
||||
if (game->onPlayerTaxiRequired)
|
||||
{
|
||||
game->onPlayerTaxiRequired();
|
||||
game->onPlayerTaxiRequired = nullptr;
|
||||
}
|
||||
else {
|
||||
std::cerr << "[script] call_tutorial_taxi_required: function is empty\n";
|
||||
}
|
||||
});
|
||||
|
||||
lua.script_file(scriptPath);
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -402,15 +402,16 @@ namespace ZL {
|
||||
void UiStaticImage::draw(Renderer& renderer) const {
|
||||
if (!texture) return;
|
||||
|
||||
const float alpha = (fadeInEnabled && fadeInDurationMs > 0.0f)
|
||||
const float fadeAlpha = (fadeInEnabled && fadeInDurationMs > 0.0f)
|
||||
? std::clamp(fadeInElapsedMs / fadeInDurationMs, 0.0f, 1.0f)
|
||||
: 1.0f;
|
||||
const float finalAlpha = alpha * fadeAlpha;
|
||||
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
renderer.RenderUniform1f("uAlpha", alpha);
|
||||
renderer.RenderUniform1f("uAlpha", finalAlpha);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(mesh);
|
||||
if (alpha < 1.0f) {
|
||||
if (finalAlpha < 1.0f) {
|
||||
renderer.RenderUniform1f("uAlpha", 1.0f); // restore for subsequent draws
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +236,9 @@ namespace ZL {
|
||||
|
||||
VertexRenderStruct mesh;
|
||||
|
||||
// Externally controllable opacity (0=transparent, 1=opaque). Multiplies with fade-in.
|
||||
float alpha = 1.0f;
|
||||
|
||||
// Fade-in on first display (triggers each time the containing UI is shown)
|
||||
bool fadeInEnabled = false;
|
||||
float fadeInDurationMs = 1000.0f;
|
||||
|
||||
@@ -19,9 +19,14 @@ bool DialogueOverlay::init(Renderer& renderer, const std::string& zipFile) {
|
||||
zipFilename = zipFile;
|
||||
|
||||
textboxTexture = renderer.textureManager.LoadFromPng("resources/dialogue/textbox_bg.png", zipFile);
|
||||
choiceMainTexture = renderer.textureManager.LoadFromPng("resources/dialogue/choice_main.png", zipFile);
|
||||
textboxChoiceTexture = renderer.textureManager.LoadFromPng("resources/dialogue/DialogChoice001.png", zipFile);
|
||||
/*choiceMainTexture = renderer.textureManager.LoadFromPng("resources/dialogue/choice_main.png", zipFile);
|
||||
choiceOptionalTexture = renderer.textureManager.LoadFromPng("resources/dialogue/choice_optional.png", zipFile);
|
||||
choiceSelectedTexture = renderer.textureManager.LoadFromPng("resources/dialogue/choice_selected.png", zipFile);
|
||||
*/
|
||||
choiceMainTexture = renderer.textureManager.LoadFromPng("resources/dialogue/ChoiceNormal001.png", zipFile);
|
||||
choiceOptionalTexture = renderer.textureManager.LoadFromPng("resources/dialogue/ChoiceNormal001.png", zipFile);
|
||||
choiceSelectedTexture = renderer.textureManager.LoadFromPng("resources/dialogue/ChoiceSelected001.png", zipFile);
|
||||
|
||||
nameRenderer = std::make_unique<TextRenderer>();
|
||||
bodyRenderer = std::make_unique<TextRenderer>();
|
||||
@@ -57,6 +62,7 @@ void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
|
||||
|
||||
UiRect portraitRect{ 24.0f + 90, 24.0f + 16, 176.0f, 176.0f };
|
||||
UiRect textboxRect{ 30.f, -48.f, 1222.f, 340.0f };
|
||||
UiRect textboxChoiceRect{ 30.f, -48.f, 1200.f, 619.5f };
|
||||
|
||||
lastDialogueAdvanceRect = { portraitRect.x, portraitRect.y, textboxRect.x + textboxRect.w - portraitRect.x, textboxRect.h };
|
||||
|
||||
@@ -69,6 +75,11 @@ void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
|
||||
textboxQuad.rebuild(textboxRect);
|
||||
}
|
||||
|
||||
if (!textboxChoiceQuad.initialized || textboxChoiceQuad.rect.w != textboxChoiceRect.w || textboxChoiceQuad.rect.h != textboxChoiceRect.h ||
|
||||
textboxChoiceQuad.rect.x != textboxChoiceRect.x || textboxChoiceQuad.rect.y != textboxChoiceRect.y) {
|
||||
textboxChoiceQuad.rebuild(textboxChoiceRect);
|
||||
}
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
renderer.shaderManager.PushShader(defaultShaderName);
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
@@ -77,45 +88,77 @@ void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
|
||||
renderer.LoadIdentity();
|
||||
renderer.RenderUniform1f("uAlpha", 1.0f);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, textboxTexture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(textboxQuad.mesh);
|
||||
if (model.mode == PresentationMode::Choice)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, textboxTexture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(textboxChoiceQuad.mesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, textboxTexture->getTexID());
|
||||
renderer.DrawVertexRenderStruct(textboxQuad.mesh);
|
||||
}
|
||||
|
||||
{
|
||||
renderer.PushMatrix();
|
||||
|
||||
if (model.mode == PresentationMode::Choice)
|
||||
{
|
||||
renderer.TranslateMatrix(Vector3f{ 0, 230, 0 });
|
||||
}
|
||||
|
||||
auto portrait = loadTextureCached(model.portraitPath);
|
||||
if (portrait) {
|
||||
glBindTexture(GL_TEXTURE_2D, portrait->getTexID());
|
||||
renderer.DrawVertexRenderStruct(portraitQuad.mesh);
|
||||
}
|
||||
renderer.PopMatrix();
|
||||
}
|
||||
|
||||
renderer.PopMatrix();
|
||||
renderer.PopProjectionMatrix();
|
||||
renderer.shaderManager.PopShader();
|
||||
|
||||
const float nameX = 312;
|
||||
|
||||
const float nameX = 312+8;
|
||||
const float nameY = 232 - 38.0f;
|
||||
const float bodyX = 312;
|
||||
const float bodyX = 312+8;
|
||||
const float bodyY = 232 - 78.0f;
|
||||
|
||||
lastChoiceRects.clear();
|
||||
|
||||
if (!model.speaker.empty()) {
|
||||
nameRenderer->drawText(model.speaker, nameX, nameY, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
|
||||
|
||||
if (model.mode == PresentationMode::Choice)
|
||||
{
|
||||
nameRenderer->drawText(model.speaker, nameX+x, nameY+230, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
nameRenderer->drawText(model.speaker, nameX+x, nameY, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
|
||||
}
|
||||
}
|
||||
|
||||
const float bodyMaxWidthPx = W - nameX - 48.f - ZL::x - 60.f;
|
||||
const float bodyMaxWidthPx = W - nameX - 48.f - 60.f;
|
||||
const std::string wrappedBody = wrapTextToWidth(model.visibleText, *bodyRenderer, bodyMaxWidthPx, 1.0f);
|
||||
bodyRenderer->drawText(wrappedBody, bodyX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
|
||||
lastChoiceRects.clear();
|
||||
|
||||
if (model.mode == PresentationMode::Choice) {
|
||||
const float choiceStartY = textboxRect.y + 56.0f;
|
||||
const float choiceHeight = 30.0f;
|
||||
const float choiceSpacing = 8.0f;
|
||||
const float choiceWidth = textboxRect.w - 48.0f;
|
||||
const float choiceStartY = 270.f-35;
|
||||
const float choiceHeight = 121.f*0.9;
|
||||
const float choiceSpacing = -30.f;
|
||||
const float choiceWidth = 913.f * 0.9;
|
||||
|
||||
//UiRect textboxRect{ 30.f, -48.f, 1222.f, 340.0f };
|
||||
|
||||
|
||||
if (choiceQuads.size() < model.choices.size()) {
|
||||
choiceQuads.resize(model.choices.size());
|
||||
}
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
renderer.shaderManager.PushShader(defaultShaderName);
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
renderer.PushProjectionMatrix(0.0f, W, 0.0f, Environment::projectionHeight, -10.0f, 10.0f);
|
||||
@@ -124,7 +167,7 @@ void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
|
||||
|
||||
for (size_t i = 0; i < model.choices.size(); ++i) {
|
||||
const float cy = choiceStartY + (choiceHeight + choiceSpacing) * static_cast<float>(model.choices.size() - 1 - i);
|
||||
UiRect rect{ textboxRect.x + 20.0f, cy, choiceWidth, choiceHeight };
|
||||
UiRect rect{ 300.f, cy, choiceWidth, choiceHeight };
|
||||
lastChoiceRects.push_back(rect);
|
||||
choiceQuads[i].rebuild(rect);
|
||||
|
||||
@@ -149,7 +192,7 @@ void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
|
||||
|
||||
const std::string wrappedChoice = wrapTextToWidth(model.choices[i].text, *choiceRenderer, rect.w - 28.0f, 1.0f);
|
||||
choiceRenderer->drawText(
|
||||
wrappedChoice, rect.x + 14.0f, rect.y + 9.0f, 1.0f, false,
|
||||
wrappedChoice, rect.x + 32.0f, rect.y + 50.f, 1.5f, false,
|
||||
isHighlighted ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ private:
|
||||
std::string zipFilename;
|
||||
|
||||
std::shared_ptr<Texture> textboxTexture;
|
||||
std::shared_ptr<Texture> textboxChoiceTexture;
|
||||
|
||||
std::shared_ptr<Texture> choiceMainTexture;
|
||||
std::shared_ptr<Texture> choiceOptionalTexture;
|
||||
std::shared_ptr<Texture> choiceSelectedTexture;
|
||||
@@ -42,6 +44,7 @@ private:
|
||||
|
||||
UiQuad portraitQuad;
|
||||
UiQuad textboxQuad;
|
||||
UiQuad textboxChoiceQuad;
|
||||
mutable std::vector<UiQuad> choiceQuads;
|
||||
|
||||
std::shared_ptr<Texture> loadTextureCached(const std::string& path);
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace ZL {
|
||||
[&itemId](const Item& item) { return item.id == itemId; });
|
||||
if (it != items.end()) {
|
||||
std::cout << "Item removed from inventory: " << it->name << std::endl;
|
||||
if (onItemRemoved) onItemRemoved(itemId);
|
||||
items.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -31,8 +31,9 @@ namespace ZL {
|
||||
void clear() { items.clear(); }
|
||||
size_t getCount() const { return items.size(); }
|
||||
|
||||
// Called whenever an item is added; receives the item id.
|
||||
// Called whenever an item is added/removed; receives the item id.
|
||||
std::function<void(const std::string&)> onItemAdded;
|
||||
std::function<void(const std::string&)> onItemRemoved;
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
@@ -113,16 +113,21 @@ bool QuestJournal::unlockQuest(const std::string& questId) {
|
||||
if (!quest) return false;
|
||||
if (quest->status == QuestStatus::Hidden) {
|
||||
quest->status = QuestStatus::Available;
|
||||
if (onQuestUnlocked) onQuestUnlocked(questId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QuestJournal::completeQuest(const std::string& questId) {
|
||||
return setStatus(questId, QuestStatus::Completed);
|
||||
bool changed = setStatus(questId, QuestStatus::Completed);
|
||||
if (changed && onQuestCompleted) onQuestCompleted(questId);
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool QuestJournal::failQuest(const std::string& questId) {
|
||||
return setStatus(questId, QuestStatus::Failed);
|
||||
bool changed = setStatus(questId, QuestStatus::Failed);
|
||||
if (changed && onQuestFailed) onQuestFailed(questId);
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool QuestJournal::setObjectiveCompleted(const std::string& questId, const std::string& objectiveId, bool completed) {
|
||||
@@ -133,13 +138,19 @@ bool QuestJournal::setObjectiveCompleted(const std::string& questId, const std::
|
||||
if (objective.id == objectiveId) {
|
||||
objective.completed = completed;
|
||||
|
||||
if (completed) {
|
||||
if (onObjectiveCompleted) onObjectiveCompleted(questId, objectiveId);
|
||||
}
|
||||
|
||||
if (completed && quest->definition.autoComplete && quest->status == QuestStatus::Available) {
|
||||
const bool allDone = std::all_of(
|
||||
quest->definition.objectives.begin(),
|
||||
quest->definition.objectives.end(),
|
||||
[](const QuestObjective& o) { return o.completed; });
|
||||
if (allDone)
|
||||
if (allDone) {
|
||||
quest->status = QuestStatus::Completed;
|
||||
if (onQuestCompleted) onQuestCompleted(questId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace ZL::Quest {
|
||||
|
||||
@@ -11,6 +12,12 @@ class QuestJournal {
|
||||
public:
|
||||
bool loadFromFile(const std::string& path, const std::string& zipFile = "");
|
||||
|
||||
// Event callbacks — set by the owner (MenuManager) to receive notifications.
|
||||
std::function<void(const std::string& questId)> onQuestUnlocked;
|
||||
std::function<void(const std::string& questId)> onQuestCompleted;
|
||||
std::function<void(const std::string& questId)> onQuestFailed;
|
||||
std::function<void(const std::string& questId, const std::string& objectiveId)> onObjectiveCompleted;
|
||||
|
||||
bool unlockQuest(const std::string& questId);
|
||||
bool completeQuest(const std::string& questId);
|
||||
bool failQuest(const std::string& questId);
|
||||
|
||||
@@ -471,7 +471,7 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
|
||||
|
||||
r->DrawVertexRenderStruct(cached.mesh);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
//glDisable(GL_BLEND);
|
||||
|
||||
r->shaderManager.PopShader();
|
||||
|
||||
|
||||
Reference in new issue
Block a user