working on campaign

This commit is contained in:
Vladislav Khorev committed 2026-10-23 22:47:55 +06:00
1 parent 9fa120d4ad
commit d38a75ea72
24 files changed
+929 -410

No files matched your search

+61 -61
View File
@@ -2,6 +2,7 @@
#include "Game.h"
#include "Localization.h"
#include "items/ItemRegistry.h"
#include "Campaign.h"
#include "render/TextRenderer.h"
#include "utils/Utils.h"
#include <iostream>
@@ -323,6 +324,7 @@ namespace FRG {
settingsGraphicsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings_graphics.json", zipFile);
languageScreenRoot = loadLocalizedUi("resources/w/ui/screen_language.json", zipFile);
loadSavedGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_load.json", zipFile);
campaignScreenRoot = loadLocalizedUi("resources/w/ui/screen_campaign.json", zipFile);
saveGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_save.json", zipFile);
}
@@ -337,7 +339,7 @@ namespace FRG {
texItemSelected_ = renderer.textureManager.LoadFromPng("resources/w/ui/img/journal/ButtonBkg001.png", zipFile, true);
texItemTransparent_ = renderer.textureManager.LoadFromPng("resources/w/ui/img/journal/ButtonBkgTransparent001.png", zipFile, true);
gameState_.questJournal.loadFromFile("resources/config/quests.json", zipFile);
// Quest definitions are loaded per campaign by Game (see Game::activateCampaign).
toastTextureQuestNew = loadLocalizedTexture("resources/w/ui/img/toast/quest_new001.png", zipFile);
toastTextureQuestCompleted = loadLocalizedTexture("resources/w/ui/img/toast/quest_completed001.png", zipFile);
@@ -376,18 +378,19 @@ namespace FRG {
showMainMenu();
}
void MenuManager::startNewGame(const std::string& campaignId) {
// The reset runs on a later main-thread task, which then shows the HUD itself;
// the main menu stays up until then. Showing the HUD now would draw one frame
// from the previous game's state (e.g. its phone/journal buttons).
if (startGameFunc) startGameFunc(campaignId);
}
void MenuManager::enterGameplay(bool isFromLoad) {
if (isFromLoad) {
uiManager.clearMenuStack();
topUiManager.clearMenuStack();
}
// A new game resets the state on a later main-thread task, which then shows the
// HUD itself; the main menu stays up until then. Showing the HUD now would draw
// one frame from the previous game's state (e.g. its phone/journal buttons).
if (!isFromLoad && uiState_ == GameUiState::MainMenu && startGameFunc)
startGameFunc();
else
showGameplayHud();
showGameplayHud();
}
std::shared_ptr<UiNode> MenuManager::gameplayHudRoot() const {
@@ -472,8 +475,7 @@ namespace FRG {
uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
enterGameplay();
showCampaignScreen();
});
uiManager.setTextButtonCallback("menuAboutButton", [this](const std::string&) {
showAboutScreen();
@@ -534,8 +536,7 @@ namespace FRG {
uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
enterGameplay();
showCampaignScreen();
});
uiManager.setTextButtonCallback("menuAboutButton", [this](const std::string&) {
showAboutScreen();
@@ -1034,7 +1035,8 @@ namespace FRG {
steps.push_back([this]() {
ItemRegistry::instance().loadFromJson("resources/config/items.json", CONST_ZIP_FILE);
gameState_.questJournal.reloadDefinitionsPreservingState("resources/config/quests.json", CONST_ZIP_FILE);
if (const CampaignConfig* campaign = CampaignRegistry::instance().find(gameState_.campaignId))
gameState_.questJournal.reloadDefinitionsPreservingState(campaign->questsPath, CONST_ZIP_FILE);
});
steps.push_back([this]() {
@@ -1074,22 +1076,7 @@ namespace FRG {
for (int i = 0; i < 4; ++i) {
int slot = i + 1;
if (getSlotInfoFunc) {
SaveSlotInfo info = getSlotInfoFunc(slot);
std::string emptyText;
if (FRG::g_currentLanguage == FRG::Language::Russian)
{
emptyText = EMPTY_LANGUAGE_RU;
}
else if (FRG::g_currentLanguage == FRG::Language::English)
{
emptyText = EMPTY_LANGUAGE_EN;
}
std::string label = info.empty
? emptyText
: localizedLocationName[info.locationName][FRG::g_currentLanguage] + " " + info.savedAt;
uiManager.setTextButtonText(kSlotButtons[i], label);
uiManager.setTextButtonText(kSlotButtons[i], slotLabel(getSlotInfoFunc(slot)));
}
uiManager.setTextButtonCallback(kSlotButtons[i], [this, slot](const std::string&) {
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
@@ -1099,6 +1086,47 @@ namespace FRG {
}
}
std::string MenuManager::slotLabel(const SaveSlotInfo& info) {
if (info.empty)
return FRG::g_currentLanguage == FRG::Language::English ? EMPTY_LANGUAGE_EN : EMPTY_LANGUAGE_RU;
std::string label = localizedLocationName[info.locationName][FRG::g_currentLanguage] + " " + info.savedAt;
// Name the campaign unless it's the main game, so saves of different campaigns are told apart.
const CampaignConfig* campaign = CampaignRegistry::instance().find(info.campaignId);
if (campaign && campaign->id != CampaignRegistry::kDefaultCampaignId)
label = campaign->title(FRG::g_currentLanguage) + ": " + label;
return label;
}
void MenuManager::showCampaignScreen()
{
uiManager.pushMenuFromSavedRoot(campaignScreenRoot);
uiManager.setTextButtonCallback("campaignBackButton", [this](const std::string&) {
uiManager.popMenu();
uiManager.updateAllLayouts();
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
});
// One button per campaign in campaigns.json order; spare buttons are hidden.
static const char* kCampaignButtons[4] = {
"campaignSlot1Button", "campaignSlot2Button", "campaignSlot3Button", "campaignSlot4Button"
};
const auto& campaigns = CampaignRegistry::instance().all();
for (int i = 0; i < 4; ++i) {
const bool used = i < static_cast<int>(campaigns.size());
uiManager.setNodeVisible(kCampaignButtons[i], used);
if (!used) continue;
const std::string campaignId = campaigns[i].id;
uiManager.setTextButtonText(kCampaignButtons[i], campaigns[i].title(FRG::g_currentLanguage));
uiManager.setTextButtonCallback(kCampaignButtons[i], [this, campaignId](const std::string&) {
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
uiManager.popMenu();
startNewGame(campaignId);
});
}
}
void MenuManager::showSaveGameScreen()
{
uiManager.pushMenuFromSavedRoot(saveGameScreenRoot);
@@ -1115,42 +1143,12 @@ namespace FRG {
int slot = i + 1;
const char* buttonName = kSlotButtons[i];
if (getSlotInfoFunc) {
SaveSlotInfo info = getSlotInfoFunc(slot);
std::string emptyText;
if (FRG::g_currentLanguage == FRG::Language::Russian)
{
emptyText = EMPTY_LANGUAGE_RU;
}
else if (FRG::g_currentLanguage == FRG::Language::English)
{
emptyText = EMPTY_LANGUAGE_EN;
}
std::string label = info.empty
? emptyText
: localizedLocationName[info.locationName][FRG::g_currentLanguage] + " " + info.savedAt;
uiManager.setTextButtonText(buttonName, label);
uiManager.setTextButtonText(buttonName, slotLabel(getSlotInfoFunc(slot)));
}
uiManager.setTextButtonCallback(buttonName, [this, slot, buttonName](const std::string&) {
if (onSaveGame) onSaveGame(slot);
if (getSlotInfoFunc) {
SaveSlotInfo info = getSlotInfoFunc(slot);
std::string emptyText;
if (FRG::g_currentLanguage == FRG::Language::Russian)
{
emptyText = EMPTY_LANGUAGE_RU;
}
else if (FRG::g_currentLanguage == FRG::Language::English)
{
emptyText = EMPTY_LANGUAGE_EN;
}
std::string label = info.empty
? emptyText
: localizedLocationName[info.locationName][FRG::g_currentLanguage] + " " + info.savedAt;
uiManager.setTextButtonText(buttonName, label);
uiManager.setTextButtonText(buttonName, slotLabel(getSlotInfoFunc(slot)));
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
}
});
@@ -2142,6 +2140,8 @@ namespace FRG {
void MenuManager::onEnemyKilledInUniInterior()
{
// Every location's enemies report here; only the university interior drives the tutorial.
if (gameState_.currentLocationName != "uni_interior") return;
// The last tutorial step; like the attack hint, also ends the earlier darklands hints.
if (gameState_.tutorialStep >= TutorialStep::DarklandsHealth)
gameState_.tutorialStep = TutorialStep::Completed;