added inventory & active object

This commit is contained in:
Vlad 2026-04-09 15:33:55 +06:00
parent e1b491be73
commit b3fda88784
16 changed files with 793 additions and 98 deletions

View File

@ -76,6 +76,10 @@ add_executable(space-game001
../src/items/GameObjectLoader.h ../src/items/GameObjectLoader.h
../src/items/GameObjectLoader.cpp ../src/items/GameObjectLoader.cpp
../src/items/Item.h
../src/items/Item.cpp
../src/items/InteractiveObject.h
../src/items/InteractiveObject.cpp
) )
# Установка проекта по умолчанию для Visual Studio # Установка проекта по умолчанию для Visual Studio

View File

@ -10,7 +10,8 @@
"positionX": 0.0, "positionX": 0.0,
"positionY": 0.0, "positionY": 0.0,
"positionZ": 0.0, "positionZ": 0.0,
"scale": 1.0 "scale": 1.0,
"interactive": false
}, },
{ {
"name": "inai", "name": "inai",
@ -22,7 +23,8 @@
"positionX": 2.5, "positionX": 2.5,
"positionY": 1.4, "positionY": 1.4,
"positionZ": -9.9, "positionZ": -9.9,
"scale": 1.0 "scale": 1.0,
"interactive": false
}, },
{ {
"name": "bench", "name": "bench",
@ -34,7 +36,29 @@
"positionX": -2.1, "positionX": -2.1,
"positionY": 0.5, "positionY": 0.5,
"positionZ": -7.9, "positionZ": -7.9,
"scale": 3.0 "scale": 3.0,
"interactive": false
},
{
"name": "medik",
"texturePath": "resources/fire2.png",
"meshPath": "resources/w/firebox.txt",
"rotationX": 0.0,
"rotationY": -1.5707963267948966,
"rotationZ": 0.0,
"positionX": 0.0,
"positionY": 0.0,
"positionZ": 4.0,
"scale": 1.0,
"interactive": true,
"item": {
"id": "health_potion",
"name": "Health Potion",
"description": "Restores 50 HP",
"icon": "resources/fire2.png",
"radius": 2.0
},
"activateFunction" : "on_health_pickup"
} }
] ]
} }

View File

@ -0,0 +1,67 @@
{
"root": {
"type": "FrameLayout",
"name": "inventory_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/connecting.png",
"hover": "resources/connecting.png",
"pressed": "resources/connecting.png"
}
},
{
"type": "FrameLayout",
"name": "inventory_items_panel",
"x": 100.0,
"y": 150.0,
"width": 500.0,
"height": 600.0,
"children": [
{
"type": "TextView",
"name": "inventory_items_text",
"x": 100.0,
"y": 30.0,
"width": 460.0,
"height": 450.0,
"text": "Inventory (Empty)",
"fontSize": 18,
"fontPath": "resources/fonts/DroidSans.ttf",
"centered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextButton",
"name": "close_inventory_button",
"x": 200.0,
"y": 20.0,
"width": 40.0,
"height": 40.0,
"text": "X",
"fontSize": 20,
"fontPath": "resources/fonts/DroidSans.ttf",
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0],
"textures": {
"normal": "resources/spark.png"
}
}
]
}
]
}
}

View File

@ -1,5 +1,6 @@
-- Chain of waypoints for NPC 0. -- ============================================
-- Each on_arrived callback issues the next walk command. -- NPC PATROL WAYPOINTS
-- ============================================
local function step3() local function step3()
game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1) game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1)
@ -14,3 +15,25 @@ function step1()
end end
step1() step1()
-- ============================================
-- ITEM PICKUP HANDLER
-- ============================================
function on_health_pickup()
game_api.pickup_item("health_potion")
end
function on_item_pickup(object_name)
print("========================================")
print("on_item_pickup CALLBACK CALLED!")
print("Player picked up item from: " .. tostring(object_name))
print("========================================")
game_api.pickup_item(object_name)
--game_api.show_inventory()
end
print("Lua script loaded successfully!")

View File

@ -148,34 +148,11 @@ namespace ZL
roomMesh.RefreshVBO(); roomMesh.RefreshVBO();
std::cout << "Load resurces step 5" << std::endl; std::cout << "Load resurces step 5" << std::endl;
// Load static game objects
gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE); gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
/* // Load interactive objects
std::cout << "Load resurces step 5" << std::endl; interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
fireboxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/Cube001.png", CONST_ZIP_FILE));
fireboxMesh.data = LoadFromTextFile02("resources/w/firebox.txt", CONST_ZIP_FILE);
fireboxMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
fireboxMesh.RefreshVBO();
std::cout << "Load resurces step 6" << std::endl;
inaiTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/inai001.png", CONST_ZIP_FILE));
std::cout << "Load resurces step 6.1" << std::endl;
inaiMesh.data = LoadFromTextFile02("resources/w/inai001.txt", CONST_ZIP_FILE);
inaiMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
inaiMesh.data.Move({ 2.5, 1.4, -9.9 });
inaiMesh.RefreshVBO();
std::cout << "Load resurces step 7" << std::endl;
benchTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/bench001opt.png", CONST_ZIP_FILE));
benchMesh.data = LoadFromTextFile02("resources/w/bench002opt.txt", CONST_ZIP_FILE);
benchMesh.data.Scale(3.f);
benchMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());
benchMesh.data.Move({ -2.1, 0.5, -7.9 });
benchMesh.RefreshVBO();
*/
std::cout << "Load resurces step 8" << std::endl;
auto violaTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/viola.png", CONST_ZIP_FILE)); auto violaTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/viola.png", CONST_ZIP_FILE));
@ -236,8 +213,91 @@ namespace ZL
scriptEngine.init(this); scriptEngine.init(this);
std::cout << "Load resurces step 13" << std::endl; 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);
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;
//scriptEngine.callItemPickupCallback("toggle_inventory");
scriptEngine.showInventory(this);
//scriptEngine
//scriptEngine
});
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;
}
} }
InteractiveObject* Game::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
if (interactiveObjects.empty()) {
std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
return nullptr;
}
std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
std::cout << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl;
std::cout << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
float closestDistance = FLT_MAX;
InteractiveObject* closestObject = nullptr;
for (auto& intObj : interactiveObjects) {
std::cout << "[RAYCAST] Checking object: " << intObj.name << " (active: " << intObj.isActive << ")" << std::endl;
if (!intObj.isActive) {
std::cout << "[RAYCAST] -> Object inactive, skipping" << std::endl;
continue;
}
std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
<< intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
Eigen::Vector3f toObject = intObj.position - rayOrigin;
std::cout << "[RAYCAST] Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl;
float distanceAlongRay = toObject.dot(rayDir);
std::cout << "[RAYCAST] Distance along ray: " << distanceAlongRay << std::endl;
if (distanceAlongRay < 0.1f) {
std::cout << "[RAYCAST] -> Object behind camera, skipping" << std::endl;
continue;
}
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
float distToObject = (closestPointOnRay - intObj.position).norm();
std::cout << "[RAYCAST] Distance to object: " << distToObject
<< " (interaction radius: " << intObj.interactionRadius << ")" << std::endl;
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
std::cout << "[RAYCAST] *** HIT DETECTED! ***" << std::endl;
closestDistance = distanceAlongRay;
closestObject = &intObj;
}
}
if (closestObject) {
std::cout << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->name << " ***" << std::endl;
} else {
std::cout << "[RAYCAST] No objects hit" << std::endl;
}
return closestObject;
}
void Game::drawUI() void Game::drawUI()
{ {
@ -291,6 +351,11 @@ namespace ZL
glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
renderer.DrawVertexRenderStruct(benchMesh); renderer.DrawVertexRenderStruct(benchMesh);
*/ */
for (auto& intObj : interactiveObjects) {
if (intObj.isActive) {
intObj.draw(renderer);
}
}
if (player) player->draw(renderer); if (player) player->draw(renderer);
for (auto& npc : npcs) npc->draw(renderer); for (auto& npc : npcs) npc->draw(renderer);
@ -374,9 +439,23 @@ namespace ZL
if (player) player->update(delta); if (player) player->update(delta);
for (auto& npc : npcs) npc->update(delta); for (auto& npc : npcs) npc->update(delta);
// Check if player reached target interactive object
if (targetInteractiveObject && player) {
float distToObject = (player->position - targetInteractiveObject->position).norm();
// If player is close enough to pick up the item
if (distToObject <= targetInteractiveObject->interactionRadius + 1.0f) {
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->id << std::endl;
// Call Lua callback
scriptEngine.callItemPickupCallback(targetInteractiveObject->id);
targetInteractiveObject = nullptr;
}
}
} }
} }
void Game::render() { void Game::render() {
@ -456,13 +535,14 @@ namespace ZL
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) { if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
if (event.button.button == SDL_BUTTON_LEFT) { if (event.button.button == SDL_BUTTON_LEFT) {
// Преобразуем экранные пиксели в проекционные единицы
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth); int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight); int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
if (event.type == SDL_MOUSEBUTTONDOWN) { if (event.type == SDL_MOUSEBUTTONDOWN) {
std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my); handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
// Unproject click to ground plane (y=0) for Viola's walk target // Calculate ray for picking
float ndcX = 2.0f * event.button.x / Environment::width - 1.0f; float ndcX = 2.0f * event.button.x / Environment::width - 1.0f;
float ndcY = 1.0f - 2.0f * event.button.y / Environment::height; float ndcY = 1.0f - 2.0f * event.button.y / Environment::height;
float aspect = (float)Environment::width / (float)Environment::height; float aspect = (float)Environment::width / (float)Environment::height;
@ -479,11 +559,33 @@ namespace ZL
Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized(); Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();
if (rayDir.y() < -0.001f && player) { std::cout << "[CLICK] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl;
std::cout << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
// First check if we clicked on interactive object
InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir);
if (clickedObject && player && clickedObject->isActive) {
std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->name << " ***" << std::endl;
std::cout << "[CLICK] Object position: (" << clickedObject->position.x() << ", "
<< clickedObject->position.y() << ", " << clickedObject->position.z() << ")" << std::endl;
std::cout << "[CLICK] Player position: (" << player->position.x() << ", "
<< player->position.y() << ", " << player->position.z() << ")" << std::endl;
targetInteractiveObject = clickedObject;
player->setTarget(clickedObject->position);
std::cout << "[CLICK] Player moving to object..." << std::endl;
}
else if (rayDir.y() < -0.001f && player) {
// Otherwise, unproject click to ground plane for Viola's walk target
float t = -camPos.y() / rayDir.y(); float t = -camPos.y() / rayDir.y();
Eigen::Vector3f hit = camPos + rayDir * t; Eigen::Vector3f hit = camPos + rayDir * t;
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z())); player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
} }
else {
std::cout << "[CLICK] No valid target found" << std::endl;
}
std::cout << "========================================\n" << std::endl;
} else { } else {
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my); handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
} }

View File

@ -10,6 +10,8 @@
#include "Projectile.h" #include "Projectile.h"
#include "utils/TaskManager.h" #include "utils/TaskManager.h"
#include "items/GameObjectLoader.h" #include "items/GameObjectLoader.h"
#include "items/Item.h"
#include "items/InteractiveObject.h"
#include <queue> #include <queue>
#include <vector> #include <vector>
#include <string> #include <string>
@ -61,12 +63,24 @@ namespace ZL {
std::shared_ptr<Texture> benchTexture; std::shared_ptr<Texture> benchTexture;
VertexRenderStruct benchMesh; VertexRenderStruct benchMesh;
*/ */
std::vector<InteractiveObject> interactiveObjects;
Inventory inventory;
InteractiveObject* pickedUpObject = nullptr;
// Interactive object targeting
InteractiveObject* targetInteractiveObject = nullptr;
bool inventoryOpen = false;
std::unique_ptr<Character> player; std::unique_ptr<Character> player;
std::vector<std::unique_ptr<Character>> npcs; std::vector<std::unique_ptr<Character>> npcs;
float cameraAzimuth = 0.0f; float cameraAzimuth = 0.0f;
float cameraInclination = M_PI * 30.f / 180.f; float cameraInclination = M_PI * 30.f / 180.f;
// Public access for ScriptEngine
MenuManager menuManager;
ScriptEngine scriptEngine;
private: private:
bool rightMouseDown = false; bool rightMouseDown = false;
int lastMouseX = 0; int lastMouseX = 0;
@ -83,6 +97,7 @@ namespace ZL {
void handleDown(int64_t fingerId, int mx, int my); void handleDown(int64_t fingerId, int mx, int my);
void handleUp(int64_t fingerId, int mx, int my); void handleUp(int64_t fingerId, int mx, int my);
void handleMotion(int64_t fingerId, int mx, int my); void handleMotion(int64_t fingerId, int mx, int my);
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
static Game* s_instance; static Game* s_instance;
@ -97,9 +112,6 @@ namespace ZL {
static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000; static const size_t CONST_MAX_TIME_INTERVAL = 1000;
MenuManager menuManager;
ScriptEngine scriptEngine;
}; };

View File

@ -7,55 +7,200 @@
namespace ZL { namespace ZL {
struct ScriptEngine::Impl { struct ScriptEngine::Impl {
sol::state lua; sol::state lua;
}; };
ScriptEngine::ScriptEngine() = default; ScriptEngine::ScriptEngine() = default;
ScriptEngine::~ScriptEngine() = default; ScriptEngine::~ScriptEngine() = default;
void ScriptEngine::init(Game* game) { void ScriptEngine::init(Game* game) {
impl = std::make_unique<Impl>(); impl = std::make_unique<Impl>();
sol::state& lua = impl->lua; sol::state& lua = impl->lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table); lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table);
auto api = lua.create_named_table("game_api"); auto api = lua.create_named_table("game_api");
// npc_walk_to(index, x, y, z [, on_arrived]) // npc_walk_to(index, x, y, z [, on_arrived])
// on_arrived is an optional Lua function called when the NPC reaches the target. // on_arrived is an optional Lua function called when the NPC reaches the target.
// It can call npc_walk_to again (or anything else) to chain behaviour. // It can call npc_walk_to again (or anything else) to chain behaviour.
api.set_function("npc_walk_to", api.set_function("npc_walk_to",
[game](int index, float x, float y, float z, sol::object on_arrived) { [game](int index, float x, float y, float z, sol::object on_arrived) {
auto& npcs = game->npcs; auto& npcs = game->npcs;
if (index < 0 || index >= static_cast<int>(npcs.size())) { if (index < 0 || index >= static_cast<int>(npcs.size())) {
std::cerr << "[script] npc_walk_to: index " << index std::cerr << "[script] npc_walk_to: index " << index
<< " out of range (0.." << npcs.size() - 1 << ")\n"; << " out of range (0.." << npcs.size() - 1 << ")\n";
return; return;
}
std::function<void()> cb;
if (on_arrived.is<sol::protected_function>()) {
sol::protected_function fn = on_arrived.as<sol::protected_function>();
cb = [fn]() mutable {
auto result = fn();
if (!result.valid()) {
sol::error err = result;
std::cerr << "[script] on_arrived error: " << err.what() << "\n";
}
};
}
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
});
// pickup_item(object_name)
api.set_function("pickup_item", [game](const std::string& objectName) {
std::cout << "[script] pickup_item: " << objectName << std::endl;
for (auto& intObj : game->interactiveObjects) {
if (intObj.id == objectName && intObj.isActive) {
// Add item to inventory
game->inventory.addItem(intObj.dropItem);
// Deactivate object
intObj.isActive = false;
std::cout << "[script] Item picked up successfully: " << intObj.dropItem.name << std::endl;
return;
}
} }
std::function<void()> cb; std::cerr << "[script] Warning: Interactive object not found or already picked up: " << objectName << std::endl;
if (on_arrived.is<sol::protected_function>()) { });
sol::protected_function fn = on_arrived.as<sol::protected_function>();
cb = [fn]() mutable {
auto result = fn(); // show_inventory()
if (!result.valid()) { api.set_function("show_inventory", [game]() {
sol::error err = result; std::cout << "[script] show_inventory called" << std::endl;
std::cerr << "[script] on_arrived error: " << err.what() << "\n";
} game->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
}; game->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
game->inventoryOpen = true;
// Update UI with current items
const auto& items = game->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";
}
} }
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
});
runScript("resources/start.lua"); game->menuManager.uiManager.setText("inventory_items_text", itemText);
} });
void ScriptEngine::runScript(const std::string& path) { // hide_inventory()
auto result = impl->lua.safe_script_file(path, sol::script_pass_on_error); api.set_function("hide_inventory", [game]() {
if (!result.valid()) { std::cout << "[script] hide_inventory called" << std::endl;
sol::error err = result; game->menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
std::cerr << "[script] Error in " << path << ": " << err.what() << "\n"; game->menuManager.uiManager.setNodeVisible("close_inventory_button", false);
game->inventoryOpen = false;
});
// add_item(item_id, name, description, icon)
api.set_function("add_item", [game](const std::string& id, const std::string& name,
const std::string& description, const std::string& icon) {
std::cout << "[script] add_item: " << name << std::endl;
Item newItem(id, name, description, icon);
game->inventory.addItem(newItem);
});
// remove_item(item_id)
api.set_function("remove_item", [game](const std::string& id) {
std::cout << "[script] remove_item: " << id << std::endl;
game->inventory.removeItem(id);
});
// deactivate_interactive_object(object_name)
api.set_function("deactivate_interactive_object", [game](const std::string& objectName) {
std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl;
for (auto& intObj : game->interactiveObjects) {
if (intObj.id == objectName) {
intObj.isActive = false;
std::cout << "[script] Interactive object deactivated: " << objectName << std::endl;
return;
}
}
std::cerr << "[script] Warning: Interactive object not found: " << objectName << std::endl;
});
// get_inventory_count()
api.set_function("get_inventory_count", [game]() {
return game->inventory.getCount();
});
// has_item(item_id)
api.set_function("has_item", [game](const std::string& id) {
return game->inventory.hasItem(id);
});
lua.script_file("resources/start.lua");
}
void ScriptEngine::runScript(const std::string& path) {
auto result = impl->lua.safe_script_file(path, sol::script_pass_on_error);
if (!result.valid()) {
sol::error err = result;
std::cerr << "[script] Error in " << path << ": " << err.what() << "\n";
}
}
void ScriptEngine::showInventory(Game* game) {
std::cout << "[script] toggle_inventory called" << std::endl;
bool isVisible = game->menuManager.uiManager.getNodeVisible("inventory_items_panel");
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", !isVisible);
game->menuManager.uiManager.setNodeVisible("close_inventory_button", !isVisible);
game->inventoryOpen = !isVisible;
// Update UI with current items only if showing
if (!isVisible) {
const auto& items = game->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";
}
}
game->menuManager.uiManager.setText("inventory_items_text", itemText);
}
}
void ScriptEngine::callItemPickupCallback(const std::string& objectName) {
if (!impl) {
std::cerr << "[SCRIPT] impl is null!" << std::endl;
return;
}
sol::state& lua = impl->lua;
std::cout << "[SCRIPT] Looking for callback: on_item_pickup" << std::endl;
sol::function pickup = lua["on_item_pickup"];
if (pickup.valid()) {
std::cout << "[SCRIPT] Callback found! Calling with argument: " << objectName << std::endl;
auto result = pickup(objectName);
if (!result.valid()) {
sol::error err = result;
std::cerr << "[SCRIPT] on_item_pickup callback error: " << err.what() << "\n";
}
else {
std::cout << "[SCRIPT] Callback executed successfully!" << std::endl;
}
}
else {
std::cerr << "[SCRIPT] on_item_pickup callback not found!" << std::endl;
}
} }
}
} // namespace ZL } // namespace ZL

View File

@ -17,6 +17,9 @@ public:
// Execute a Lua script file. Logs errors to stderr. // Execute a Lua script file. Logs errors to stderr.
void runScript(const std::string& path); void runScript(const std::string& path);
void callItemPickupCallback(const std::string& objectName);
void showInventory(Game* game);
private: private:
struct Impl; struct Impl;
std::unique_ptr<Impl> impl; std::unique_ptr<Impl> impl;

View File

@ -1092,24 +1092,55 @@ namespace ZL {
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
for (const auto& img : staticImages) { std::function<void(const std::shared_ptr<UiNode>&)> drawNode =
img->draw(renderer); [&](const std::shared_ptr<UiNode>& node) {
} if (!node || !node->visible) return;
for (const auto& b : buttons) {
b->draw(renderer); renderer.PushMatrix();
} renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f,
for (const auto& tb : textButtons) { node->screenRect.y + node->screenRect.h * 0.5f, 0.0f });
tb->draw(renderer); renderer.ScaleMatrix({ node->scaleX, node->scaleY, 1.0f });
} renderer.TranslateMatrix({ -(node->screenRect.x + node->screenRect.w * 0.5f),
for (const auto& s : sliders) { -(node->screenRect.y + node->screenRect.h * 0.5f), 0.0f });
s->draw(renderer);
}
for (const auto& tv : textViews) { // 1. Сначала изображения
tv->draw(renderer); if (node->staticImage) {
} node->staticImage->draw(renderer);
for (const auto& tf : textFields) { }
tf->draw(renderer);
} // 2. Потом кнопки
if (node->button) {
node->button->draw(renderer);
}
if (node->textButton) {
node->textButton->draw(renderer);
}
// 3. Потом слайдеры
if (node->slider) {
node->slider->draw(renderer);
}
// 4. Потом текстовые поля
if (node->textField) {
node->textField->draw(renderer);
}
// 5. ПОСЛЕДНИЙ - ТЕКСТ (поверх всего!)
if (node->textView) {
node->textView->draw(renderer);
}
// Рисуем детей
for (auto& child : node->children) {
drawNode(child);
}
renderer.PopMatrix();
};
drawNode(root);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
@ -1641,4 +1672,23 @@ namespace ZL {
return true; return true;
} }
std::shared_ptr<UiNode> UiManager::findNode(const std::string& name) {
if (!root) return nullptr;
return findNodeByName(root, name);
}
bool UiManager::setNodeVisible(const std::string& nodeName, bool visible) {
if (!root) return false;
auto node = findNodeByName(root, nodeName);
if (!node) return false;
node->visible = visible;
return true;
}
bool UiManager::getNodeVisible(const std::string& nodeName) {
if (!root) return false;
auto node = findNodeByName(root, nodeName);
if (!node) return false;
return node->visible;
}
} // namespace ZL } // namespace ZL

View File

@ -228,6 +228,11 @@ namespace ZL {
float width = 0; float width = 0;
float height = 0; float height = 0;
float scaleX = 1.0f;
float scaleY = 1.0f;
bool visible = true;
// Иерархия // Иерархия
std::vector<std::shared_ptr<UiNode>> children; std::vector<std::shared_ptr<UiNode>> children;
@ -353,6 +358,11 @@ namespace ZL {
bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb); bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb);
void updateAllLayouts(); void updateAllLayouts();
std::shared_ptr<UiNode> findNode(const std::string& name);
bool setNodeVisible(const std::string& nodeName, bool visible);
bool getNodeVisible(const std::string& nodeName);
private: private:
void layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY); void layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY);
void syncComponentRects(const std::shared_ptr<UiNode>& node); void syncComponentRects(const std::shared_ptr<UiNode>& node);

View File

@ -50,6 +50,17 @@ namespace ZL {
data.scale = item.value("scale", 1.0f); data.scale = item.value("scale", 1.0f);
// Interactive object properties
data.interactive = item.value("interactive", false);
if (data.interactive && item.contains("item") && item["item"].is_object()) {
const auto& itemData = item["item"];
data.itemId = itemData.value("id", "");
data.itemName = itemData.value("name", "Unknown Item");
data.itemDescription = itemData.value("description", "");
data.itemIcon = itemData.value("icon", "");
data.interactionRadius = itemData.value("radius", 2.0f);
}
if (!data.meshPath.empty()) { if (!data.meshPath.empty()) {
objects.push_back(data); objects.push_back(data);
} }
@ -73,6 +84,8 @@ namespace ZL {
std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath); std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);
for (const auto& objData : objectsData) { for (const auto& objData : objectsData) {
if (objData.interactive) continue; // Skip interactive objects, they're handled separately
std::cout << "Loading game object: " << objData.name << std::endl; std::cout << "Loading game object: " << objData.name << std::endl;
LoadedGameObject gameObj; LoadedGameObject gameObj;
@ -131,4 +144,104 @@ namespace ZL {
return gameObjects; return gameObjects;
} }
std::vector<InteractiveObject> GameObjectLoader::loadAndCreateInteractiveObjects(
const std::string& jsonPath,
Renderer& renderer,
const std::string& zipPath)
{
std::vector<InteractiveObject> interactiveObjects;
std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);
for (const auto& objData : objectsData) {
if (!objData.interactive) continue;
std::cout << "Loading interactive object: " << objData.name << std::endl;
InteractiveObject intObj;
intObj.id = objData.name;
intObj.name = objData.name;
intObj.interactionRadius = objData.interactionRadius;
// Load texture
try {
intObj.texture = std::make_shared<Texture>(CreateTextureDataFromPng(objData.texturePath, zipPath.c_str()));
}
catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load texture for interactive '" << objData.name << "': " << e.what() << std::endl;
continue;
}
// Load mesh
try {
intObj.mesh.data = LoadFromTextFile02(objData.meshPath, zipPath);
}
catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load mesh for interactive '" << objData.name << "': " << e.what() << std::endl;
continue;
}
// Apply rotation
Eigen::Quaternionf rotationQuat = Eigen::Quaternionf::Identity();
if (objData.rotationX != 0.0f) {
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationX, Eigen::Vector3f::UnitX())) * rotationQuat;
}
if (objData.rotationY != 0.0f) {
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationY, Eigen::Vector3f::UnitY())) * rotationQuat;
}
if (objData.rotationZ != 0.0f) {
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationZ, Eigen::Vector3f::UnitZ())) * rotationQuat;
}
intObj.mesh.data.RotateByMatrix(rotationQuat.toRotationMatrix());
// Apply scale
if (objData.scale != 1.0f) {
intObj.mesh.data.Scale(objData.scale);
}
intObj.mesh.RefreshVBO();
// Calculate mesh bounds to properly offset position
if (!intObj.mesh.data.PositionData.empty()) {
Eigen::Vector3f meshMin = intObj.mesh.data.PositionData[0];
Eigen::Vector3f meshMax = intObj.mesh.data.PositionData[0];
for (const auto& vert : intObj.mesh.data.PositionData) {
meshMin = meshMin.cwiseMin(vert);
meshMax = meshMax.cwiseMax(vert);
}
Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f;
std::cout << "[LOADER] Mesh bounds:" << std::endl;
std::cout << " Min: (" << meshMin.x() << ", " << meshMin.y() << ", " << meshMin.z() << ")" << std::endl;
std::cout << " Max: (" << meshMax.x() << ", " << meshMax.y() << ", " << meshMax.z() << ")" << std::endl;
std::cout << " Center: (" << meshCenter.x() << ", " << meshCenter.y() << ", " << meshCenter.z() << ")" << std::endl;
// Translate mesh so it's centered at origin
intObj.mesh.data.Move(-meshCenter);
intObj.mesh.RefreshVBO();
// Adjust position to account for mesh offset
intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ) + meshCenter;
std::cout << "[LOADER] Corrected position: (" << intObj.position.x() << ", "
<< intObj.position.y() << ", " << intObj.position.z() << ")" << std::endl;
} else {
// Fallback if no vertices
intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ);
}
// Create item
intObj.dropItem = Item(objData.itemId, objData.itemName, objData.itemDescription, objData.itemIcon);
interactiveObjects.push_back(intObj);
std::cout << "Successfully loaded interactive: " << objData.name << " (item: " << objData.itemName << ")" << std::endl;
}
std::cout << "Total interactive objects loaded: " << interactiveObjects.size() << std::endl;
return interactiveObjects;
}
} // namespace ZL } // namespace ZL

View File

@ -5,6 +5,7 @@
#include <unordered_map> #include <unordered_map>
#include "external/nlohmann/json.hpp" #include "external/nlohmann/json.hpp"
#include "TextModel.h" #include "TextModel.h"
#include "InteractiveObject.h"
namespace ZL { namespace ZL {
@ -23,6 +24,12 @@ namespace ZL {
float positionY = 0.0f; float positionY = 0.0f;
float positionZ = 0.0f; float positionZ = 0.0f;
float scale = 1.0f; float scale = 1.0f;
bool interactive = false;
std::string itemId;
std::string itemName;
std::string itemDescription;
std::string itemIcon;
float interactionRadius = 2.0f;
}; };
struct LoadedGameObject { struct LoadedGameObject {
@ -40,6 +47,12 @@ namespace ZL {
Renderer& renderer, Renderer& renderer,
const std::string& zipPath = "" const std::string& zipPath = ""
); );
static std::vector<InteractiveObject> loadAndCreateInteractiveObjects(
const std::string& jsonPath,
Renderer& renderer,
const std::string& zipPath = ""
);
}; };
} // namespace ZL } // namespace ZL

View File

@ -0,0 +1,37 @@
#include "InteractiveObject.h"
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include <iostream>
#include <string>
namespace ZL {
extern const std::string textureUniformName;
}
namespace ZL {
void InteractiveObject::draw(Renderer& renderer) const {
if (!isActive || !texture) return;
std::cout << "[DRAW] InteractiveObject::draw() called" << std::endl;
std::cout << "[DRAW] Object: " << name << std::endl;
std::cout << "[DRAW] Position: (" << position.x() << ", " << position.y() << ", " << position.z() << ")" << std::endl;
// Check mesh bounds
if (!mesh.data.PositionData.empty()) {
std::cout << "[DRAW] First vertex: (" << mesh.data.PositionData[0].x() << ", "
<< mesh.data.PositionData[0].y() << ", " << mesh.data.PositionData[0].z() << ")" << std::endl;
}
// Apply position transformation
renderer.PushMatrix();
renderer.TranslateMatrix(position);
renderer.RenderUniform1i(textureUniformName, 0);
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
renderer.DrawVertexRenderStruct(mesh);
renderer.PopMatrix();
}
} // namespace ZL

View File

@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <memory>
#include <Eigen/Core>
#include "Item.h"
#include "render/Renderer.h"
namespace ZL {
struct Texture;
struct VertexRenderStruct;
class Renderer;
struct InteractiveObject {
std::string id;
std::string name;
Eigen::Vector3f position;
float interactionRadius;
std::shared_ptr<Texture> texture;
VertexRenderStruct mesh;
Item dropItem;
bool isActive = true;
InteractiveObject() : interactionRadius(2.0f) {}
bool isInRange(const Eigen::Vector3f& playerPos) const {
return (playerPos - position).norm() <= interactionRadius;
}
void draw(Renderer& renderer) const;
};
} // namespace ZL

26
src/items/Item.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "Item.h"
#include <algorithm>
#include <iostream>
namespace ZL {
void Inventory::addItem(const Item& item) {
items.push_back(item);
std::cout << "Item added to inventory: " << item.name << std::endl;
}
void Inventory::removeItem(const std::string& itemId) {
auto it = std::find_if(items.begin(), items.end(),
[&itemId](const Item& item) { return item.id == itemId; });
if (it != items.end()) {
std::cout << "Item removed from inventory: " << it->name << std::endl;
items.erase(it);
}
}
bool Inventory::hasItem(const std::string& itemId) const {
return std::find_if(items.begin(), items.end(),
[&itemId](const Item& item) { return item.id == itemId; }) != items.end();
}
} // namespace ZL

33
src/items/Item.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <memory>
#include <vector>
namespace ZL {
struct Item {
std::string id;
std::string name;
std::string description;
std::string icon;
Item() = default;
Item(const std::string& _id, const std::string& _name, const std::string& _desc, const std::string& _icon)
: id(_id), name(_name), description(_desc), icon(_icon) {
}
};
class Inventory {
private:
std::vector<Item> items;
public:
void addItem(const Item& item);
void removeItem(const std::string& itemId);
const std::vector<Item>& getItems() const { return items; }
bool hasItem(const std::string& itemId) const;
void clear() { items.clear(); }
size_t getCount() const { return items.size(); }
};
} // namespace ZL