diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 9b18fb5..291d3d2 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -76,6 +76,10 @@ add_executable(space-game001 ../src/items/GameObjectLoader.h ../src/items/GameObjectLoader.cpp + ../src/items/Item.h + ../src/items/Item.cpp + ../src/items/InteractiveObject.h + ../src/items/InteractiveObject.cpp ) # Установка проекта по умолчанию для Visual Studio diff --git a/resources/config2/gameobjects.json b/resources/config2/gameobjects.json index 0a9b49d..b74a73f 100644 --- a/resources/config2/gameobjects.json +++ b/resources/config2/gameobjects.json @@ -10,7 +10,8 @@ "positionX": 0.0, "positionY": 0.0, "positionZ": 0.0, - "scale": 1.0 + "scale": 1.0, + "interactive": false }, { "name": "inai", @@ -22,7 +23,8 @@ "positionX": 2.5, "positionY": 1.4, "positionZ": -9.9, - "scale": 1.0 + "scale": 1.0, + "interactive": false }, { "name": "bench", @@ -34,7 +36,29 @@ "positionX": -2.1, "positionY": 0.5, "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" } ] } \ No newline at end of file diff --git a/resources/config2/ui_inventory.json b/resources/config2/ui_inventory.json new file mode 100644 index 0000000..5f2a167 --- /dev/null +++ b/resources/config2/ui_inventory.json @@ -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" + } + } + ] + } + ] + } +} \ No newline at end of file diff --git a/resources/start.lua b/resources/start.lua index ccffe4d..3780f38 100644 --- a/resources/start.lua +++ b/resources/start.lua @@ -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() game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1) @@ -14,3 +15,25 @@ function step1() end 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!") \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index e1b5f01..fdc82fb 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -148,34 +148,11 @@ namespace ZL roomMesh.RefreshVBO(); std::cout << "Load resurces step 5" << std::endl; + // Load static game objects gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE); - - /* - std::cout << "Load resurces step 5" << std::endl; - fireboxTexture = std::make_unique(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(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(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; + + // Load interactive objects + interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE); auto violaTexture = std::make_shared(CreateTextureDataFromPng("resources/viola.png", CONST_ZIP_FILE)); @@ -236,8 +213,91 @@ namespace ZL scriptEngine.init(this); 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() { @@ -291,6 +351,11 @@ namespace ZL glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID()); renderer.DrawVertexRenderStruct(benchMesh); */ + for (auto& intObj : interactiveObjects) { + if (intObj.isActive) { + intObj.draw(renderer); + } + } if (player) player->draw(renderer); for (auto& npc : npcs) npc->draw(renderer); @@ -374,9 +439,23 @@ namespace ZL if (player) player->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() { @@ -456,13 +535,14 @@ namespace ZL if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) { if (event.button.button == SDL_BUTTON_LEFT) { - // Преобразуем экранные пиксели в проекционные единицы int mx = static_cast((float)event.button.x / Environment::width * Environment::projectionWidth); int my = static_cast((float)event.button.y / Environment::height * Environment::projectionHeight); + if (event.type == SDL_MOUSEBUTTONDOWN) { + std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl; 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 ndcY = 1.0f - 2.0f * event.button.y / 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(); - 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(); 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())); } + else { + std::cout << "[CLICK] No valid target found" << std::endl; + } + std::cout << "========================================\n" << std::endl; } else { handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my); } diff --git a/src/Game.h b/src/Game.h index 6287e04..539bf02 100644 --- a/src/Game.h +++ b/src/Game.h @@ -10,6 +10,8 @@ #include "Projectile.h" #include "utils/TaskManager.h" #include "items/GameObjectLoader.h" +#include "items/Item.h" +#include "items/InteractiveObject.h" #include #include #include @@ -61,12 +63,24 @@ namespace ZL { std::shared_ptr benchTexture; VertexRenderStruct benchMesh; */ + std::vector interactiveObjects; + Inventory inventory; + InteractiveObject* pickedUpObject = nullptr; + + // Interactive object targeting + InteractiveObject* targetInteractiveObject = nullptr; + bool inventoryOpen = false; std::unique_ptr player; std::vector> npcs; float cameraAzimuth = 0.0f; float cameraInclination = M_PI * 30.f / 180.f; + + // Public access for ScriptEngine + MenuManager menuManager; + ScriptEngine scriptEngine; + private: bool rightMouseDown = false; int lastMouseX = 0; @@ -83,6 +97,7 @@ namespace ZL { void handleDown(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); + InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir); #ifdef EMSCRIPTEN static Game* s_instance; @@ -97,9 +112,6 @@ namespace ZL { static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_MAX_TIME_INTERVAL = 1000; - - MenuManager menuManager; - ScriptEngine scriptEngine; }; diff --git a/src/ScriptEngine.cpp b/src/ScriptEngine.cpp index 0951a18..2afde43 100644 --- a/src/ScriptEngine.cpp +++ b/src/ScriptEngine.cpp @@ -7,55 +7,200 @@ namespace ZL { -struct ScriptEngine::Impl { - sol::state lua; -}; + struct ScriptEngine::Impl { + sol::state lua; + }; -ScriptEngine::ScriptEngine() = default; -ScriptEngine::~ScriptEngine() = default; + ScriptEngine::ScriptEngine() = default; + ScriptEngine::~ScriptEngine() = default; -void ScriptEngine::init(Game* game) { - impl = std::make_unique(); - sol::state& lua = impl->lua; + void ScriptEngine::init(Game* game) { + impl = std::make_unique(); + 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]) - // 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. - api.set_function("npc_walk_to", - [game](int index, float x, float y, float z, sol::object on_arrived) { - auto& npcs = game->npcs; - if (index < 0 || index >= static_cast(npcs.size())) { - std::cerr << "[script] npc_walk_to: index " << index - << " out of range (0.." << npcs.size() - 1 << ")\n"; - return; + // npc_walk_to(index, x, y, z [, on_arrived]) + // 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. + api.set_function("npc_walk_to", + [game](int index, float x, float y, float z, sol::object on_arrived) { + auto& npcs = game->npcs; + if (index < 0 || index >= static_cast(npcs.size())) { + std::cerr << "[script] npc_walk_to: index " << index + << " out of range (0.." << npcs.size() - 1 << ")\n"; + return; + } + std::function cb; + if (on_arrived.is()) { + sol::protected_function fn = on_arrived.as(); + 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 cb; - if (on_arrived.is()) { - sol::protected_function fn = on_arrived.as(); - cb = [fn]() mutable { - auto result = fn(); - if (!result.valid()) { - sol::error err = result; - std::cerr << "[script] on_arrived error: " << err.what() << "\n"; - } - }; + std::cerr << "[script] Warning: Interactive object not found or already picked up: " << objectName << std::endl; + }); + + + // show_inventory() + api.set_function("show_inventory", [game]() { + std::cout << "[script] show_inventory called" << std::endl; + + 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) { - 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"; + // hide_inventory() + api.set_function("hide_inventory", [game]() { + std::cout << "[script] hide_inventory called" << std::endl; + game->menuManager.uiManager.setNodeVisible("inventory_items_panel", false); + 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 diff --git a/src/ScriptEngine.h b/src/ScriptEngine.h index 5c45d51..3d0bbc6 100644 --- a/src/ScriptEngine.h +++ b/src/ScriptEngine.h @@ -17,6 +17,9 @@ public: // Execute a Lua script file. Logs errors to stderr. void runScript(const std::string& path); + void callItemPickupCallback(const std::string& objectName); + void showInventory(Game* game); + private: struct Impl; std::unique_ptr impl; diff --git a/src/UiManager.cpp b/src/UiManager.cpp index 81b2b9f..d20487c 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -1092,24 +1092,55 @@ namespace ZL { renderer.PushMatrix(); renderer.LoadIdentity(); - for (const auto& img : staticImages) { - img->draw(renderer); - } - for (const auto& b : buttons) { - b->draw(renderer); - } - for (const auto& tb : textButtons) { - tb->draw(renderer); - } - for (const auto& s : sliders) { - s->draw(renderer); - } - for (const auto& tv : textViews) { - tv->draw(renderer); - } - for (const auto& tf : textFields) { - tf->draw(renderer); - } + std::function&)> drawNode = + [&](const std::shared_ptr& node) { + if (!node || !node->visible) return; + + renderer.PushMatrix(); + renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f, + node->screenRect.y + node->screenRect.h * 0.5f, 0.0f }); + renderer.ScaleMatrix({ node->scaleX, node->scaleY, 1.0f }); + renderer.TranslateMatrix({ -(node->screenRect.x + node->screenRect.w * 0.5f), + -(node->screenRect.y + node->screenRect.h * 0.5f), 0.0f }); + + + // 1. Сначала изображения + if (node->staticImage) { + node->staticImage->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.PopProjectionMatrix(); @@ -1641,4 +1672,23 @@ namespace ZL { return true; } + std::shared_ptr 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 \ No newline at end of file diff --git a/src/UiManager.h b/src/UiManager.h index a237771..ce14d3b 100644 --- a/src/UiManager.h +++ b/src/UiManager.h @@ -228,6 +228,11 @@ namespace ZL { float width = 0; float height = 0; + float scaleX = 1.0f; + float scaleY = 1.0f; + + bool visible = true; + // Иерархия std::vector> children; @@ -353,6 +358,11 @@ namespace ZL { bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function cb); void updateAllLayouts(); + std::shared_ptr findNode(const std::string& name); + + bool setNodeVisible(const std::string& nodeName, bool visible); + bool getNodeVisible(const std::string& nodeName); + private: void layoutNode(const std::shared_ptr& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY); void syncComponentRects(const std::shared_ptr& node); diff --git a/src/items/GameObjectLoader.cpp b/src/items/GameObjectLoader.cpp index 85ee3eb..1d92298 100644 --- a/src/items/GameObjectLoader.cpp +++ b/src/items/GameObjectLoader.cpp @@ -50,6 +50,17 @@ namespace ZL { 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()) { objects.push_back(data); } @@ -73,6 +84,8 @@ namespace ZL { std::vector objectsData = loadFromJson(jsonPath, zipPath); 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; LoadedGameObject gameObj; @@ -131,4 +144,104 @@ namespace ZL { return gameObjects; } + std::vector GameObjectLoader::loadAndCreateInteractiveObjects( + const std::string& jsonPath, + Renderer& renderer, + const std::string& zipPath) + { + std::vector interactiveObjects; + std::vector 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(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 \ No newline at end of file diff --git a/src/items/GameObjectLoader.h b/src/items/GameObjectLoader.h index d5fbc73..282cf1a 100644 --- a/src/items/GameObjectLoader.h +++ b/src/items/GameObjectLoader.h @@ -5,6 +5,7 @@ #include #include "external/nlohmann/json.hpp" #include "TextModel.h" +#include "InteractiveObject.h" namespace ZL { @@ -23,6 +24,12 @@ namespace ZL { float positionY = 0.0f; float positionZ = 0.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 { @@ -40,6 +47,12 @@ namespace ZL { Renderer& renderer, const std::string& zipPath = "" ); + + static std::vector loadAndCreateInteractiveObjects( + const std::string& jsonPath, + Renderer& renderer, + const std::string& zipPath = "" + ); }; } // namespace ZL \ No newline at end of file diff --git a/src/items/InteractiveObject.cpp b/src/items/InteractiveObject.cpp new file mode 100644 index 0000000..42a6621 --- /dev/null +++ b/src/items/InteractiveObject.cpp @@ -0,0 +1,37 @@ +#include "InteractiveObject.h" +#include "render/Renderer.h" +#include "render/TextureManager.h" +#include +#include + +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 \ No newline at end of file diff --git a/src/items/InteractiveObject.h b/src/items/InteractiveObject.h new file mode 100644 index 0000000..b04fc46 --- /dev/null +++ b/src/items/InteractiveObject.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#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; + 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 \ No newline at end of file diff --git a/src/items/Item.cpp b/src/items/Item.cpp new file mode 100644 index 0000000..b8b12eb --- /dev/null +++ b/src/items/Item.cpp @@ -0,0 +1,26 @@ +#include "Item.h" +#include +#include + +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 \ No newline at end of file diff --git a/src/items/Item.h b/src/items/Item.h new file mode 100644 index 0000000..cf27f4f --- /dev/null +++ b/src/items/Item.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include + +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 items; + + public: + void addItem(const Item& item); + void removeItem(const std::string& itemId); + const std::vector& getItems() const { return items; } + bool hasItem(const std::string& itemId) const; + void clear() { items.clear(); } + size_t getCount() const { return items.size(); } + }; + +} // namespace ZL \ No newline at end of file