Working on save-load system and UI

This commit is contained in:
Vladislav Khorev committed 2026-06-20 21:41:07 +03:00
1 parent 901142385d
commit d7db0faea3
29 files changed
+708 -180

No files matched your search

+161 -74
View File
@@ -1,6 +1,7 @@
#include "Game.h"
#include "AnimatedModel.h"
#include "utils/Utils.h"
#include <fstream>
#include "items/ItemRegistry.h"
#include "render/OpenGlExtensions.h"
#include <iostream>
@@ -242,6 +243,113 @@ namespace ZL
ItemRegistry::instance().loadFromJson("resources/config2/items.json", CONST_ZIP_FILE);
createLocations();
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);
mobileRotateTexture = renderer.textureManager.LoadFromPng(
"resources/rotateYourDevice.png", CONST_ZIP_FILE, true);
// Wire inventory callbacks: tutorial tracking + toast notifications.
gameState.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);
};
gameState.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.
menuManager.startDialogueFunc = [this](const std::string& id) {
if (currentLocation()) currentLocation()->dialogueSystem.startDialogue(id);
};
menuManager.startDarklandsTransitionFunc = [this]() {
startDarklandsTransition();
};
menuManager.startNightTransitionFunc = [this]() {
startNightTransition();
};
menuManager.chatOpenCallback = [this](int chatIndex) {
if (currentLocation())
currentLocation()->scriptEngine.callChatOpenCallback(chatIndex);
};
menuManager.skipCutsceneFunc = [this]() {
if (currentLocation()) currentLocation()->dialogueSystem.skipCutscene();
};
std::cout << "Load resurces step 13" << std::endl;
menuManager.onResetGame = [this]() {
gameState.currentLocationName.clear();
gameState.isDarklands = false;
gameState.isNight = false;
gameState.isDawn = false;
gameState.playerHp = 200.f;
gameState.playerMaxHp = 200.f;
gameState.money = 5500;
gameState.globalInts.clear();
gameState.globalFloats.clear();
gameState.tutorialStep = TutorialStep::Step0;
gameState.uniIntTutorialState = UniIntTutorialState::Step10;
gameState.tutorialPhonePickedUp = false;
gameState.tutorialJournalPickedUp = false;
gameState.tutorialPhoneChatScreenOpened = false;
gameState.tutorialJournalScreenOpened = false;
gameState.tutorialNeedOpenTaxiScreen = false;
for (int i = 0; i < 3; ++i) {
gameState.chatUnread[i] = true;
gameState.chatPreviewMsg[i].clear();
gameState.chatHistory[i].clear();
}
gameState.inventory.items.clear();
gameState.questJournal.loadFromFile("resources/quests/quests.json", CONST_ZIP_FILE);
createLocations();
};
try {
menuManager.setup(gameState.inventory, CONST_ZIP_FILE);
std::cout << "UI loaded successfully" << std::endl;
}
catch (const std::exception& e) {
std::cerr << "Failed to load UI: " << e.what() << std::endl;
}
menuManager.startGameFunc = [this]() {
gameState.currentLocationName = "location_dorm";
currentLocation()->scriptEngine.callLocationEnterCallback();
};
menuManager.onSaveGame = [this](int slot) { saveGame(slot); };
menuManager.onLoadGame = [this](int slot) { loadGame(slot); };
menuManager.getSlotInfoFunc = [this](int slot) { return readSlotInfo(slot); };
loadingCompleted = true;
if (audioPlayer->init()) {
audioPlayer->setMusicVolume(100);
audioPlayer->setSoundVolume(80);
std::cout << "Audio initialized successfully" << std::endl;
}
else {
std::cout << "Audio initialization failed" << std::endl;
}
}
void Game::createLocations()
{
gameState.locations.clear();
gameState.globalFloats["player_hp"] = 200;
LocationSetup uniInteriorParams;
@@ -280,12 +388,12 @@ namespace ZL
"resources/navigation/uni_interior3_n2_lr_tr_hall_aiperi.json",
"resources/navigation/uni_interior3_darklands_all_open.json", //5
"resources/navigation/uni_interior4_unlocked_hall.json",
"resources/navigation/uni_interior4_unlocked_n3.json",
"resources/navigation/uni_interior4_unlocked_n3.json",
"resources/navigation/uni_interior4_unlocked_s1.json",
"resources/navigation/uni_interior4_unlocked_s2.json",
"resources/navigation/uni_interior4_unlocked_s3.json",
"resources/navigation/uni_interior4_unlocked_s3_s1.json", //11
"resources/navigation/uni_interior4_unlocked_s3_hall.json",
"resources/navigation/uni_interior4_unlocked_s3_hall.json",
"resources/navigation/uni_interior4_unlocked_s3_n3.json",
"resources/navigation/uni_interior4_unlocked_s3_n2.json",
"resources/navigation/uni_interior4_unlocked_s3_s2.json",
@@ -375,6 +483,7 @@ namespace ZL
//params_dorm.gameObjectsJsonPath = "resources/config2/gameobjects_dorm.json";
//params_dorm.gameObjectsJsonPath = "resources/config2/gameobjects_dorm_trees001.json";
params_dorm.gameObjectsJsonPath = "resources/config2/gameobjects_dorm_new_x.json";
params_dorm.npcsJsonPath = "resources/config2/npcs_dorm.json";
params_dorm.dialoguesJsonPath = "resources/dialogue/dorm_dialogues.json";
@@ -387,7 +496,7 @@ namespace ZL
"resources/navigation/dorm3_b.json",
"resources/navigation/dorm3_all_open.json",
};*/
/*
params_dorm.navigationJsonPaths = {
"resources/navigation/dorm3_bca.txt", //0
"resources/navigation/dorm3_ca.txt", //1
@@ -396,15 +505,15 @@ namespace ZL
"resources/navigation/dorm3_b.txt",
"resources/navigation/dorm3_all_open.txt", //5
};
*/
params_dorm.navigationJsonPaths = {
/*params_dorm.navigationJsonPaths = {
"resources/navigation/dorm0_large.json",
"resources/navigation/dorm0_large.json",
"resources/navigation/dorm0_large.json",
"resources/navigation/dorm0_large.json",
"resources/navigation/dorm0_large.json",
"resources/navigation/dorm0_large.json",
};
};*/
params_dorm.teleportsJsonPath = "resources/config2/teleports_dorm.json";
params_dorm.triggerZonesJsonPath = "resources/config2/trigger_zones_dorm.json";
params_dorm.lightsJsonPath = "resources/config2/lights_dorm.json";
@@ -509,43 +618,6 @@ namespace ZL
};
}
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);
mobileRotateTexture = renderer.textureManager.LoadFromPng(
"resources/rotateYourDevice.png", CONST_ZIP_FILE, true);
// Wire inventory callbacks: tutorial tracking + toast notifications.
gameState.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);
};
gameState.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.
menuManager.startDialogueFunc = [this](const std::string& id) {
if (currentLocation()) currentLocation()->dialogueSystem.startDialogue(id);
};
menuManager.startDarklandsTransitionFunc = [this]() {
startDarklandsTransition();
};
menuManager.startNightTransitionFunc = [this]() {
startNightTransition();
};
menuManager.chatOpenCallback = [this](int chatIndex) {
if (currentLocation())
currentLocation()->scriptEngine.callChatOpenCallback(chatIndex);
};
// Wire chat-bubble callback so dynamic bubbles appear as dialogue lines are shown.
for (auto& [name, loc] : gameState.locations) {
loc->dialogueSystem.setOnChatBubbleReady([this](const std::string& text, bool incoming) {
@@ -562,25 +634,6 @@ namespace ZL
menuManager.onCutsceneFinished();
});
}
menuManager.skipCutsceneFunc = [this]() {
if (currentLocation()) currentLocation()->dialogueSystem.skipCutscene();
};
std::cout << "Load resurces step 13" << std::endl;
try {
menuManager.setup(gameState.inventory, CONST_ZIP_FILE);
std::cout << "UI loaded successfully" << std::endl;
}
catch (const std::exception& e) {
std::cerr << "Failed to load UI: " << e.what() << std::endl;
}
menuManager.startGameFunc = [this]() {
gameState.currentLocationName = "location_dorm";
currentLocation()->scriptEngine.callLocationEnterCallback();
};
// Wire HP-change callbacks so all player instances update the health bar HUD.
for (auto& [name, loc] : gameState.locations) {
@@ -590,18 +643,6 @@ namespace ZL
};
}
}
loadingCompleted = true;
if (audioPlayer->init()) {
audioPlayer->setMusicVolume(100);
audioPlayer->setSoundVolume(80);
std::cout << "Audio initialized successfully" << std::endl;
}
else {
std::cout << "Audio initialization failed" << std::endl;
}
}
void Game::drawUI()
@@ -1515,4 +1556,50 @@ namespace ZL
glDisable(GL_BLEND);
}
SaveSlotInfo Game::readSlotInfo(int slot) const
{
std::string path = "save_slot" + std::to_string(slot) + ".json";
std::string content = ZL::readTextFile(path);
if (content.empty()) return {};
try {
nlohmann::json root = nlohmann::json::parse(content);
SaveSlotInfo info;
info.empty = false;
info.locationName = root.value("currentLocationName", "");
info.savedAt = root.value("savedAt", "");
return info;
} catch (...) {
return {};
}
}
void Game::saveGame(int slot)
{
nlohmann::json root;
gameState.save(root);
std::string path = "save_slot" + std::to_string(slot) + ".json";
std::ofstream file(path);
if (file.is_open()) {
file << root.dump(2);
} else {
std::cerr << "[save] Could not open " << path << " for writing" << std::endl;
}
}
void Game::loadGame(int slot)
{
std::string path = "save_slot" + std::to_string(slot) + ".json";
std::string content = ZL::readTextFile(path);
if (content.empty()) {
std::cerr << "[save] Save file not found or empty: " << path << std::endl;
return;
}
try {
nlohmann::json root = nlohmann::json::parse(content);
gameState.load(root);
} catch (const std::exception& e) {
std::cerr << "[save] Failed to parse save file " << path << ": " << e.what() << std::endl;
}
}
} // namespace ZL