Compare commits

...

2 Commits

Author SHA1 Message Date
Vladislav Khorev
fb7b42ddf9 Some clean up 2026-05-02 20:40:29 +03:00
Vladislav Khorev
6149182f02 Fixing bugs, added girl 2026-05-02 20:34:43 +03:00
24 changed files with 297863 additions and 26690 deletions

View File

@ -29,7 +29,7 @@
{ {
"name": "tree001", "name": "tree001",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -1.5707963267948966,
"rotationZ": 0.0, "rotationZ": 0.0,
@ -42,7 +42,7 @@
{ {
"name": "tree002", "name": "tree002",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5707963267948966, "rotationY": 1.5707963267948966,
"rotationZ": 0.0, "rotationZ": 0.0,
@ -55,7 +55,7 @@
{ {
"name": "tree003", "name": "tree003",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 0.0, "rotationY": 0.0,
"rotationZ": 0.0, "rotationZ": 0.0,
@ -68,7 +68,7 @@
{ {
"name": "tree004", "name": "tree004",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -1.5707963267948966,
"rotationZ": 0.0, "rotationZ": 0.0,
@ -81,7 +81,7 @@
{ {
"name": "tree005", "name": "tree005",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5707963267948966, "rotationY": 1.5707963267948966,
"rotationZ": 0.0, "rotationZ": 0.0,
@ -94,7 +94,7 @@
{ {
"name": "tree006", "name": "tree006",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 0.0, "rotationY": 0.0,
"rotationZ": 0.0, "rotationZ": 0.0,
@ -107,7 +107,7 @@
{ {
"name": "tree007", "name": "tree007",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -1.5707963267948966,
"rotationZ": 0.0, "rotationZ": 0.0,
@ -120,7 +120,7 @@
{ {
"name": "tree008", "name": "tree008",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5707963267948966, "rotationY": 1.5707963267948966,
"rotationZ": 0.0, "rotationZ": 0.0,
@ -133,7 +133,7 @@
{ {
"name": "tree009", "name": "tree009",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 0.0, "rotationY": 0.0,
"rotationZ": 0.0, "rotationZ": 0.0,
@ -146,7 +146,7 @@
{ {
"name": "tree010", "name": "tree010",
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree002.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -1.5707963267948966,
"rotationZ": 0.0, "rotationZ": 0.0,

View File

@ -0,0 +1,46 @@
{
"root": {
"type": "FrameLayout",
"name": "hud_root",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "TextButton",
"name": "inventory_button",
"x": 50.0,
"y": 50.0,
"width": 150.0,
"height": 60.0,
"text": "Inventory",
"fontSize": 24,
"fontPath": "resources/fonts/DroidSans.ttf",
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0],
"textures": {
"normal": "resources/w/red.png",
"hover": "resources/w/red.png",
"pressed": "resources/w/red.png"
}
},
{
"type": "TextButton",
"name": "quest_journal_button",
"x": 220.0,
"y": 50.0,
"width": 170.0,
"height": 60.0,
"text": "Quests",
"fontSize": 24,
"fontPath": "resources/fonts/DroidSans.ttf",
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0],
"textures": {
"normal": "resources/w/red.png",
"hover": "resources/w/red.png",
"pressed": "resources/w/red.png"
}
}
]
}
}

View File

@ -37,16 +37,10 @@
{ {
"id": "npc_02_woman", "id": "npc_02_woman",
"name": "Студентка", "name": "Студентка",
"animationIdlePath": "resources/w/jam/woman_idle002.anim", "animationIdlePath": "resources/w/new_anims/girl_stand_idle005.txt",
"animationWalkPath": "resources/w/jam/woman_walk002.anim", "animationWalkPath": "resources/w/new_anims/girl_walk005.txt",
"meshTextures": { "meshTextures": {
"Body": "resources/w/jam/female_packed0_diffuse.png", "polySurface1": "resources/w/new_anims/Chat_02_diff.png"
"Bottoms": "resources/w/jam/female_packed3_diffuse.png",
"Eyelashes": "resources/w/jam/female_packed0_diffuse.png",
"Eyes": "resources/w/jam/female_packed0_diffuse.png",
"Hair": "resources/w/jam/female_packed2_diffuse.png",
"Shoes": "resources/w/jam/female_packed1_diffuse.png",
"Tops": "resources/w/jam/female_packed2_diffuse.png"
}, },
"positionX": 19.5, "positionX": 19.5,
"positionY": 0.0, "positionY": 0.0,
@ -54,7 +48,7 @@
"facingAngle" : 3.141592, "facingAngle" : 3.141592,
"walkSpeed": 1.5, "walkSpeed": 1.5,
"rotationSpeed": 8.0, "rotationSpeed": 8.0,
"modelScale": 0.0001, "modelScale": 0.00016,
"modelCorrectionRotX": 0.0, "modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0, "modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0, "modelCorrectionRotZ": 0.0,

View File

@ -5,24 +5,6 @@
"width": "match_parent", "width": "match_parent",
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
{
"type": "TextButton",
"name": "inventory_button",
"x": 50.0,
"y": 50.0,
"width": 150.0,
"height": 60.0,
"text": "Inventory",
"fontSize": 24,
"fontPath": "resources/fonts/DroidSans.ttf",
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0],
"textures": {
"normal": "resources/w/red.png",
"hover": "resources/w/red.png",
"pressed": "resources/w/red.png"
}
},
{ {
"type": "FrameLayout", "type": "FrameLayout",
"name": "inventory_items_panel", "name": "inventory_items_panel",

View File

@ -5,24 +5,6 @@
"width": "match_parent", "width": "match_parent",
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
{
"type": "TextButton",
"name": "quest_journal_button",
"x": 220.0,
"y": 50.0,
"width": 170.0,
"height": 60.0,
"text": "Quests",
"fontSize": 24,
"fontPath": "resources/fonts/DroidSans.ttf",
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0],
"textures": {
"normal": "resources/w/red.png",
"hover": "resources/w/red.png",
"pressed": "resources/w/red.png"
}
},
{ {
"type": "FrameLayout", "type": "FrameLayout",
"name": "quest_journal_panel", "name": "quest_journal_panel",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
resources/w/jam/female_packed0_diffuse.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/jam/female_packed1_diffuse.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/jam/female_packed2_diffuse.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/jam/female_packed3_diffuse.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/jam/woman_idle001.anim (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/jam/woman_idle002.anim (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/jam/woman_walk001.anim (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/jam/woman_walk002.anim (Stored with Git LFS)

Binary file not shown.

BIN
resources/w/new_anims/Chat_02_diff.png (Stored with Git LFS) Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -342,7 +342,6 @@ void Character::update(int64_t deltaMs) {
} }
if (hitSparkEmitter.isConfigured()) { if (hitSparkEmitter.isConfigured()) {
hitSparkEmitter.update(static_cast<float>(deltaMs)); hitSparkEmitter.update(static_cast<float>(deltaMs));
} }

View File

@ -236,51 +236,9 @@ namespace ZL
std::cout << "Load resurces step 13" << std::endl; std::cout << "Load resurces step 13" << std::endl;
// Load UI with inventory button
try { try {
menuManager.uiManager.loadFromFile("resources/config2/ui_inventory.json", renderer, CONST_ZIP_FILE); menuManager.setup(inventory, CONST_ZIP_FILE);
menuManager.uiManager.appendFromFile("resources/config2/ui_quest_journal.json", renderer, CONST_ZIP_FILE);
questJournal.loadFromFile("resources/quests/quests.json", CONST_ZIP_FILE);
setupQuestJournalUi();
std::cout << "UI loaded successfully" << std::endl; std::cout << "UI loaded successfully" << std::endl;
menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
menuManager.uiManager.setNodeVisible("close_inventory_button", false);
menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) {
std::cout << "[UI] Inventory button clicked" << std::endl;
if (this->questJournalOpen) {
this->toggleQuestJournal();
}
this->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
this->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
this->inventoryOpen = true;
// Update UI with current items
const auto& items = this->inventory.getItems();
std::string itemText;
if (items.empty()) {
itemText = "Inventory (Empty)";
}
else {
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
for (size_t i = 0; i < items.size(); ++i) {
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
}
}
this->menuManager.uiManager.setText("inventory_items_text", itemText);
});
menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
std::cout << "[UI] Close button clicked" << std::endl;
menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
menuManager.uiManager.setNodeVisible("close_inventory_button", false);
inventoryOpen = false;
});
} }
catch (const std::exception& e) { catch (const std::exception& e) {
std::cerr << "Failed to load UI: " << e.what() << std::endl; std::cerr << "Failed to load UI: " << e.what() << std::endl;
@ -327,147 +285,6 @@ namespace ZL
static int questStatusPriority(Quest::QuestStatus status) {
switch (status) {
case Quest::QuestStatus::Active: return 0;
case Quest::QuestStatus::Available: return 1;
case Quest::QuestStatus::Completed: return 2;
case Quest::QuestStatus::Failed: return 3;
default: return 4;
}
}
static std::array<float, 4> questStatusColor(Quest::QuestStatus status) {
switch (status) {
case Quest::QuestStatus::Completed: return { 0.25f, 0.95f, 0.35f, 1.0f };
case Quest::QuestStatus::Failed: return { 1.0f, 0.25f, 0.25f, 1.0f };
case Quest::QuestStatus::Active: return { 1.0f, 1.0f, 1.0f, 1.0f };
case Quest::QuestStatus::Available: return { 0.86f, 0.86f, 0.86f, 1.0f };
default: return { 0.45f, 0.45f, 0.45f, 1.0f };
}
}
void Game::setupQuestJournalUi() {
questJournalOpen = false;
selectedQuestIndex = -1;
visibleQuestIds.clear();
menuManager.uiManager.setNodeVisible("quest_journal_panel", false);
menuManager.uiManager.setNodeVisible("quest_close_button", false);
menuManager.uiManager.setTextButtonCallback("quest_journal_button", [this](const std::string&) {
toggleQuestJournal();
});
menuManager.uiManager.setTextButtonCallback("quest_close_button", [this](const std::string&) {
if (questJournalOpen) {
toggleQuestJournal();
}
});
for (int i = 0; i < 9; ++i) {
const std::string slotName = "quest_slot_" + std::to_string(i);
menuManager.uiManager.setTextButtonCallback(slotName, [this, i](const std::string&) {
selectQuestByIndex(i);
});
menuManager.uiManager.setNodeVisible(slotName, false);
}
}
void Game::toggleQuestJournal() {
questJournalOpen = !questJournalOpen;
std::cout << "[quest] toggleQuestJournal: " << (questJournalOpen ? "open" : "closed") << std::endl;
if (questJournalOpen) {
if (inventoryOpen) {
menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
menuManager.uiManager.setNodeVisible("close_inventory_button", false);
inventoryOpen = false;
}
}
menuManager.uiManager.setNodeVisible("quest_journal_panel", questJournalOpen);
menuManager.uiManager.setNodeVisible("quest_close_button", questJournalOpen);
if (questJournalOpen) {
refreshQuestJournalUi();
if (!visibleQuestIds.empty()) {
selectQuestByIndex(0);
}
}
}
void Game::refreshQuestJournalUi() {
visibleQuestIds.clear();
auto quests = questJournal.getVisibleQuests();
std::sort(quests.begin(), quests.end(), [](const Quest::QuestState* a, const Quest::QuestState* b) {
const int pa = questStatusPriority(a->status);
const int pb = questStatusPriority(b->status);
if (pa != pb) return pa < pb;
// Newer quests are shown above older quests inside the same status bucket.
return a->orderIndex > b->orderIndex;
});
for (int i = 0; i < 9; ++i) {
const std::string slotName = "quest_slot_" + std::to_string(i);
if (i < static_cast<int>(quests.size())) {
const auto* quest = quests[i];
visibleQuestIds.push_back(quest->definition.id);
const bool selected = (i == selectedQuestIndex);
const std::string prefix = selected ? "> " : " ";
menuManager.uiManager.setTextButtonText(slotName, prefix + quest->definition.title);
menuManager.uiManager.setTextButtonColor(slotName, questStatusColor(quest->status));
menuManager.uiManager.setNodeVisible(slotName, true);
}
else {
menuManager.uiManager.setTextButtonText(slotName, "");
menuManager.uiManager.setNodeVisible(slotName, false);
}
}
}
void Game::selectQuestByIndex(int index) {
if (index < 0 || index >= static_cast<int>(visibleQuestIds.size())) {
return;
}
selectedQuestIndex = index;
Quest::QuestState* quest = questJournal.findQuest(visibleQuestIds[index]);
if (!quest) {
return;
}
const auto& def = quest->definition;
menuManager.uiManager.setText("quest_middle_title_text", def.title);
menuManager.uiManager.setTextColor("quest_middle_title_text", questStatusColor(quest->status));
const std::string meta = std::string("Category: ") + Quest::toString(def.category)
+ " | Status: " + Quest::toString(quest->status)
+ " | Level: " + std::to_string(def.recommendedLevel);
menuManager.uiManager.setText("quest_meta_text", meta);
std::string objectivesText;
for (size_t i = 0; i < def.objectives.size(); ++i) {
const auto& obj = def.objectives[i];
const bool isActive = static_cast<int>(i) == quest->activeObjectiveIndex;
const std::string mark = obj.completed ? "[x] " : (isActive ? "> [ ] " : "[ ] ");
objectivesText += mark + obj.text;
if (i + 1 < def.objectives.size()) {
objectivesText += "\n";
}
}
menuManager.uiManager.setText("quest_objectives_text", objectivesText);
menuManager.uiManager.setText("quest_lore_title_text", "Описание задания");
menuManager.uiManager.setText("quest_description_text", def.description);
refreshQuestJournalUi();
}
void Game::drawScene() { void Game::drawScene() {
glViewport(0, 0, Environment::width, Environment::height); glViewport(0, 0, Environment::width, Environment::height);
if (!loadingCompleted) { if (!loadingCompleted) {
@ -702,7 +519,7 @@ namespace ZL
break; break;
case SDLK_j: case SDLK_j:
toggleQuestJournal(); menuManager.toggleQuestJournal();
break; break;
case SDLK_RETURN: case SDLK_RETURN:

View File

@ -20,8 +20,6 @@
#include <unordered_set> #include <unordered_set>
#include "Location.h" #include "Location.h"
#include "AudioPlayerAsync.h" #include "AudioPlayerAsync.h"
#include "quest/QuestJournal.h"
namespace ZL { namespace ZL {
class Game { class Game {
@ -51,13 +49,6 @@ namespace ZL {
Inventory inventory; Inventory inventory;
InteractiveObject* pickedUpObject = nullptr; InteractiveObject* pickedUpObject = nullptr;
bool inventoryOpen = false;
ZL::Quest::QuestJournal questJournal;
bool questJournalOpen = false;
int selectedQuestIndex = -1;
std::vector<std::string> visibleQuestIds;
MenuManager menuManager; MenuManager menuManager;
void activateSlowMoEffect(); void activateSlowMoEffect();
@ -107,11 +98,6 @@ namespace ZL {
void endPinch(); void endPinch();
int countNonUiPointers() const; int countNonUiPointers() const;
void setupQuestJournalUi();
void toggleQuestJournal();
void refreshQuestJournalUi();
void selectQuestByIndex(int index);
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
static Game* s_instance; static Game* s_instance;
static void onResourcesZipLoaded(const char* filename); static void onResourcesZipLoaded(const char* filename);

View File

@ -157,6 +157,7 @@ namespace ZL
teleportSparks->setUseWorldSpace(true); teleportSparks->setUseWorldSpace(true);
teleportSparks->markConfigured(); teleportSparks->markConfigured();
// If the player happens to spawn already inside the zone, treat them // If the player happens to spawn already inside the zone, treat them
// as in-zone so they don't immediately teleport on the first update. // as in-zone so they don't immediately teleport on the first update.
if (player && (player->position - teleportPosition).norm() <= teleportRadius) { if (player && (player->position - teleportPosition).norm() <= teleportRadius) {
@ -957,7 +958,7 @@ namespace ZL
// Check if we clicked on an NPC // Check if we clicked on an NPC
Character* clickedNpc = raycastNpcs(camPos, rayDir); Character* clickedNpc = raycastNpcs(camPos, rayDir);
if (clickedNpc && player) { if (clickedNpc && player && clickedNpc->hp > 0) {
float distance = (player->position - clickedNpc->position).norm(); float distance = (player->position - clickedNpc->position).norm();
int npcIndex = -1; int npcIndex = -1;
for (size_t i = 0; i < npcs.size(); ++i) { for (size_t i = 0; i < npcs.size(); ++i) {
@ -966,7 +967,7 @@ namespace ZL
break; break;
} }
} }
if (npcIndex != -1 && clickedNpc->hp > 0) { if (npcIndex != -1) {
targetInteractiveObject = nullptr; targetInteractiveObject = nullptr;
if (clickedNpc->canAttack) { if (clickedNpc->canAttack) {

View File

@ -1,392 +1,196 @@
#include "MenuManager.h" #include "MenuManager.h"
#include <iostream> #include <iostream>
#include <algorithm>
#ifdef EMSCRIPTEN #include <string>
#include <emscripten.h>
#include <cstdlib>
#endif
namespace ZL { namespace ZL {
extern bool inverseVertical; static int questStatusPriority(Quest::QuestStatus status) {
switch (status) {
case Quest::QuestStatus::Active: return 0;
case Quest::QuestStatus::Available: return 1;
case Quest::QuestStatus::Completed: return 2;
case Quest::QuestStatus::Failed: return 3;
default: return 4;
}
}
static std::array<float, 4> questStatusColor(Quest::QuestStatus status) {
switch (status) {
case Quest::QuestStatus::Completed: return { 0.25f, 0.95f, 0.35f, 1.0f };
case Quest::QuestStatus::Failed: return { 1.0f, 0.25f, 0.25f, 1.0f };
case Quest::QuestStatus::Active: return { 1.0f, 1.0f, 1.0f, 1.0f };
case Quest::QuestStatus::Available: return { 0.86f, 0.86f, 0.86f, 1.0f };
default: return { 0.45f, 0.45f, 0.45f, 1.0f };
}
}
MenuManager::MenuManager(Renderer& iRenderer) : MenuManager::MenuManager(Renderer& iRenderer) :
renderer(iRenderer) renderer(iRenderer)
{ {
} }
/* void MenuManager::setup(Inventory& inv, const std::string& zipFile) {
void MenuManager::setupMenu() inventory = &inv;
{
mainMenuRoot = loadUiFromFile("resources/config/main_menu.json", renderer, CONST_ZIP_FILE);
aboutMenuRoot = loadUiFromFile("resources/config/about.json", renderer, CONST_ZIP_FILE);
shipSelectionRoot = loadUiFromFile("resources/config/ship_selection_menu.json", renderer, CONST_ZIP_FILE);
connectingRoot = loadUiFromFile("resources/config/connecting.json", renderer, CONST_ZIP_FILE);
connectionFailedRoot= loadUiFromFile("resources/config/connection_failed.json", renderer, CONST_ZIP_FILE);
gameplayRoot = loadUiFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);
gameOverRoot = loadUiFromFile("resources/config/game_over.json", renderer, CONST_ZIP_FILE);
helpScreenRoot = loadUiFromFile("resources/config/ui_with_help.json", renderer, CONST_ZIP_FILE);
connectionLostRoot = loadUiFromFile("resources/config/connection_lost.json", renderer, CONST_ZIP_FILE);
enterMainMenu(); hudRoot = loadUiFromFile("resources/config2/hud.json", renderer, zipFile);
} inventoryRoot = loadUiFromFile("resources/config2/ui_inventory.json", renderer, zipFile);
questJournalRoot = loadUiFromFile("resources/config2/ui_quest_journal.json", renderer, zipFile);
bool MenuManager::shouldRenderSpace() const questJournal.loadFromFile("resources/quests/quests.json", zipFile);
{
return state == GameState::Gameplay
|| state == GameState::GameOver
|| state == GameState::HelpScreen
|| state == GameState::ConnectionLost;
}
// ── State: MainMenu ──────────────────────────────────────────────────────
void MenuManager::enterMainMenu()
{
state = GameState::MainMenu;
uiManager.replaceRoot(mainMenuRoot);
if (onMainMenuEntered) onMainMenuEntered();
uiManager.setButtonCallback("singleButton", [this](const std::string&) {
enterShipSelectionSingle();
});
uiManager.setButtonCallback("multiplayerButton", [this](const std::string&) {
enterShipSelectionMulti();
});
uiManager.setButtonCallback("aboutButton", [this](const std::string&) {
enterAboutMenu();
});
}
void MenuManager::enterAboutMenu()
{
state = GameState::AboutMenu;
uiManager.replaceRoot(aboutMenuRoot);
uiManager.setButtonCallback("aboutBackButton", [this](const std::string&) {
enterMainMenu();
});
}
// ── State: ShipSelectionSingle ───────────────────────────────────────────
void MenuManager::enterShipSelectionSingle()
{
state = GameState::ShipSelectionSingle;
uiManager.replaceRoot(shipSelectionRoot);
std::string initialNick;
#ifdef EMSCRIPTEN
char* savedNickC = emscripten_run_script_string("localStorage.getItem('spacegame_nick') || ''");
if (savedNickC) {
initialNick = savedNickC;
free(savedNickC);
}
#endif
auto tf = uiManager.findTextField("nicknameInput");
if (tf) {
if (!initialNick.empty()) tf->text = initialNick;
#ifdef EMSCRIPTEN
uiManager.setTextFieldCallback("nicknameInput", [](const std::string&, const std::string& value) {
EM_ASM_({
try { localStorage.setItem('spacegame_nick', UTF8ToString($0)); } catch(e) {}
}, value.c_str());
});
#endif
}
uiManager.setButtonCallback("spaceshipButton", [this, initialNick](const std::string&) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = initialNick;
if (nick.empty()) nick = "Player";
enterGameplay(); enterGameplay();
if (onSingleplayerPressed) onSingleplayerPressed(nick, 0);
});
uiManager.setButtonCallback("cargoshipButton", [this, initialNick](const std::string&) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = initialNick;
if (nick.empty()) nick = "Player";
enterGameplay();
if (onSingleplayerPressed) onSingleplayerPressed(nick, 1);
});
uiManager.setButtonCallback("backButton", [this](const std::string&) {
enterMainMenu();
});
} }
// ── State: ShipSelectionMulti ───────────────────────────────────────────── void MenuManager::enterGameplay() {
void MenuManager::enterShipSelectionMulti()
{
state = GameState::ShipSelectionMulti;
uiManager.replaceRoot(shipSelectionRoot);
std::string initialNick;
#ifdef EMSCRIPTEN
char* savedNickC = emscripten_run_script_string("localStorage.getItem('spacegame_nick') || ''");
if (savedNickC) {
initialNick = savedNickC;
free(savedNickC);
}
#endif
auto tf = uiManager.findTextField("nicknameInput");
if (tf) {
if (!initialNick.empty()) tf->text = initialNick;
#ifdef EMSCRIPTEN
uiManager.setTextFieldCallback("nicknameInput", [](const std::string&, const std::string& value) {
EM_ASM_({
try { localStorage.setItem('spacegame_nick', UTF8ToString($0)); } catch(e) {}
}, value.c_str());
});
#endif
}
uiManager.setButtonCallback("spaceshipButton", [this, initialNick](const std::string&) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = initialNick;
if (nick.empty()) nick = "Player";
pendingMultiNick = nick;
pendingMultiShipType = 0;
enterConnecting();
if (onMultiplayerPressed) onMultiplayerPressed(nick, 0);
});
uiManager.setButtonCallback("cargoshipButton", [this, initialNick](const std::string&) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = initialNick;
if (nick.empty()) nick = "Player";
pendingMultiNick = nick;
pendingMultiShipType = 1;
enterConnecting();
if (onMultiplayerPressed) onMultiplayerPressed(nick, 1);
});
uiManager.setButtonCallback("backButton", [this](const std::string&) {
enterMainMenu();
});
}
// ── State: Connecting ────────────────────────────────────────────────────
void MenuManager::enterConnecting()
{
state = GameState::Connecting;
uiManager.replaceRoot(connectingRoot);
// No interactive elements — just a static "Connecting..." image
}
// ── State: ConnectionFailed ───────────────────────────────────────────────
void MenuManager::enterConnectionFailed()
{
state = GameState::ConnectionFailed;
uiManager.replaceRoot(connectionFailedRoot);
uiManager.setButtonCallback("connectionFailedReconnectButton", [this](const std::string&) {
enterConnecting();
if (onMultiplayerPressed) onMultiplayerPressed(pendingMultiNick, pendingMultiShipType);
});
uiManager.setButtonCallback("connectionFailedGoBack", [this](const std::string&) {
enterMainMenu();
});
}
// ── State: Gameplay ──────────────────────────────────────────────────────
void MenuManager::enterGameplay()
{
state = GameState::Gameplay; state = GameState::Gameplay;
uiManager.replaceRoot(gameplayRoot); uiManager.replaceRoot(hudRoot);
if (Environment::shipState.shipType == 1) uiManager.setTextButtonCallback("inventory_button", [this](const std::string&) {
{ openInventory();
uiManager.findButton("shootButton")->state = ButtonState::Disabled;
uiManager.findButton("shootButton2")->state = ButtonState::Disabled;
}
else
{
if (Environment::shipState.velocity < 0.1)
{
uiManager.findButton("minusButton")->state = ButtonState::Disabled;
uiManager.findButton("plusButton")->state = ButtonState::Normal;
uiManager.findButton("shootButton")->state = ButtonState::Normal;
uiManager.findButton("shootButton2")->state = ButtonState::Normal;
}
else if (Environment::shipState.velocity >= 0.1 && Environment::shipState.velocity <= 200)
{
uiManager.findButton("minusButton")->state = ButtonState::Normal;
uiManager.findButton("plusButton")->state = ButtonState::Normal;
uiManager.findButton("shootButton")->state = ButtonState::Normal;
uiManager.findButton("shootButton2")->state = ButtonState::Normal;
}
else if (Environment::shipState.velocity > 200 && Environment::shipState.velocity < 400 - 0.1)
{
uiManager.findButton("minusButton")->state = ButtonState::Normal;
uiManager.findButton("plusButton")->state = ButtonState::Normal;
uiManager.findButton("shootButton")->state = ButtonState::Disabled;
uiManager.findButton("shootButton2")->state = ButtonState::Disabled;
}
else if (Environment::shipState.velocity >= 400 - 0.1)
{
uiManager.findButton("minusButton")->state = ButtonState::Normal;
uiManager.findButton("plusButton")->state = ButtonState::Disabled;
uiManager.findButton("shootButton")->state = ButtonState::Disabled;
uiManager.findButton("shootButton2")->state = ButtonState::Disabled;
}
}
if (forceSetupSpaceUICallback)
{
forceSetupSpaceUICallback();
}
if (auto btn = uiManager.findButton("takeButton")) btn->state = ButtonState::Disabled;
if (auto btn = uiManager.findButton("showPlayersButton"))
{
btn->state = ButtonState::Disabled;
}
uiManager.setButtonPressCallback("shootButton", [this](const std::string&) {
if (onFirePressed) onFirePressed();
});
uiManager.setButtonPressCallback("shootButton2", [this](const std::string&) {
if (onFirePressed) onFirePressed();
});
uiManager.setButtonPressCallback("plusButton", [this](const std::string&) {
int newVel = Environment::shipState.selectedVelocity + 1;
if (newVel > 4) newVel = 4;
uiManager.findButton("minusButton")->state = ButtonState::Normal;
if (newVel == 4)
{
uiManager.findButton("plusButton")->state = ButtonState::Disabled;
}
else
{
uiManager.findButton("plusButton")->state = ButtonState::Normal;
}
if (onVelocityChanged) onVelocityChanged(newVel);
});
uiManager.setButtonPressCallback("minusButton", [this](const std::string&) {
int newVel = Environment::shipState.selectedVelocity - 1;
if (newVel < 0) newVel = 0;
uiManager.findButton("plusButton")->state = ButtonState::Normal;
if (newVel == 0)
{
uiManager.findButton("minusButton")->state = ButtonState::Disabled;
}
else
{
uiManager.findButton("minusButton")->state = ButtonState::Normal;
}
if (onVelocityChanged) onVelocityChanged(newVel);
}); });
uiManager.setButtonPressCallback("takeButton", [this](const std::string&) { uiManager.setTextButtonCallback("quest_journal_button", [this](const std::string&) {
if (onTakeButtonPressed) onTakeButtonPressed(); openQuestJournal();
});
uiManager.setButtonCallback("showPlayersButton", [this](const std::string&) {
if (onShowPlayersPressed) onShowPlayersPressed();
});
uiManager.setButtonPressCallback("inverseMouseButton", [this](const std::string&) {
inverseVertical = !inverseVertical;
std::cout << "Inverse mouse: " << (inverseVertical ? "ON" : "OFF") << std::endl;
});
uiManager.setButtonCallback("infoButton", [this](const std::string&) {
//if (onShowPlayersPressed) onShowPlayersPressed();
enterHelp();
});
}
// ── State: GameOver ──────────────────────────────────────────────────────
void MenuManager::enterGameOver(int score)
{
state = GameState::GameOver;
uiManager.replaceRoot(gameOverRoot);
uiManager.setText("scoreText", "Score: " + std::to_string(score));
uiManager.setButtonCallback("restartButton", [this](const std::string&) {
if (onRestartPressed) onRestartPressed();
enterGameplay();
});
uiManager.setButtonCallback("gameOverExitButton", [this](const std::string&) {
enterMainMenu();
}); });
} }
void MenuManager::enterHelp() void MenuManager::openInventory() {
{ state = GameState::Inventory;
state = GameState::HelpScreen; uiManager.pushMenuFromSavedRoot(inventoryRoot);
uiManager.replaceRoot(helpScreenRoot);
uiManager.setButtonCallback("infoButtonUnderlying_help", [this](const std::string&) { uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string&) {
enterGameplay(); closeInventory();
});
const auto& items = inventory->getItems();
std::string itemText;
if (items.empty()) {
itemText = "Inventory (Empty)";
}
else {
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
for (size_t i = 0; i < items.size(); ++i) {
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
}
}
uiManager.setText("inventory_items_text", itemText);
}
void MenuManager::closeInventory() {
state = GameState::Gameplay;
uiManager.popMenu();
}
void MenuManager::openQuestJournal() {
state = GameState::QuestJournal;
uiManager.pushMenuFromSavedRoot(questJournalRoot);
uiManager.setTextButtonCallback("quest_close_button", [this](const std::string&) {
closeQuestJournal();
});
for (int i = 0; i < 9; ++i) {
const std::string slotName = "quest_slot_" + std::to_string(i);
uiManager.setTextButtonCallback(slotName, [this, i](const std::string&) {
selectQuestByIndex(i);
}); });
} }
// ── State: ConnectionLost ───────────────────────────────────────────────── refreshQuestJournalUi();
if (!visibleQuestIds.empty()) {
selectQuestByIndex(0);
}
}
void MenuManager::enterConnectionLost() void MenuManager::closeQuestJournal() {
{ state = GameState::Gameplay;
state = GameState::ConnectionLost; selectedQuestIndex = -1;
uiManager.replaceRoot(connectionLostRoot); visibleQuestIds.clear();
uiManager.popMenu();
}
uiManager.setButtonCallback("reconnectButton", [this](const std::string&) { void MenuManager::toggleQuestJournal() {
enterConnecting(); std::cout << "[quest] toggleQuestJournal: " << (isQuestJournalOpen() ? "closing" : "opening") << std::endl;
if (onMultiplayerPressed) onMultiplayerPressed(pendingMultiNick, pendingMultiShipType); if (state == GameState::QuestJournal) {
}); closeQuestJournal();
uiManager.setButtonCallback("exitServerButton", [this](const std::string&) { }
enterMainMenu(); else {
if (state == GameState::Inventory) {
closeInventory();
}
openQuestJournal();
}
}
void MenuManager::refreshQuestJournalUi() {
visibleQuestIds.clear();
auto quests = questJournal.getVisibleQuests();
std::sort(quests.begin(), quests.end(), [](const Quest::QuestState* a, const Quest::QuestState* b) {
const int pa = questStatusPriority(a->status);
const int pb = questStatusPriority(b->status);
if (pa != pb) return pa < pb;
return a->orderIndex > b->orderIndex;
}); });
for (int i = 0; i < 9; ++i) {
const std::string slotName = "quest_slot_" + std::to_string(i);
if (i < static_cast<int>(quests.size())) {
const auto* quest = quests[i];
visibleQuestIds.push_back(quest->definition.id);
const bool selected = (i == selectedQuestIndex);
const std::string prefix = selected ? "> " : " ";
uiManager.setTextButtonText(slotName, prefix + quest->definition.title);
uiManager.setTextButtonColor(slotName, questStatusColor(quest->status));
uiManager.setNodeVisible(slotName, true);
}
else {
uiManager.setTextButtonText(slotName, "");
uiManager.setNodeVisible(slotName, false);
} }
// ── Public event API ──────────────────────────────────────────────────────
void MenuManager::notifyConnected()
{
if (state == GameState::Connecting) {
enterGameplay();
} }
} }
void MenuManager::notifyConnectionFailed() void MenuManager::selectQuestByIndex(int index) {
{ if (index < 0 || index >= static_cast<int>(visibleQuestIds.size())) {
if (state == GameState::Connecting) { return;
enterConnectionFailed();
}
} }
void MenuManager::showGameOver(int score) selectedQuestIndex = index;
{ Quest::QuestState* quest = questJournal.findQuest(visibleQuestIds[index]);
if (state == GameState::Gameplay) { if (!quest) {
enterGameOver(score); return;
}
} }
void MenuManager::showConnectionLost() const auto& def = quest->definition;
{
if (state == GameState::Gameplay) { uiManager.setText("quest_middle_title_text", def.title);
enterConnectionLost(); uiManager.setTextColor("quest_middle_title_text", questStatusColor(quest->status));
const std::string meta = std::string("Category: ") + Quest::toString(def.category)
+ " | Status: " + Quest::toString(quest->status)
+ " | Level: " + std::to_string(def.recommendedLevel);
uiManager.setText("quest_meta_text", meta);
std::string objectivesText;
for (size_t i = 0; i < def.objectives.size(); ++i) {
const auto& obj = def.objectives[i];
const bool isActive = static_cast<int>(i) == quest->activeObjectiveIndex;
const std::string mark = obj.completed ? "[x] " : (isActive ? "> [ ] " : "[ ] ");
objectivesText += mark + obj.text;
if (i + 1 < def.objectives.size()) {
objectivesText += "\n";
}
}
uiManager.setText("quest_objectives_text", objectivesText);
uiManager.setText("quest_lore_title_text", "Описание задания");
uiManager.setText("quest_description_text", def.description);
refreshQuestJournalUi();
} }
}*/
} // namespace ZL } // namespace ZL

View File

@ -3,86 +3,58 @@
#include "Environment.h" #include "Environment.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"
#include "UiManager.h" #include "UiManager.h"
#include "items/Item.h"
#include "quest/QuestJournal.h"
#include <vector>
#include <string>
#include <memory>
namespace ZL { namespace ZL {
extern const char* CONST_ZIP_FILE; extern const char* CONST_ZIP_FILE;
enum class GameState { enum class GameState {
MainMenu,
AboutMenu,
ShipSelectionSingle,
ShipSelectionMulti,
Connecting,
ConnectionFailed,
Gameplay, Gameplay,
GameOver, Inventory,
HelpScreen, QuestJournal
ConnectionLost
}; };
class MenuManager { class MenuManager {
protected:
Renderer& renderer;
/*
// Pre-loaded UI roots (loaded once in setupMenu)
std::shared_ptr<UiNode> mainMenuRoot;
std::shared_ptr<UiNode> aboutMenuRoot;
std::shared_ptr<UiNode> shipSelectionRoot;
std::shared_ptr<UiNode> connectingRoot;
std::shared_ptr<UiNode> connectionFailedRoot;
std::shared_ptr<UiNode> gameplayRoot;
std::shared_ptr<UiNode> gameOverRoot;
std::shared_ptr<UiNode> helpScreenRoot;
std::shared_ptr<UiNode> connectionLostRoot;
// Stored for multiplayer retry
std::string pendingMultiNick;
int pendingMultiShipType = 0;
GameState state = GameState::MainMenu;
// State transition methods
void enterMainMenu();
void enterAboutMenu();
void enterShipSelectionSingle();
void enterShipSelectionMulti();
void enterConnecting();
void enterConnectionFailed();
void enterGameplay();
void enterGameOver(int score);
void enterHelp();
void enterConnectionLost();
*/
public: public:
UiManager uiManager; UiManager uiManager;
ZL::Quest::QuestJournal questJournal;
MenuManager(Renderer& iRenderer); MenuManager(Renderer& iRenderer);
//void setupMenu(); void setup(Inventory& inv, const std::string& zipFile);
/* void openInventory();
// Returns true for states where Space should render and run (Gameplay, GameOver, ConnectionLost) void closeInventory();
bool shouldRenderSpace() const;
GameState getState() const { return state; }
// Called by game events void openQuestJournal();
void showGameOver(int score); void closeQuestJournal();
void showConnectionLost(); void toggleQuestJournal();
void notifyConnected();
void notifyConnectionFailed();
// Callbacks set by Game/Space bool isInventoryOpen() const { return state == GameState::Inventory; }
std::function<void()> onMainMenuEntered; bool isQuestJournalOpen() const { return state == GameState::QuestJournal; }
std::function<void()> onRestartPressed;
std::function<void(float)> onVelocityChanged;
std::function<void()> onFirePressed;
std::function<void()> onTakeButtonPressed;
std::function<void(const std::string&, int)> onSingleplayerPressed;
std::function<void(const std::string&, int)> onMultiplayerPressed;
std::function<void()> onShowPlayersPressed;
std::function<void()> forceSetupSpaceUICallback;*/ protected:
Renderer& renderer;
private:
void enterGameplay();
void refreshQuestJournalUi();
void selectQuestByIndex(int index);
GameState state = GameState::Gameplay;
Inventory* inventory = nullptr;
std::shared_ptr<UiNode> hudRoot;
std::shared_ptr<UiNode> inventoryRoot;
std::shared_ptr<UiNode> questJournalRoot;
int selectedQuestIndex = -1;
std::vector<std::string> visibleQuestIds;
}; };
} // namespace ZL } // namespace ZL