Working on loading
This commit is contained in:
1 parent
d38a75ea72
commit
9b71ee447a
11 files changed
+268
-55
No files matched your search
+124
-46
@@ -327,18 +327,10 @@ namespace FRG
|
||||
Character::nameTranslations.loadFromFile("resources/config/translate_names.json");
|
||||
});
|
||||
|
||||
// Every campaign's locations are built up front (one loading step per location),
|
||||
// so starting or loading any campaign later only switches the active set.
|
||||
// Campaigns are only listed here; their locations are built on demand when the
|
||||
// player starts or loads one (see loadCampaignThen). MenuManager::setup() needs
|
||||
// the list to preload each campaign's loading screen.
|
||||
CampaignRegistry::instance().loadFromJson("resources/config/campaigns.json", CONST_ZIP_FILE);
|
||||
loadSteps.push([this]() {
|
||||
gameState.globalFloats["player_hp"] = 200; // read by location scripts as they load
|
||||
});
|
||||
for (const CampaignConfig& campaign : CampaignRegistry::instance().all()) {
|
||||
queueCampaignBuildSteps(campaign);
|
||||
}
|
||||
loadSteps.push([this]() {
|
||||
activateCampaign(CampaignRegistry::kDefaultCampaignId);
|
||||
});
|
||||
|
||||
loadSteps.push([this]() {
|
||||
// Wire inventory callbacks: tutorial tracking + toast notifications.
|
||||
@@ -393,17 +385,28 @@ namespace FRG
|
||||
}
|
||||
|
||||
menuManager.startGameFunc = [this](const std::string& campaignId) {
|
||||
mainThreadHandler.EnqueueMainThreadTask([this, campaignId]() {
|
||||
activateCampaign(campaignId);
|
||||
performResetToInitialState();
|
||||
applyTutorialSetting();
|
||||
const CampaignConfig& campaign = activeCampaign();
|
||||
gameState.currentLocationName = campaign.startLocation;
|
||||
menuManager.showGameplayHud(); // first HUD frame drawn from the fresh state
|
||||
if (currentLocation()) currentLocation()->scriptEngine.callLocationEnterCallback();
|
||||
audioPlayer->crossFadeMusicAsync(campaign.startMusic);
|
||||
menuManager.lastMusicTrack = campaign.startMusic;
|
||||
});
|
||||
auto start = [this, campaignId]() {
|
||||
loadCampaignThen(campaignId, [this]() {
|
||||
performResetToInitialState();
|
||||
applyTutorialSetting();
|
||||
const CampaignConfig& campaign = activeCampaign();
|
||||
gameState.currentLocationName = campaign.startLocation;
|
||||
menuManager.showGameplayHud(); // first HUD frame drawn from the fresh state
|
||||
if (currentLocation()) currentLocation()->scriptEngine.callLocationEnterCallback();
|
||||
audioPlayer->crossFadeMusicAsync(campaign.startMusic);
|
||||
menuManager.lastMusicTrack = campaign.startMusic;
|
||||
});
|
||||
};
|
||||
if (campaignLocations.count(campaignId)) {
|
||||
// Already loaded: the reset runs right away, so keep it off the menu
|
||||
// button's call stack.
|
||||
mainThreadHandler.EnqueueMainThreadTask(start);
|
||||
} else {
|
||||
// Not loaded: this only shows the loading screen and queues the build
|
||||
// steps, so start it now -- deferring it would draw one more frame of
|
||||
// the main menu before the loading screen appears.
|
||||
start();
|
||||
}
|
||||
};
|
||||
|
||||
menuManager.onSaveGame = [this](int slot) { saveGame(slot); };
|
||||
@@ -429,7 +432,11 @@ namespace FRG
|
||||
// the duration, exactly like the initial resource load.
|
||||
menuManager.runAsLoadingSequence = [this](std::vector<std::function<void()>> steps) {
|
||||
for (auto& step : steps) loadSteps.push(std::move(step));
|
||||
loadSteps.push([this]() { loadingCompleted = true; });
|
||||
loadSteps.push([this]() {
|
||||
loadingCompleted = true;
|
||||
// Hidden together with loadingCompleted, so no frame falls back to drawLoading().
|
||||
menuManager.hideLoadingScreen();
|
||||
});
|
||||
loadStepsTotal = loadSteps.size();
|
||||
loadStepsDone = 0;
|
||||
showLoadingProgressBar = false;
|
||||
@@ -462,6 +469,10 @@ namespace FRG
|
||||
});
|
||||
|
||||
loadSteps.push([this]() {
|
||||
// Everything loaded so far (UI, portraits, shared assets) stays loaded for the
|
||||
// whole session; textures loaded later belong to a campaign and are released
|
||||
// when it is unloaded.
|
||||
renderer.textureManager.MarkAllResident();
|
||||
loadingCompleted = true;
|
||||
});
|
||||
|
||||
@@ -478,15 +489,8 @@ namespace FRG
|
||||
}
|
||||
|
||||
void Game::activateCampaign(const std::string& campaignId) {
|
||||
auto it = campaignLocations.find(campaignId);
|
||||
if (it == campaignLocations.end()) {
|
||||
std::cerr << "[campaign] Unknown campaign '" << campaignId << "', using '"
|
||||
<< CampaignRegistry::kDefaultCampaignId << "'" << std::endl;
|
||||
it = campaignLocations.find(CampaignRegistry::kDefaultCampaignId);
|
||||
if (it == campaignLocations.end()) return;
|
||||
}
|
||||
gameState.campaignId = it->first;
|
||||
gameState.locations = it->second;
|
||||
gameState.campaignId = campaignId;
|
||||
gameState.locations = campaignLocations[campaignId];
|
||||
gameState.questJournal.loadFromFile(activeCampaign().questsPath, CONST_ZIP_FILE);
|
||||
}
|
||||
|
||||
@@ -500,18 +504,55 @@ namespace FRG
|
||||
return *campaign;
|
||||
}
|
||||
|
||||
void Game::queueCampaignBuildSteps(const CampaignConfig& campaign) {
|
||||
const std::string campaignId = campaign.id;
|
||||
for (const std::string& configPath : campaign.locationConfigPaths) {
|
||||
loadSteps.push([this, campaignId, configPath]() {
|
||||
void Game::loadCampaignThen(const std::string& requestedId, std::function<void()> then) {
|
||||
const CampaignConfig* campaign = CampaignRegistry::instance().find(requestedId);
|
||||
if (!campaign) {
|
||||
std::cerr << "[campaign] Unknown campaign '" << requestedId << "', using '"
|
||||
<< CampaignRegistry::kDefaultCampaignId << "'" << std::endl;
|
||||
campaign = CampaignRegistry::instance().find(CampaignRegistry::kDefaultCampaignId);
|
||||
if (!campaign) return;
|
||||
}
|
||||
const std::string campaignId = campaign->id;
|
||||
|
||||
if (campaignLocations.count(campaignId)) {
|
||||
activateCampaign(campaignId);
|
||||
then();
|
||||
return;
|
||||
}
|
||||
|
||||
// Build it behind the campaign's loading screen, one location per loading step.
|
||||
// The screen is hidden when the sequence completes (see runAsLoadingSequence).
|
||||
menuManager.showLoadingScreen(campaign->loadingScreenPath);
|
||||
std::vector<std::function<void()>> steps;
|
||||
steps.push_back([this, campaignId]() {
|
||||
unloadCampaigns(); // only one campaign is kept in memory
|
||||
campaignLocations[campaignId];
|
||||
gameState.globalFloats["player_hp"] = 200; // read by location scripts as they load
|
||||
});
|
||||
for (const std::string& configPath : campaign->locationConfigPaths) {
|
||||
steps.push_back([this, campaignId, configPath]() {
|
||||
LocationConfig config;
|
||||
if (LocationConfig::fromFile(configPath, CONST_ZIP_FILE, config))
|
||||
campaignLocations[campaignId][config.id] = createLocation(config);
|
||||
});
|
||||
}
|
||||
loadSteps.push([this, campaignId]() {
|
||||
wireCampaignLocations(campaignLocations[campaignId]);
|
||||
steps.push_back([this, campaignId, then]() {
|
||||
LocationMap& locations = campaignLocations[campaignId];
|
||||
wireCampaignLocations(locations);
|
||||
if (shadowMap) {
|
||||
for (auto& [name, loc] : locations) loc->shadowMap = shadowMap;
|
||||
}
|
||||
activateCampaign(campaignId);
|
||||
then();
|
||||
});
|
||||
menuManager.runAsLoadingSequence(std::move(steps));
|
||||
}
|
||||
|
||||
void Game::unloadCampaigns() {
|
||||
gameState.locations.clear();
|
||||
campaignLocations.clear();
|
||||
// Drop the cache entries of the textures the campaign loaded; UI images stay.
|
||||
renderer.textureManager.UnloadNonResident("resources/w/ui");
|
||||
}
|
||||
|
||||
void Game::performFullReset() {
|
||||
@@ -783,8 +824,11 @@ namespace FRG
|
||||
}
|
||||
|
||||
// Wire HP-change callbacks so all player instances update the health bar HUD.
|
||||
for (auto& [name, loc] : locations) {
|
||||
if (loc->player) {
|
||||
for (auto& [name, locPtr] : locations) {
|
||||
if (locPtr->player) {
|
||||
// Raw pointer: the location owns this player, so a shared_ptr here would be
|
||||
// a reference cycle that keeps unloaded locations alive.
|
||||
Location* loc = locPtr.get();
|
||||
loc->player->state.onHpChanged = [this, loc](float hp, float maxHp, int attackerIndex) {
|
||||
menuManager.updateHealthBar(hp, maxHp);
|
||||
|
||||
@@ -875,10 +919,33 @@ namespace FRG
|
||||
|
||||
|
||||
|
||||
// Campaign loading screen: a UI layout drawn with the same state as the HUD.
|
||||
void Game::drawLoadingScreenUi()
|
||||
{
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
renderer.shaderManager.PushShader(defaultShaderName);
|
||||
renderer.RenderUniform1i(textureUniformName, 0);
|
||||
glEnable(GL_BLEND);
|
||||
menuManager.loadingUiManager.draw(renderer);
|
||||
glDisable(GL_BLEND);
|
||||
renderer.shaderManager.PopShader();
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
CheckGlError(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void Game::drawScene() {
|
||||
glViewport(0, 0, Environment::width, Environment::height);
|
||||
if (!loadingCompleted) {
|
||||
drawLoading();
|
||||
if (menuManager.isLoadingScreenShown())
|
||||
drawLoadingScreenUi();
|
||||
else
|
||||
drawLoading();
|
||||
}
|
||||
/*else if (Environment::width < Environment::height) {
|
||||
drawMobilePortraitOverlay();
|
||||
@@ -1070,6 +1137,7 @@ namespace FRG
|
||||
updateDarklandsFlash(delta);
|
||||
menuManager.uiManager.update(static_cast<float>(delta));
|
||||
menuManager.topUiManager.update(static_cast<float>(delta));
|
||||
menuManager.loadingUiManager.update(static_cast<float>(delta));
|
||||
|
||||
if (!menuManager.isMainMenuOpen())
|
||||
menuManager.update(static_cast<float>(delta));
|
||||
@@ -1115,6 +1183,7 @@ namespace FRG
|
||||
const bool isPortrait = Environment::width < Environment::height;
|
||||
menuManager.uiManager.updateAllLayouts();
|
||||
menuManager.topUiManager.updateAllLayouts();
|
||||
menuManager.loadingUiManager.updateAllLayouts();
|
||||
|
||||
if (wasPortrait != isPortrait) {
|
||||
menuManager.onOrientationChanged();
|
||||
@@ -1748,16 +1817,25 @@ namespace FRG
|
||||
std::cerr << "[save] Save file not found or empty: " << path << std::endl;
|
||||
return;
|
||||
}
|
||||
nlohmann::json root;
|
||||
try {
|
||||
nlohmann::json root = nlohmann::json::parse(content);
|
||||
// Every campaign is built at startup, so loading only switches to the save's
|
||||
// campaign (its locations and quest definitions) before applying the state.
|
||||
activateCampaign(root.value("campaignId", std::string(CampaignRegistry::kDefaultCampaignId)));
|
||||
gameState.load(root);
|
||||
root = nlohmann::json::parse(content);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[save] Failed to parse save file " << path << ": " << e.what() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// The save's campaign may not be loaded yet; the state is applied once it is.
|
||||
const std::string campaignId = root.value("campaignId", std::string(CampaignRegistry::kDefaultCampaignId));
|
||||
loadCampaignThen(campaignId, [this, root]() {
|
||||
gameState.load(root);
|
||||
playMusicForLoadedGame();
|
||||
menuManager.enterGameplay(true);
|
||||
});
|
||||
}
|
||||
|
||||
void Game::playMusicForLoadedGame()
|
||||
{
|
||||
if (gameState.isDarklands)
|
||||
{
|
||||
audioPlayer->crossFadeMusicAsync("music/bishkek fight calm.ogg");
|
||||
|
||||
Reference in new issue
Block a user