Fixing bugs, added girl

This commit is contained in:
Vladislav Khorev 2026-05-02 20:34:43 +03:00
parent d24748df1e
commit 6149182f02
21 changed files with 1255815 additions and 654 deletions

View File

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

View File

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

View File

@ -22,6 +22,24 @@
"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"
}
},
{
"type": "FrameLayout",

View File

@ -5,24 +5,6 @@
"width": "match_parent",
"height": "match_parent",
"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",
"name": "quest_journal_panel",

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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()) {
hitSparkEmitter.update(static_cast<float>(deltaMs));
}

View File

@ -236,51 +236,9 @@ namespace ZL
std::cout << "Load resurces step 13" << std::endl;
// Load UI with inventory button
try {
menuManager.uiManager.loadFromFile("resources/config2/ui_inventory.json", renderer, 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();
menuManager.setup(inventory, CONST_ZIP_FILE);
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) {
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() {
glViewport(0, 0, Environment::width, Environment::height);
if (!loadingCompleted) {
@ -702,7 +519,7 @@ namespace ZL
break;
case SDLK_j:
toggleQuestJournal();
menuManager.toggleQuestJournal();
break;
case SDLK_RETURN:

View File

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

View File

@ -157,6 +157,7 @@ namespace ZL
teleportSparks->setUseWorldSpace(true);
teleportSparks->markConfigured();
// 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.
if (player && (player->position - teleportPosition).norm() <= teleportRadius) {
@ -957,7 +958,7 @@ namespace ZL
// Check if we clicked on an NPC
Character* clickedNpc = raycastNpcs(camPos, rayDir);
if (clickedNpc && player) {
if (clickedNpc && player && clickedNpc->hp > 0) {
float distance = (player->position - clickedNpc->position).norm();
int npcIndex = -1;
for (size_t i = 0; i < npcs.size(); ++i) {
@ -966,7 +967,7 @@ namespace ZL
break;
}
}
if (npcIndex != -1 && clickedNpc->hp > 0) {
if (npcIndex != -1) {
targetInteractiveObject = nullptr;
if (clickedNpc->canAttack) {

View File

@ -1,392 +1,198 @@
#include "MenuManager.h"
#include <iostream>
#ifdef EMSCRIPTEN
#include <emscripten.h>
#include <cstdlib>
#endif
#include <algorithm>
#include <string>
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) :
renderer(iRenderer)
{
}
/*
void MenuManager::setupMenu()
{
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);
void MenuManager::setup(Inventory& inv, const std::string& zipFile) {
inventory = &inv;
enterMainMenu();
}
ingameRoot = loadUiFromFile("resources/config2/ui_inventory.json", renderer, zipFile);
questJournalRoot = loadUiFromFile("resources/config2/ui_quest_journal.json", renderer, zipFile);
bool MenuManager::shouldRenderSpace() const
{
return state == GameState::Gameplay
|| state == GameState::GameOver
|| state == GameState::HelpScreen
|| state == GameState::ConnectionLost;
}
questJournal.loadFromFile("resources/quests/quests.json", zipFile);
// ── 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();
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::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()
{
void MenuManager::enterGameplay() {
state = GameState::Gameplay;
uiManager.replaceRoot(gameplayRoot);
uiManager.replaceRoot(ingameRoot);
if (Environment::shipState.shipType == 1)
{
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;
}
}
uiManager.setNodeVisible("inventory_items_panel", false);
uiManager.setNodeVisible("close_inventory_button", false);
inventoryOpen = false;
if (forceSetupSpaceUICallback)
{
forceSetupSpaceUICallback();
}
uiManager.setTextButtonCallback("inventory_button", [this](const std::string&) {
std::cout << "[UI] Inventory button clicked" << std::endl;
uiManager.setNodeVisible("inventory_items_panel", true);
uiManager.setNodeVisible("close_inventory_button", true);
inventoryOpen = true;
if (auto btn = uiManager.findButton("takeButton")) btn->state = ButtonState::Disabled;
if (auto btn = uiManager.findButton("showPlayersButton"))
{
btn->state = ButtonState::Disabled;
const auto& items = inventory->getItems();
std::string itemText;
if (items.empty()) {
itemText = "Inventory (Empty)";
}
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 {
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";
}
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.setText("inventory_items_text", itemText);
});
uiManager.setButtonPressCallback("takeButton", [this](const std::string&) {
if (onTakeButtonPressed) onTakeButtonPressed();
uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string&) {
std::cout << "[UI] Close button clicked" << std::endl;
uiManager.setNodeVisible("inventory_items_panel", false);
uiManager.setNodeVisible("close_inventory_button", false);
inventoryOpen = false;
});
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();
uiManager.setTextButtonCallback("quest_journal_button", [this](const std::string&) {
openQuestJournal();
});
}
void MenuManager::enterHelp()
{
state = GameState::HelpScreen;
uiManager.replaceRoot(helpScreenRoot);
void MenuManager::openQuestJournal() {
if (inventoryOpen) {
uiManager.setNodeVisible("inventory_items_panel", false);
uiManager.setNodeVisible("close_inventory_button", false);
inventoryOpen = false;
}
uiManager.setButtonCallback("infoButtonUnderlying_help", [this](const std::string&) {
enterGameplay();
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()
{
state = GameState::ConnectionLost;
uiManager.replaceRoot(connectionLostRoot);
void MenuManager::closeQuestJournal() {
state = GameState::Gameplay;
selectedQuestIndex = -1;
visibleQuestIds.clear();
uiManager.popMenu();
}
uiManager.setButtonCallback("reconnectButton", [this](const std::string&) {
enterConnecting();
if (onMultiplayerPressed) onMultiplayerPressed(pendingMultiNick, pendingMultiShipType);
});
uiManager.setButtonCallback("exitServerButton", [this](const std::string&) {
enterMainMenu();
void MenuManager::toggleQuestJournal() {
std::cout << "[quest] toggleQuestJournal: " << (isQuestJournalOpen() ? "closing" : "opening") << std::endl;
if (isQuestJournalOpen()) {
closeQuestJournal();
}
else {
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()
{
if (state == GameState::Connecting) {
enterConnectionFailed();
}
void MenuManager::selectQuestByIndex(int index) {
if (index < 0 || index >= static_cast<int>(visibleQuestIds.size())) {
return;
}
void MenuManager::showGameOver(int score)
{
if (state == GameState::Gameplay) {
enterGameOver(score);
}
selectedQuestIndex = index;
Quest::QuestState* quest = questJournal.findQuest(visibleQuestIds[index]);
if (!quest) {
return;
}
void MenuManager::showConnectionLost()
{
if (state == GameState::Gameplay) {
enterConnectionLost();
const auto& def = quest->definition;
uiManager.setText("quest_middle_title_text", def.title);
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

View File

@ -3,86 +3,51 @@
#include "Environment.h"
#include "render/TextureManager.h"
#include "UiManager.h"
#include "items/Item.h"
#include "quest/QuestJournal.h"
#include <vector>
#include <string>
#include <memory>
namespace ZL {
extern const char* CONST_ZIP_FILE;
enum class GameState {
MainMenu,
AboutMenu,
ShipSelectionSingle,
ShipSelectionMulti,
Connecting,
ConnectionFailed,
Gameplay,
GameOver,
HelpScreen,
ConnectionLost
QuestJournal
};
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:
UiManager uiManager;
ZL::Quest::QuestJournal questJournal;
MenuManager(Renderer& iRenderer);
//void setupMenu();
void setup(Inventory& inv, const std::string& zipFile);
void openQuestJournal();
void closeQuestJournal();
void toggleQuestJournal();
bool isQuestJournalOpen() const { return state == GameState::QuestJournal; }
/*
// Returns true for states where Space should render and run (Gameplay, GameOver, ConnectionLost)
bool shouldRenderSpace() const;
GameState getState() const { return state; }
protected:
Renderer& renderer;
// Called by game events
void showGameOver(int score);
void showConnectionLost();
void notifyConnected();
void notifyConnectionFailed();
private:
void enterGameplay();
void refreshQuestJournalUi();
void selectQuestByIndex(int index);
// Callbacks set by Game/Space
std::function<void()> onMainMenuEntered;
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;
GameState state = GameState::Gameplay;
Inventory* inventory = nullptr;
std::function<void()> forceSetupSpaceUICallback;*/
std::shared_ptr<UiNode> ingameRoot;
std::shared_ptr<UiNode> questJournalRoot;
bool inventoryOpen = false;
int selectedQuestIndex = -1;
std::vector<std::string> visibleQuestIds;
};
} // namespace ZL