Working on loading
This commit is contained in:
1 parent
d38a75ea72
commit
9b71ee447a
11 files changed
+268
-55
No files matched your search
@@ -12,7 +12,8 @@
|
|||||||
"startMusic": "music/obshaga.ogg",
|
"startMusic": "music/obshaga.ogg",
|
||||||
"startState": "resources/config/start_state.json",
|
"startState": "resources/config/start_state.json",
|
||||||
"quests": "resources/config/quests.json",
|
"quests": "resources/config/quests.json",
|
||||||
"tutorial": true
|
"tutorial": true,
|
||||||
|
"loadingScreen": "resources/w/ui/screen_loading.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "battle_zone",
|
"id": "battle_zone",
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
"startMusic": "music/bishkek univer day.ogg",
|
"startMusic": "music/bishkek univer day.ogg",
|
||||||
"startState": "resources/config/start_state_battle_zone.json",
|
"startState": "resources/config/start_state_battle_zone.json",
|
||||||
"quests": "resources/config/quests_battle_zone.json",
|
"quests": "resources/config/quests_battle_zone.json",
|
||||||
"tutorial": false
|
"tutorial": false,
|
||||||
|
"loadingScreen": "resources/w/ui/screen_loading.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"root": {
|
||||||
|
"type": "FrameLayout",
|
||||||
|
"width": "match_parent",
|
||||||
|
"height": "match_parent",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "StaticImage",
|
||||||
|
"name": "loadingBkg",
|
||||||
|
"width": 2992,
|
||||||
|
"height": 1632,
|
||||||
|
"horizontal_gravity": "center",
|
||||||
|
"vertical_gravity": "center",
|
||||||
|
"texture": "resources/w/ui/img/main/MenuBkg001_v001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "TextView",
|
||||||
|
"name": "loadingText",
|
||||||
|
"horizontal_gravity": "center",
|
||||||
|
"vertical_gravity": "center",
|
||||||
|
"width": 600,
|
||||||
|
"height": 80,
|
||||||
|
"text": "Загрузка...",
|
||||||
|
"fontSize": 48,
|
||||||
|
"textCentered": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"root": {
|
||||||
|
"type": "FrameLayout",
|
||||||
|
"width": "match_parent",
|
||||||
|
"height": "match_parent",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "StaticImage",
|
||||||
|
"name": "loadingBkg",
|
||||||
|
"width": 2992,
|
||||||
|
"height": 1632,
|
||||||
|
"horizontal_gravity": "center",
|
||||||
|
"vertical_gravity": "center",
|
||||||
|
"texture": "resources/w/ui/img/main/MenuBkg001_v001.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "TextView",
|
||||||
|
"name": "loadingText",
|
||||||
|
"horizontal_gravity": "center",
|
||||||
|
"vertical_gravity": "center",
|
||||||
|
"width": 600,
|
||||||
|
"height": 80,
|
||||||
|
"text": "Loading...",
|
||||||
|
"fontSize": 48,
|
||||||
|
"textCentered": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,6 +96,7 @@ namespace FRG {
|
|||||||
c.startStatePath = entry.value("startState", "");
|
c.startStatePath = entry.value("startState", "");
|
||||||
c.questsPath = entry.value("quests", "");
|
c.questsPath = entry.value("quests", "");
|
||||||
c.tutorial = entry.value("tutorial", false);
|
c.tutorial = entry.value("tutorial", false);
|
||||||
|
c.loadingScreenPath = entry.value("loadingScreen", std::string(CampaignConfig::kDefaultLoadingScreen));
|
||||||
campaigns_.push_back(std::move(c));
|
campaigns_.push_back(std::move(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ namespace FRG {
|
|||||||
std::string startStatePath;
|
std::string startStatePath;
|
||||||
std::string questsPath;
|
std::string questsPath;
|
||||||
bool tutorial = false;
|
bool tutorial = false;
|
||||||
|
// UI layout shown while the campaign loads (preloaded with the rest of the UI).
|
||||||
|
std::string loadingScreenPath = kDefaultLoadingScreen;
|
||||||
|
|
||||||
|
static constexpr const char* kDefaultLoadingScreen = "resources/w/ui/screen_loading.json";
|
||||||
|
|
||||||
std::string title(Language lang) const;
|
std::string title(Language lang) const;
|
||||||
};
|
};
|
||||||
|
|||||||
+124
-46
@@ -327,18 +327,10 @@ namespace FRG
|
|||||||
Character::nameTranslations.loadFromFile("resources/config/translate_names.json");
|
Character::nameTranslations.loadFromFile("resources/config/translate_names.json");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Every campaign's locations are built up front (one loading step per location),
|
// Campaigns are only listed here; their locations are built on demand when the
|
||||||
// so starting or loading any campaign later only switches the active set.
|
// 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);
|
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]() {
|
loadSteps.push([this]() {
|
||||||
// Wire inventory callbacks: tutorial tracking + toast notifications.
|
// Wire inventory callbacks: tutorial tracking + toast notifications.
|
||||||
@@ -393,17 +385,28 @@ namespace FRG
|
|||||||
}
|
}
|
||||||
|
|
||||||
menuManager.startGameFunc = [this](const std::string& campaignId) {
|
menuManager.startGameFunc = [this](const std::string& campaignId) {
|
||||||
mainThreadHandler.EnqueueMainThreadTask([this, campaignId]() {
|
auto start = [this, campaignId]() {
|
||||||
activateCampaign(campaignId);
|
loadCampaignThen(campaignId, [this]() {
|
||||||
performResetToInitialState();
|
performResetToInitialState();
|
||||||
applyTutorialSetting();
|
applyTutorialSetting();
|
||||||
const CampaignConfig& campaign = activeCampaign();
|
const CampaignConfig& campaign = activeCampaign();
|
||||||
gameState.currentLocationName = campaign.startLocation;
|
gameState.currentLocationName = campaign.startLocation;
|
||||||
menuManager.showGameplayHud(); // first HUD frame drawn from the fresh state
|
menuManager.showGameplayHud(); // first HUD frame drawn from the fresh state
|
||||||
if (currentLocation()) currentLocation()->scriptEngine.callLocationEnterCallback();
|
if (currentLocation()) currentLocation()->scriptEngine.callLocationEnterCallback();
|
||||||
audioPlayer->crossFadeMusicAsync(campaign.startMusic);
|
audioPlayer->crossFadeMusicAsync(campaign.startMusic);
|
||||||
menuManager.lastMusicTrack = 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); };
|
menuManager.onSaveGame = [this](int slot) { saveGame(slot); };
|
||||||
@@ -429,7 +432,11 @@ namespace FRG
|
|||||||
// the duration, exactly like the initial resource load.
|
// the duration, exactly like the initial resource load.
|
||||||
menuManager.runAsLoadingSequence = [this](std::vector<std::function<void()>> steps) {
|
menuManager.runAsLoadingSequence = [this](std::vector<std::function<void()>> steps) {
|
||||||
for (auto& step : steps) loadSteps.push(std::move(step));
|
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();
|
loadStepsTotal = loadSteps.size();
|
||||||
loadStepsDone = 0;
|
loadStepsDone = 0;
|
||||||
showLoadingProgressBar = false;
|
showLoadingProgressBar = false;
|
||||||
@@ -462,6 +469,10 @@ namespace FRG
|
|||||||
});
|
});
|
||||||
|
|
||||||
loadSteps.push([this]() {
|
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;
|
loadingCompleted = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -478,15 +489,8 @@ namespace FRG
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Game::activateCampaign(const std::string& campaignId) {
|
void Game::activateCampaign(const std::string& campaignId) {
|
||||||
auto it = campaignLocations.find(campaignId);
|
gameState.campaignId = campaignId;
|
||||||
if (it == campaignLocations.end()) {
|
gameState.locations = campaignLocations[campaignId];
|
||||||
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.questJournal.loadFromFile(activeCampaign().questsPath, CONST_ZIP_FILE);
|
gameState.questJournal.loadFromFile(activeCampaign().questsPath, CONST_ZIP_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,18 +504,55 @@ namespace FRG
|
|||||||
return *campaign;
|
return *campaign;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::queueCampaignBuildSteps(const CampaignConfig& campaign) {
|
void Game::loadCampaignThen(const std::string& requestedId, std::function<void()> then) {
|
||||||
const std::string campaignId = campaign.id;
|
const CampaignConfig* campaign = CampaignRegistry::instance().find(requestedId);
|
||||||
for (const std::string& configPath : campaign.locationConfigPaths) {
|
if (!campaign) {
|
||||||
loadSteps.push([this, campaignId, configPath]() {
|
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;
|
LocationConfig config;
|
||||||
if (LocationConfig::fromFile(configPath, CONST_ZIP_FILE, config))
|
if (LocationConfig::fromFile(configPath, CONST_ZIP_FILE, config))
|
||||||
campaignLocations[campaignId][config.id] = createLocation(config);
|
campaignLocations[campaignId][config.id] = createLocation(config);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
loadSteps.push([this, campaignId]() {
|
steps.push_back([this, campaignId, then]() {
|
||||||
wireCampaignLocations(campaignLocations[campaignId]);
|
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() {
|
void Game::performFullReset() {
|
||||||
@@ -783,8 +824,11 @@ namespace FRG
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wire HP-change callbacks so all player instances update the health bar HUD.
|
// Wire HP-change callbacks so all player instances update the health bar HUD.
|
||||||
for (auto& [name, loc] : locations) {
|
for (auto& [name, locPtr] : locations) {
|
||||||
if (loc->player) {
|
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) {
|
loc->player->state.onHpChanged = [this, loc](float hp, float maxHp, int attackerIndex) {
|
||||||
menuManager.updateHealthBar(hp, maxHp);
|
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() {
|
void Game::drawScene() {
|
||||||
glViewport(0, 0, Environment::width, Environment::height);
|
glViewport(0, 0, Environment::width, Environment::height);
|
||||||
if (!loadingCompleted) {
|
if (!loadingCompleted) {
|
||||||
drawLoading();
|
if (menuManager.isLoadingScreenShown())
|
||||||
|
drawLoadingScreenUi();
|
||||||
|
else
|
||||||
|
drawLoading();
|
||||||
}
|
}
|
||||||
/*else if (Environment::width < Environment::height) {
|
/*else if (Environment::width < Environment::height) {
|
||||||
drawMobilePortraitOverlay();
|
drawMobilePortraitOverlay();
|
||||||
@@ -1070,6 +1137,7 @@ namespace FRG
|
|||||||
updateDarklandsFlash(delta);
|
updateDarklandsFlash(delta);
|
||||||
menuManager.uiManager.update(static_cast<float>(delta));
|
menuManager.uiManager.update(static_cast<float>(delta));
|
||||||
menuManager.topUiManager.update(static_cast<float>(delta));
|
menuManager.topUiManager.update(static_cast<float>(delta));
|
||||||
|
menuManager.loadingUiManager.update(static_cast<float>(delta));
|
||||||
|
|
||||||
if (!menuManager.isMainMenuOpen())
|
if (!menuManager.isMainMenuOpen())
|
||||||
menuManager.update(static_cast<float>(delta));
|
menuManager.update(static_cast<float>(delta));
|
||||||
@@ -1115,6 +1183,7 @@ namespace FRG
|
|||||||
const bool isPortrait = Environment::width < Environment::height;
|
const bool isPortrait = Environment::width < Environment::height;
|
||||||
menuManager.uiManager.updateAllLayouts();
|
menuManager.uiManager.updateAllLayouts();
|
||||||
menuManager.topUiManager.updateAllLayouts();
|
menuManager.topUiManager.updateAllLayouts();
|
||||||
|
menuManager.loadingUiManager.updateAllLayouts();
|
||||||
|
|
||||||
if (wasPortrait != isPortrait) {
|
if (wasPortrait != isPortrait) {
|
||||||
menuManager.onOrientationChanged();
|
menuManager.onOrientationChanged();
|
||||||
@@ -1748,16 +1817,25 @@ namespace FRG
|
|||||||
std::cerr << "[save] Save file not found or empty: " << path << std::endl;
|
std::cerr << "[save] Save file not found or empty: " << path << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
nlohmann::json root;
|
||||||
try {
|
try {
|
||||||
nlohmann::json root = nlohmann::json::parse(content);
|
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);
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "[save] Failed to parse save file " << path << ": " << e.what() << std::endl;
|
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)
|
if (gameState.isDarklands)
|
||||||
{
|
{
|
||||||
audioPlayer->crossFadeMusicAsync("music/bishkek fight calm.ogg");
|
audioPlayer->crossFadeMusicAsync("music/bishkek fight calm.ogg");
|
||||||
|
|||||||
+12
-5
@@ -126,18 +126,24 @@ namespace FRG {
|
|||||||
|
|
||||||
void saveGame(int slot);
|
void saveGame(int slot);
|
||||||
void loadGame(int slot);
|
void loadGame(int slot);
|
||||||
|
void playMusicForLoadedGame();
|
||||||
SaveSlotInfo readSlotInfo(int slot) const;
|
SaveSlotInfo readSlotInfo(int slot) const;
|
||||||
// Every campaign's locations, all built at startup (campaign id -> locations).
|
// Locations of the loaded campaign (campaign id -> locations); at most one entry,
|
||||||
// gameState.locations is a copy of the active campaign's entry.
|
// empty until the player starts or loads a game. gameState.locations is a copy
|
||||||
|
// of the active campaign's entry.
|
||||||
std::unordered_map<std::string, LocationMap> campaignLocations;
|
std::unordered_map<std::string, LocationMap> campaignLocations;
|
||||||
|
|
||||||
// Builds one location from its config and wires the callbacks every location gets.
|
// Builds one location from its config and wires the callbacks every location gets.
|
||||||
std::shared_ptr<Location> createLocation(const LocationConfig& config);
|
std::shared_ptr<Location> createLocation(const LocationConfig& config);
|
||||||
// Wires the callbacks that span a campaign's locations (teleports, dialogue, HUD...).
|
// Wires the callbacks that span a campaign's locations (teleports, dialogue, HUD...).
|
||||||
void wireCampaignLocations(LocationMap& locations);
|
void wireCampaignLocations(LocationMap& locations);
|
||||||
// Queues one loading step per location of `campaign`, plus its wiring step.
|
// Runs `then` with the campaign active. If its locations aren't loaded, they are
|
||||||
void queueCampaignBuildSteps(const CampaignConfig& campaign);
|
// built first behind the campaign's loading screen (one loading step per
|
||||||
// Makes `campaignId` the played campaign: its locations and its quest definitions.
|
// location) and any other loaded campaign is released.
|
||||||
|
void loadCampaignThen(const std::string& campaignId, std::function<void()> then);
|
||||||
|
// Releases every loaded campaign's locations and the textures only they used.
|
||||||
|
void unloadCampaigns();
|
||||||
|
// Makes a loaded campaign the played one: its locations and its quest definitions.
|
||||||
void activateCampaign(const std::string& campaignId);
|
void activateCampaign(const std::string& campaignId);
|
||||||
const CampaignConfig& activeCampaign() const;
|
const CampaignConfig& activeCampaign() const;
|
||||||
|
|
||||||
@@ -168,6 +174,7 @@ namespace FRG {
|
|||||||
void drawUI();
|
void drawUI();
|
||||||
|
|
||||||
void drawLoading();
|
void drawLoading();
|
||||||
|
void drawLoadingScreenUi();
|
||||||
|
|
||||||
void onPointerDown(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
void onPointerDown(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
||||||
void onPointerUp(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
void onPointerUp(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
||||||
|
|||||||
+21
-1
@@ -325,6 +325,11 @@ namespace FRG {
|
|||||||
languageScreenRoot = loadLocalizedUi("resources/w/ui/screen_language.json", zipFile);
|
languageScreenRoot = loadLocalizedUi("resources/w/ui/screen_language.json", zipFile);
|
||||||
loadSavedGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_load.json", zipFile);
|
loadSavedGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_load.json", zipFile);
|
||||||
campaignScreenRoot = loadLocalizedUi("resources/w/ui/screen_campaign.json", zipFile);
|
campaignScreenRoot = loadLocalizedUi("resources/w/ui/screen_campaign.json", zipFile);
|
||||||
|
loadingScreenRoots_.clear();
|
||||||
|
for (const CampaignConfig& campaign : CampaignRegistry::instance().all()) {
|
||||||
|
if (!loadingScreenRoots_.count(campaign.loadingScreenPath))
|
||||||
|
loadingScreenRoots_[campaign.loadingScreenPath] = loadLocalizedUi(campaign.loadingScreenPath, zipFile);
|
||||||
|
}
|
||||||
saveGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_save.json", zipFile);
|
saveGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_save.json", zipFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,6 +390,21 @@ namespace FRG {
|
|||||||
if (startGameFunc) startGameFunc(campaignId);
|
if (startGameFunc) startGameFunc(campaignId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MenuManager::showLoadingScreen(const std::string& layoutPath) {
|
||||||
|
auto it = loadingScreenRoots_.find(layoutPath);
|
||||||
|
if (it == loadingScreenRoots_.end() || !it->second) {
|
||||||
|
std::cerr << "[menu] Loading screen not preloaded: " << layoutPath << std::endl;
|
||||||
|
return; // Game falls back to the startup loading image
|
||||||
|
}
|
||||||
|
loadingUiManager.replaceRoot(it->second);
|
||||||
|
loadingScreenShown_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuManager::hideLoadingScreen() {
|
||||||
|
loadingUiManager.replaceRoot(nullptr);
|
||||||
|
loadingScreenShown_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
void MenuManager::enterGameplay(bool isFromLoad) {
|
void MenuManager::enterGameplay(bool isFromLoad) {
|
||||||
if (isFromLoad) {
|
if (isFromLoad) {
|
||||||
uiManager.clearMenuStack();
|
uiManager.clearMenuStack();
|
||||||
@@ -1080,8 +1100,8 @@ namespace FRG {
|
|||||||
}
|
}
|
||||||
uiManager.setTextButtonCallback(kSlotButtons[i], [this, slot](const std::string&) {
|
uiManager.setTextButtonCallback(kSlotButtons[i], [this, slot](const std::string&) {
|
||||||
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
||||||
|
// Game enters gameplay itself once the save (and its campaign) is loaded.
|
||||||
if (onLoadGame) onLoadGame(slot);
|
if (onLoadGame) onLoadGame(slot);
|
||||||
enterGameplay(true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -33,6 +33,8 @@ namespace FRG {
|
|||||||
UiManager uiManager;
|
UiManager uiManager;
|
||||||
UiManager topUiManager;
|
UiManager topUiManager;
|
||||||
UiManager chatUiManager;
|
UiManager chatUiManager;
|
||||||
|
// Draws the campaign loading screen while Game builds a campaign.
|
||||||
|
UiManager loadingUiManager;
|
||||||
|
|
||||||
std::unordered_map<std::string,
|
std::unordered_map<std::string,
|
||||||
std::unordered_map<Language, std::string>> localizedLocationName;
|
std::unordered_map<Language, std::string>> localizedLocationName;
|
||||||
@@ -66,6 +68,14 @@ namespace FRG {
|
|||||||
// Shows the HUD for the current location with its callbacks wired and visibility
|
// Shows the HUD for the current location with its callbacks wired and visibility
|
||||||
// applied. Game calls it once a new game's state has been reset.
|
// applied. Game calls it once a new game's state has been reset.
|
||||||
void showGameplayHud();
|
void showGameplayHud();
|
||||||
|
// Called by Game once a save has been applied.
|
||||||
|
void enterGameplay(bool isFromLoad = false);
|
||||||
|
|
||||||
|
// Campaign loading screen (layout from CampaignConfig::loadingScreenPath, preloaded
|
||||||
|
// with the UI). Game shows it before building a campaign and hides it when done.
|
||||||
|
void showLoadingScreen(const std::string& layoutPath);
|
||||||
|
void hideLoadingScreen();
|
||||||
|
bool isLoadingScreenShown() const { return loadingScreenShown_; }
|
||||||
|
|
||||||
void setChatUnread(int chatIndex, bool unread, const std::string& previewMsg = "");
|
void setChatUnread(int chatIndex, bool unread, const std::string& previewMsg = "");
|
||||||
|
|
||||||
@@ -166,7 +176,6 @@ namespace FRG {
|
|||||||
AudioPlayerAsync& audioPlayer_;
|
AudioPlayerAsync& audioPlayer_;
|
||||||
|
|
||||||
void startNewGame(const std::string& campaignId);
|
void startNewGame(const std::string& campaignId);
|
||||||
void enterGameplay(bool isFromLoad = false);
|
|
||||||
// HUD layout for the current location and darklands mode.
|
// HUD layout for the current location and darklands mode.
|
||||||
std::shared_ptr<UiNode> gameplayHudRoot() const;
|
std::shared_ptr<UiNode> gameplayHudRoot() const;
|
||||||
// Single place that decides which HUD buttons and tutorial hints are visible:
|
// Single place that decides which HUD buttons and tutorial hints are visible:
|
||||||
@@ -299,6 +308,9 @@ namespace FRG {
|
|||||||
std::shared_ptr<UiNode> languageScreenRoot;
|
std::shared_ptr<UiNode> languageScreenRoot;
|
||||||
std::shared_ptr<UiNode> loadSavedGameScreenRoot;
|
std::shared_ptr<UiNode> loadSavedGameScreenRoot;
|
||||||
std::shared_ptr<UiNode> campaignScreenRoot;
|
std::shared_ptr<UiNode> campaignScreenRoot;
|
||||||
|
// Loading screen layouts by path; campaigns may share one.
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<UiNode>> loadingScreenRoots_;
|
||||||
|
bool loadingScreenShown_ = false;
|
||||||
std::shared_ptr<UiNode> saveGameScreenRoot;
|
std::shared_ptr<UiNode> saveGameScreenRoot;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -553,4 +553,26 @@ namespace FRG
|
|||||||
textureMap.clear();
|
textureMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextureManager::MarkAllResident()
|
||||||
|
{
|
||||||
|
for (const auto& [key, tex] : textureMap) residentKeys.insert(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureManager::UnloadNonResident(const std::string& residentPrefix)
|
||||||
|
{
|
||||||
|
size_t unloaded = 0;
|
||||||
|
for (auto it = textureMap.begin(); it != textureMap.end(); ) {
|
||||||
|
const bool keep = residentKeys.count(it->first)
|
||||||
|
|| (!residentPrefix.empty() && it->first.compare(0, residentPrefix.size(), residentPrefix) == 0);
|
||||||
|
if (keep) {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
it = textureMap.erase(it);
|
||||||
|
++unloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger() << "Unloaded " << unloaded << " non-resident textures" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "utils/Utils.h"
|
#include "utils/Utils.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#if defined EMSCRIPTEN || defined __ANDROID__
|
#if defined EMSCRIPTEN || defined __ANDROID__
|
||||||
#define PNG_ENABLED
|
#define PNG_ENABLED
|
||||||
@@ -79,8 +80,16 @@ namespace FRG
|
|||||||
void UnloadByPrefix(const std::string& prefix);
|
void UnloadByPrefix(const std::string& prefix);
|
||||||
void UnloadAll();
|
void UnloadAll();
|
||||||
|
|
||||||
|
// Marks every texture cached so far as resident, so UnloadNonResident() keeps it.
|
||||||
|
void MarkAllResident();
|
||||||
|
// Drops cached textures that aren't resident and whose key doesn't start with
|
||||||
|
// residentPrefix (e.g. UI images, which are reloaded under the same prefix). A
|
||||||
|
// texture is freed once nothing else holds it.
|
||||||
|
void UnloadNonResident(const std::string& residentPrefix = "");
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, std::shared_ptr<Texture>> textureMap;
|
std::unordered_map<std::string, std::shared_ptr<Texture>> textureMap;
|
||||||
|
std::unordered_set<std::string> residentKeys;
|
||||||
static std::string MakeKey(const std::string& fileName, const std::string& zipFile);
|
static std::string MakeKey(const std::string& fileName, const std::string& zipFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user