From 92ba3f2b6071ab22c768e1927806dfa5bb1786cc Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 16 Apr 2026 20:06:03 +0300 Subject: [PATCH] Some refactoring --- resources/start.lua | 5 +- src/Game.cpp | 502 +++++++---------------------------- src/Game.h | 49 ++-- src/Location.cpp | 618 +++++++++++++++++++++++++++++++++++++++++++ src/Location.h | 79 ++++++ src/ScriptEngine.cpp | 72 ++--- src/ScriptEngine.h | 7 +- 7 files changed, 847 insertions(+), 485 deletions(-) diff --git a/resources/start.lua b/resources/start.lua index 168b3fe..6831e6f 100644 --- a/resources/start.lua +++ b/resources/start.lua @@ -2,6 +2,7 @@ -- NPC PATROL WAYPOINTS -- ============================================ + local function step3() game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1) end @@ -32,12 +33,12 @@ end function on_npc_interact(npc_index) print("[Lua] NPC interaction! Index: " .. tostring(npc_index)) - + --[[ if npc_index == 1 then game_api.start_dialogue("test_line_dialogue") else game_api.receive_npc_gift(npc_index) - end + end]] end print("Lua script loaded successfully!") \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index a8cec04..386842b 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -143,111 +143,12 @@ namespace ZL std::cout << "Load resurces step 4" << std::endl; - roomTexture = std::make_unique(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE)); - roomMesh.data = LoadFromTextFile02("resources/w/room001.txt", CONST_ZIP_FILE); - roomMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI*0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); - roomMesh.RefreshVBO(); + currentLocation = std::make_unique(renderer, inventory); + + currentLocation->setup(); + std::cout << "Load resurces step 5" << std::endl; - // Load static game objects - gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE); - - // Load interactive objects - interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE); - - auto violaTexture = std::make_shared(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE)); - - // Player (Viola) - player = std::make_unique(); - /* - player->loadAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.txt", CONST_ZIP_FILE); - player->loadAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.txt", CONST_ZIP_FILE); - player->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.txt", CONST_ZIP_FILE); - player->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.txt", CONST_ZIP_FILE); - player->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.txt", CONST_ZIP_FILE); - player->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.txt", CONST_ZIP_FILE); - */ - player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE); - player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE); - player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE); - player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE); - player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE); - player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE); - - player->setTexture(violaTexture); - player->walkSpeed = 3.0f; - player->rotationSpeed = 8.0f; - - player->modelScale = 1.f; - player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); - //Eigen::Quaternionf::Identity(); - /*Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5f, Eigen::Vector3f::UnitX())) * */ - /*Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitZ()));*/ - - player->canAttack = true; - player->isPlayer = true; - std::cout << "Load resurces step 9" << std::endl; - - // Load NPCs from JSON - npcs = GameObjectLoader::loadAndCreateNpcs("resources/config2/npcs.json", CONST_ZIP_FILE); - - /* - - auto defaultTexture = std::make_shared(CreateTextureDataFromPng("resources/w/default_skin001.png", CONST_ZIP_FILE)); - - auto npc01 = std::make_unique(); - npc01->loadAnimation(AnimationState::STAND, "resources/w/default_idle002.txt", CONST_ZIP_FILE); - npc01->loadAnimation(AnimationState::WALK, "resources/w/default_walk001.txt", CONST_ZIP_FILE); - //npc01->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", CONST_ZIP_FILE); - //npc01->loadAnimation(AnimationState::WALK, "resources/walkviola_uv010.txt", CONST_ZIP_FILE); - npc01->setTexture(defaultTexture); - npc01->walkSpeed = 1.5f; - npc01->rotationSpeed = 8.0f; - npc01->modelScale = 0.01f; - //npc01->modelScale = 0.1f; - npc01->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); - - std::cout << "Load resurces step 10" << std::endl; - npc01->position = Eigen::Vector3f(0.f, 0.f, -10.f); - npc01->setTarget(npc01->position); - npcs.push_back(std::move(npc01)); - - */ - - auto ghostTexture = std::make_shared(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE)); - - std::cout << "Load resurces step 11" << std::endl; - auto npc02 = std::make_unique(); - /* - npc02->loadAnimation(AnimationState::STAND, "resources/w/default_float001.txt", CONST_ZIP_FILE); - npc02->loadAnimation(AnimationState::WALK, "resources/w/default_float001.txt", CONST_ZIP_FILE); - npc02->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.txt", CONST_ZIP_FILE); - npc02->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.txt", CONST_ZIP_FILE); - npc02->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE); - npc02->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE); - */ - npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE); - npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE); - npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE); - npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE); - npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); - npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); - //npc02->loadAnimation(AnimationState::STAND, "resources/w/float_attack003.txt", CONST_ZIP_FILE); - //npc02->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", CONST_ZIP_FILE); - npc02->setTexture(ghostTexture); - npc02->walkSpeed = 1.5f; - npc02->rotationSpeed = 8.0f; - npc02->modelScale = 0.01f; - //npc02->modelScale = 0.1f; - npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); - - npc02->position = Eigen::Vector3f(0.f, 0.f, -20.f); - npc02->setTarget(npc02->position); - npc02->canAttack = true; - npc02->attackTarget = player.get(); - - npcs.push_back(std::move(npc02)); - std::cout << "Load resurces step 12" << std::endl; // Shadow mapping shaders @@ -263,15 +164,9 @@ namespace ZL renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE); #endif - // Create shadow map (2048x2048, ortho size 40, near 0.1, far 100) - shadowMap = std::make_unique(2048, 40.0f, 0.1f, 100.0f); - shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); - std::cout << "Shadow map initialized" << std::endl; - setupNavigation(); - loadingCompleted = true; - + dialogueSystem.init(renderer, CONST_ZIP_FILE); dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json"); /*dialogueSystem.addTriggerZone({ @@ -283,8 +178,7 @@ namespace ZL false });*/ - scriptEngine.init(this); - + std::cout << "Load resurces step 13" << std::endl; // Load UI with inventory button @@ -297,10 +191,25 @@ namespace ZL 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 + 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) { @@ -313,152 +222,9 @@ namespace ZL catch (const std::exception& e) { std::cerr << "Failed to load UI: " << e.what() << std::endl; } - } - void Game::setupNavigation() - { - std::vector obstacles; - obstacles.reserve(gameObjects.size() + interactiveObjects.size()); + loadingCompleted = true; - for (const auto& item : gameObjects) { - const LoadedGameObject& gameObj = item.second; - obstacles.push_back({ &gameObj.mesh.data, Eigen::Vector3f::Zero() }); - } - - for (const InteractiveObject& intObj : interactiveObjects) { - if (!intObj.isActive) { - continue; - } - obstacles.push_back({ &intObj.mesh.data, intObj.position }); - } - - navigation.build(obstacles, "resources/config2/navigation.json", CONST_ZIP_FILE); - -#ifdef SHOW_PATH - buildDebugNavMeshes(); -#endif - - auto planner = [this](const Eigen::Vector3f& start, const Eigen::Vector3f& end) { - return navigation.findPath(start, end); - }; - - if (player) { - player->setPathPlanner(planner); - } - - for (auto& npc : npcs) { - if (npc) { - npc->setPathPlanner(planner); - } - } - } - -#ifdef SHOW_PATH - void Game::buildDebugNavMeshes() - { - debugNavMeshes.clear(); - const auto& areas = navigation.getAreas(); - float y = navigation.getFloorY() + 0.02f; - Eigen::Vector3f red(1.0f, 0.0f, 0.0f); - - for (const auto& area : areas) { - if (area.polygon.size() < 3) continue; - VertexRenderStruct mesh; - mesh.data = CreatePolygonFloor(area.polygon, y, red); - mesh.RefreshVBO(); - debugNavMeshes.push_back(std::move(mesh)); - } - } - - void Game::drawDebugNavigation() - { - renderer.shaderManager.PushShader("defaultColor"); - renderer.SetMatrix(); - for (const auto& mesh : debugNavMeshes) { - renderer.DrawVertexRenderStruct(mesh); - } - renderer.shaderManager.PopShader(); - renderer.SetMatrix(); - } -#endif - - 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; - } - - Character* Game::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) { - Character* closestNpc = nullptr; - float closestDist = maxDistance; - - for (auto& npc : npcs) { - Eigen::Vector3f toNpc = npc->position - rayOrigin; - float distAlongRay = toNpc.dot(rayDir); - if (distAlongRay < 0.1f) continue; - - Eigen::Vector3f closestPoint = rayOrigin + rayDir * distAlongRay; - float distToNpc = (closestPoint - npc->position).norm(); - - float radius = npc->modelScale * 50.0f; - - if (distToNpc <= radius && distAlongRay < closestDist) { - closestDist = distAlongRay; - closestNpc = npc.get(); - } - } - return closestNpc; } void Game::drawUI() @@ -483,7 +249,7 @@ namespace ZL CheckGlError(); } - + /* void Game::drawGame() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); @@ -515,14 +281,6 @@ namespace ZL renderer.DrawVertexRenderStruct(gameObj.mesh); } - /* - glBindTexture(GL_TEXTURE_2D, fireboxTexture->getTexID()); - renderer.DrawVertexRenderStruct(fireboxMesh); - glBindTexture(GL_TEXTURE_2D, inaiTexture->getTexID()); - renderer.DrawVertexRenderStruct(inaiMesh); - glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID()); - renderer.DrawVertexRenderStruct(benchMesh); - */ for (auto& intObj : interactiveObjects) { if (intObj.isActive) { intObj.draw(renderer); @@ -698,6 +456,7 @@ namespace ZL renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); } + */ void Game::drawScene() { glViewport(0, 0, Environment::width, Environment::height); @@ -706,11 +465,11 @@ namespace ZL } else { - if (shadowMap) { - drawShadowDepthPass(); - drawGameWithShadows(); + if (currentLocation->shadowMap) { + currentLocation->drawShadowDepthPass(); + currentLocation->drawGameWithShadows(); } else { - drawGame(); + currentLocation->drawGame(); } drawUI(); } @@ -772,41 +531,54 @@ namespace ZL lastTickCount = newTickCount; - if (player) { - player->update(delta); - dialogueSystem.update(static_cast(delta), player->position); - } - for (auto& npc : npcs) npc->update(delta); + if (currentLocation) + { + currentLocation->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 custom activate function if specified, otherwise use fallback - try { - if (!targetInteractiveObject->activateFunctionName.empty()) { - std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->activateFunctionName << std::endl; - scriptEngine.callActivateFunction(targetInteractiveObject->activateFunctionName); - } - else { - std::cout << "[PICKUP] Using fallback callback" << std::endl; - scriptEngine.callItemPickupCallback(targetInteractiveObject->id); - } - } - catch (const std::exception& e) { - std::cerr << "[PICKUP] Error calling function: " << e.what() << std::endl; - } - - targetInteractiveObject = nullptr; + if (currentLocation->player) { + dialogueSystem.update(static_cast(delta), currentLocation->player->position); } + + + + /* + if (player) { + player->update(delta); + dialogueSystem.update(static_cast(delta), player->position); + } + 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 custom activate function if specified, otherwise use fallback + try { + if (!targetInteractiveObject->activateFunctionName.empty()) { + std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->activateFunctionName << std::endl; + scriptEngine.callActivateFunction(targetInteractiveObject->activateFunctionName); + } + else { + std::cout << "[PICKUP] Using fallback callback" << std::endl; + scriptEngine.callItemPickupCallback(targetInteractiveObject->id); + } + } + catch (const std::exception& e) { + std::cerr << "[PICKUP] Error calling function: " << e.what() << std::endl; + } + + targetInteractiveObject = nullptr; + } + }*/ } + } } @@ -894,7 +666,6 @@ namespace ZL std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl; handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my); - player->attackTarget = nullptr; if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) { std::cout << "[CLICK] UI handled, skipping character movement" << std::endl; continue; @@ -906,85 +677,11 @@ namespace ZL continue; } - // Unproject click to ground plane (y=0) for Viola's walk target - 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; - float tanHalfFov = tan(CAMERA_FOV_Y * 0.5f); - - float cosAzim = cos(cameraAzimuth), sinAzim = sin(cameraAzimuth); - float cosIncl = cos(cameraInclination), sinIncl = sin(cameraInclination); - - Eigen::Vector3f camRight(cosAzim, 0.f, sinAzim); - Eigen::Vector3f camForward(sinAzim * cosIncl, -sinIncl, -cosAzim * cosIncl); - Eigen::Vector3f camUp(sinAzim * sinIncl, cosIncl, -cosAzim * sinIncl); - const Eigen::Vector3f& playerPos = player ? player->position : Eigen::Vector3f::Zero(); - Eigen::Vector3f camPos = playerPos + Eigen::Vector3f(-sinAzim * cosIncl, sinIncl, cosAzim * cosIncl) * Environment::zoom; - - Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized(); - - 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; + if (currentLocation) + { + currentLocation->handleDown(ZL::UiManager::MOUSE_FINGER_ID, event.button.x, event.button.y); } - else { - - // Check if we clicked on an NPC - Character* clickedNpc = raycastNpcs(camPos, rayDir); - if (clickedNpc && player) { - float distance = (player->position - clickedNpc->position).norm(); - int npcIndex = -1; - for (size_t i = 0; i < npcs.size(); ++i) { - if (npcs[i].get() == clickedNpc) { - npcIndex = static_cast(i); - break; - } - } - if (npcIndex != -1) { - if (distance <= clickedNpc->interactionRadius) { - std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl; - scriptEngine.callNpcInteractCallback(npcIndex); - } - else { - std::cout << "[CLICK] Too far from NPC (distance " << distance - << " > " << clickedNpc->interactionRadius << ")" << std::endl; - } - - if (clickedNpc->canAttack) - { - player->attackTarget = clickedNpc; - } - } - - } - 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; - - if (player->currentState == AnimationState::STAND || player->currentState == AnimationState::WALK) - { - 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); } @@ -994,9 +691,19 @@ namespace ZL rightMouseDown = true; lastMouseX = event.button.x; lastMouseY = event.button.y; + if (currentLocation) + { + currentLocation->rightMouseDown = true; + currentLocation->lastMouseX = event.button.x; + currentLocation->lastMouseY = event.button.y; + } } else { rightMouseDown = false; + if (currentLocation) + { + currentLocation->rightMouseDown = false; + } } } } @@ -1005,20 +712,11 @@ namespace ZL int my = static_cast((float)event.motion.y / Environment::height * Environment::projectionHeight); handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my); - if (rightMouseDown) { - int dx = event.motion.x - lastMouseX; - int dy = event.motion.y - lastMouseY; - lastMouseX = event.motion.x; - lastMouseY = event.motion.y; - - const float sensitivity = 0.005f; - cameraAzimuth += dx * sensitivity; - cameraInclination += dy * sensitivity; - - const float minInclination = M_PI * 30.f / 180.f; - const float maxInclination = M_PI * 0.5f; - cameraInclination = max(minInclination, min(maxInclination, cameraInclination)); + if (currentLocation) + { + currentLocation->handleMotion(ZL::UiManager::MOUSE_FINGER_ID, event.motion.x, event.motion.y); } + } if (event.type == SDL_MOUSEWHEEL) { @@ -1049,21 +747,21 @@ namespace ZL break; case SDLK_p: - if (player->battle_state == 0) + /*if (player->battle_state == 0) { player->battle_state = 1; } else { player->battle_state = 0; - } + }*/ break; case SDLK_l: - if (player->attack == 0) + /*if (player->attack == 0) { player->attack = 1; - } + }*/ break; case SDLK_RETURN: @@ -1169,7 +867,7 @@ namespace ZL bool Game::setNavigationAreaAvailable(const std::string& areaName, bool available) { - return navigation.setAreaAvailable(areaName, available); + return currentLocation->setNavigationAreaAvailable(areaName, available); } diff --git a/src/Game.h b/src/Game.h index 8b6dec3..96e8c05 100644 --- a/src/Game.h +++ b/src/Game.h @@ -20,9 +20,10 @@ #include "ScriptEngine.h" #include #include "dialogue/DialogueSystem.h" -#include "render/ShadowMap.h" -#include "navigation/PathFinder.h" +//#include "render/ShadowMap.h" +//#include "navigation/PathFinder.h" #include +#include "Location.h" namespace ZL { @@ -52,71 +53,66 @@ namespace ZL { VertexRenderStruct loadingMesh; bool loadingCompleted = false; + std::shared_ptr currentLocation; + /* std::shared_ptr roomTexture; VertexRenderStruct roomMesh; std::unordered_map gameObjects; - /* - std::shared_ptr fireboxTexture; - VertexRenderStruct fireboxMesh; - - std::shared_ptr inaiTexture; - VertexRenderStruct inaiMesh; - - std::shared_ptr benchTexture; - VertexRenderStruct benchMesh; - */ - std::vector interactiveObjects; + std::vector interactiveObjects;*/ Inventory inventory; InteractiveObject* pickedUpObject = nullptr; // Interactive object targeting - InteractiveObject* targetInteractiveObject = nullptr; + //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; + + /* PathFinder navigation; #ifdef SHOW_PATH std::vector debugNavMeshes; #endif - +*/ private: bool rightMouseDown = false; int lastMouseX = 0; int lastMouseY = 0; - static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f; + //static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f; int64_t getSyncTimeMs(); void processTickCount(); void drawScene(); void drawUI(); - void drawGame(); + //void drawGame(); void drawLoading(); - void drawShadowDepthPass(); - void drawGameWithShadows(); - void setupNavigation(); + //void drawShadowDepthPass(); + //void drawGameWithShadows(); + //void setupNavigation(); 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); + /*InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir); Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f); #ifdef SHOW_PATH void buildDebugNavMeshes(); void drawDebugNavigation(); #endif +*/ #ifdef EMSCRIPTEN static Game* s_instance; @@ -132,12 +128,9 @@ namespace ZL { static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_MAX_TIME_INTERVAL = 1000; - //MenuManager menuManager; Dialogue::DialogueSystem dialogueSystem; - //ScriptEngine scriptEngine; - - std::unique_ptr shadowMap; - Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity(); + //std::unique_ptr shadowMap; + //Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity(); }; diff --git a/src/Location.cpp b/src/Location.cpp index 42d09d5..9bb9057 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -1,2 +1,620 @@ #include "Location.h" +#include "utils/Utils.h" +#include "render/OpenGlExtensions.h" +#include +#include "render/TextureManager.h" +#include "TextModel.h" +#include +#include +#include +#include +#include +#include "GameConstants.h" + + + +namespace ZL +{ + extern const char* CONST_ZIP_FILE; + + static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f; + + Location::Location(Renderer& iRenderer, Inventory& iInventory) + : renderer(iRenderer) + , inventory(iInventory) + { + + } + + void Location::setup() + { + roomTexture = std::make_unique(CreateTextureDataFromPng("resources/w/room005.png", CONST_ZIP_FILE)); + roomMesh.data = LoadFromTextFile02("resources/w/room001.txt", CONST_ZIP_FILE); + roomMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); + roomMesh.RefreshVBO(); + + // Load static game objects + gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE); + + // Load interactive objects + interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE); + + + auto playerTexture = std::make_shared(CreateTextureDataFromPng("resources/w/gg/IMG_20260413_182354_992.png", CONST_ZIP_FILE)); + + player = std::make_unique(); + + player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE); + player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE); + player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE); + player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE); + player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE); + player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE); + + player->setTexture(playerTexture); + player->walkSpeed = 3.0f; + player->rotationSpeed = 8.0f; + + player->modelScale = 1.f; + player->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); + + player->canAttack = true; + player->isPlayer = true; + std::cout << "Load resurces step 9" << std::endl; + + // Load NPCs from JSON + npcs = GameObjectLoader::loadAndCreateNpcs("resources/config2/npcs.json", CONST_ZIP_FILE); + + auto ghostTexture = std::make_shared(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE)); + + std::cout << "Load resurces step 11" << std::endl; + auto npc02 = std::make_unique(); + + npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE); + npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE); + npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE); + npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE); + npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); + npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE); + npc02->setTexture(ghostTexture); + npc02->walkSpeed = 1.5f; + npc02->rotationSpeed = 8.0f; + npc02->modelScale = 0.01f; + //npc02->modelScale = 0.1f; + npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); + + npc02->position = Eigen::Vector3f(0.f, 0.f, -20.f); + npc02->setTarget(npc02->position); + npc02->canAttack = true; + npc02->attackTarget = player.get(); + + npcs.push_back(std::move(npc02)); + + // Create shadow map (2048x2048, ortho size 40, near 0.1, far 100) + shadowMap = std::make_unique(2048, 40.0f, 0.1f, 100.0f); + shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); + std::cout << "Shadow map initialized" << std::endl; + + setupNavigation(); + + scriptEngine.init(this, &inventory); + + } + + void Location::setupNavigation() + { + std::vector obstacles; + obstacles.reserve(gameObjects.size() + interactiveObjects.size()); + + for (const auto& item : gameObjects) { + const LoadedGameObject& gameObj = item.second; + obstacles.push_back({ &gameObj.mesh.data, Eigen::Vector3f::Zero() }); + } + + for (const InteractiveObject& intObj : interactiveObjects) { + if (!intObj.isActive) { + continue; + } + obstacles.push_back({ &intObj.mesh.data, intObj.position }); + } + + navigation.build(obstacles, "resources/config2/navigation.json", CONST_ZIP_FILE); + +#ifdef SHOW_PATH + buildDebugNavMeshes(); +#endif + + auto planner = [this](const Eigen::Vector3f& start, const Eigen::Vector3f& end) { + return navigation.findPath(start, end); + }; + + if (player) { + player->setPathPlanner(planner); + } + + for (auto& npc : npcs) { + if (npc) { + npc->setPathPlanner(planner); + } + } + } + +#ifdef SHOW_PATH + void Location::buildDebugNavMeshes() + { + debugNavMeshes.clear(); + const auto& areas = navigation.getAreas(); + float y = navigation.getFloorY() + 0.02f; + Eigen::Vector3f red(1.0f, 0.0f, 0.0f); + + for (const auto& area : areas) { + if (area.polygon.size() < 3) continue; + VertexRenderStruct mesh; + mesh.data = CreatePolygonFloor(area.polygon, y, red); + mesh.RefreshVBO(); + debugNavMeshes.push_back(std::move(mesh)); + } + } + + void Location::drawDebugNavigation() + { + renderer.shaderManager.PushShader("defaultColor"); + renderer.SetMatrix(); + for (const auto& mesh : debugNavMeshes) { + renderer.DrawVertexRenderStruct(mesh); + } + renderer.shaderManager.PopShader(); + renderer.SetMatrix(); + } +#endif + + + InteractiveObject* Location::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; + } + + Character* Location::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) { + Character* closestNpc = nullptr; + float closestDist = maxDistance; + + for (auto& npc : npcs) { + Eigen::Vector3f toNpc = npc->position - rayOrigin; + float distAlongRay = toNpc.dot(rayDir); + if (distAlongRay < 0.1f) continue; + + Eigen::Vector3f closestPoint = rayOrigin + rayDir * distAlongRay; + float distToNpc = (closestPoint - npc->position).norm(); + + float radius = npc->modelScale * 50.0f; + + if (distToNpc <= radius && distAlongRay < closestDist) { + closestDist = distAlongRay; + closestNpc = npc.get(); + } + } + return closestNpc; + } + + + void Location::drawGame() + { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + + renderer.shaderManager.PushShader(defaultShaderName); + renderer.RenderUniform1i(textureUniformName, 0); + + renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, + static_cast(Environment::width) / static_cast(Environment::height), + Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); + renderer.PushMatrix(); + + renderer.LoadIdentity(); + renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); + //renderer.TranslateMatrix({ 0, -6.f, 0 }); + + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix()); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix()); + const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero(); + renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() }); + + glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID()); + renderer.DrawVertexRenderStruct(roomMesh); + + for (auto& [name, gameObj] : gameObjects) { + glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID()); + renderer.DrawVertexRenderStruct(gameObj.mesh); + } + + /* + glBindTexture(GL_TEXTURE_2D, fireboxTexture->getTexID()); + renderer.DrawVertexRenderStruct(fireboxMesh); + glBindTexture(GL_TEXTURE_2D, inaiTexture->getTexID()); + renderer.DrawVertexRenderStruct(inaiMesh); + 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); + +#ifdef SHOW_PATH + drawDebugNavigation(); +#endif + + renderer.PopMatrix(); + + renderer.PopProjectionMatrix(); + + renderer.shaderManager.PopShader(); + + } + + void Location::drawShadowDepthPass() + { + if (!shadowMap) return; + + const Eigen::Vector3f& sceneCenter = player ? player->position : Eigen::Vector3f::Zero(); + shadowMap->updateLightSpaceMatrix(sceneCenter); + + shadowMap->bind(); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + // Use front-face culling during depth pass to reduce shadow acne on lit faces + //glCullFace(GL_FRONT); + glEnable(GL_CULL_FACE); + + renderer.shaderManager.PushShader("shadow_depth"); + + // Set up light's orthographic projection + const Eigen::Matrix4f& lightProj = shadowMap->getLightProjectionMatrix(); + const Eigen::Matrix4f& lightView = shadowMap->getLightViewMatrix(); + + // Push the light's projection matrix via the 6-param ortho overload won't + // match our pre-computed matrix. Instead, use the raw stack approach: + // push a dummy projection then overwrite via PushSpecialMatrix-style. + // Simpler: push ortho then push the light view as modelview. + renderer.PushProjectionMatrix( + -40.0f, 40.0f, + -40.0f, 40.0f, + 0.1f, 100.0f); + + const Eigen::Vector3f& lightDir = shadowMap->getLightDirection(); + Eigen::Vector3f lightPos = sceneCenter - lightDir * 50.0f; + Eigen::Vector3f up(0.0f, 1.0f, 0.0f); + if (std::abs(lightDir.dot(up)) > 0.99f) { + up = Eigen::Vector3f(0.0f, 0.0f, 1.0f); + } + + // Build the light view matrix and push it + renderer.PushSpecialMatrix(lightView); + + // Draw static geometry + renderer.DrawVertexRenderStruct(roomMesh); + + for (auto& [name, gameObj] : gameObjects) { + renderer.DrawVertexRenderStruct(gameObj.mesh); + } + + for (auto& intObj : interactiveObjects) { + if (intObj.isActive && intObj.texture) { + renderer.PushMatrix(); + renderer.TranslateMatrix(intObj.position); + renderer.DrawVertexRenderStruct(intObj.mesh); + renderer.PopMatrix(); + } + } + + // Draw characters (they handle their own skinning shader switch internally) + if (player) player->drawShadowDepth(renderer); + for (auto& npc : npcs) npc->drawShadowDepth(renderer); + + renderer.PopMatrix(); // light view + renderer.PopProjectionMatrix(); + renderer.shaderManager.PopShader(); + + //glCullFace(GL_BACK); + glDisable(GL_CULL_FACE); + + shadowMap->unbind(); + } + + void Location::drawGameWithShadows() + { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + +#ifdef DEBUG_LIGHT + // Debug mode: render from the light's point of view using the plain + // textured shader so we can see what the shadow map "sees". + renderer.shaderManager.PushShader(defaultShaderName); + renderer.RenderUniform1i(textureUniformName, 0); + + renderer.PushProjectionMatrix( + -40.0f, 40.0f, + -40.0f, 40.0f, + 0.1f, 1000.0f); + + renderer.PushSpecialMatrix(shadowMap->getLightViewMatrix()); + +#else + static const std::string shadowShaderName = "default_shadow"; + + renderer.shaderManager.PushShader(shadowShaderName); + renderer.RenderUniform1i(textureUniformName, 0); + renderer.RenderUniform1i("uShadowMap", 1); + + // Bind shadow map texture to unit 1 + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthTexture()); + glActiveTexture(GL_TEXTURE0); + + renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, + static_cast(Environment::width) / static_cast(Environment::height), + Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); + renderer.PushMatrix(); + + renderer.LoadIdentity(); + renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); + + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix()); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix()); + const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero(); + renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() }); + + // Capture the camera view matrix and compute uLightFromCamera + cameraViewMatrix = renderer.GetCurrentModelViewMatrix(); + Eigen::Matrix4f cameraViewInverse = cameraViewMatrix.inverse(); + Eigen::Matrix4f lightFromCamera = shadowMap->getLightSpaceMatrix() * cameraViewInverse; + renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data()); + + // Light direction in camera space for diffuse lighting + Eigen::Vector3f lightDirCamera = cameraViewMatrix.block<3, 3>(0, 0) * shadowMap->getLightDirection(); + renderer.RenderUniform3fv("uLightDir", lightDirCamera.data()); +#endif + + glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID()); + renderer.DrawVertexRenderStruct(roomMesh); + + for (auto& [name, gameObj] : gameObjects) { + glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID()); + renderer.DrawVertexRenderStruct(gameObj.mesh); + } + + for (auto& intObj : interactiveObjects) { + if (intObj.isActive) { + intObj.draw(renderer); + } + } + +#ifdef DEBUG_LIGHT + // In debug-light mode characters use the plain shaders (draw normally + // but from the light's viewpoint — projection/view already on stack). + if (player) player->draw(renderer); + for (auto& npc : npcs) npc->draw(renderer); +#else + // Characters use their own shadow-aware shaders + if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); + for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); +#endif + + renderer.PopMatrix(); + renderer.PopProjectionMatrix(); + renderer.shaderManager.PopShader(); + } + + bool Location::setNavigationAreaAvailable(const std::string& areaName, bool available) + { + return navigation.setAreaAvailable(areaName, available); + } + + void Location::update(int64_t delta) + { + if (player) { + player->update(delta); + //dialogueSystem.update(static_cast(delta), player->position); + } + 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 custom activate function if specified, otherwise use fallback + try { + if (!targetInteractiveObject->activateFunctionName.empty()) { + std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->activateFunctionName << std::endl; + scriptEngine.callActivateFunction(targetInteractiveObject->activateFunctionName); + } + else { + std::cout << "[PICKUP] Using fallback callback" << std::endl; + scriptEngine.callItemPickupCallback(targetInteractiveObject->id); + } + } + catch (const std::exception& e) { + std::cerr << "[PICKUP] Error calling function: " << e.what() << std::endl; + } + + targetInteractiveObject = nullptr; + } + } + } + + void Location::handleDown(int64_t fingerId, int mx, int my) + { + player->attackTarget = nullptr; + + // Unproject click to ground plane (y=0) for Viola's walk target + float ndcX = 2.0f * mx / Environment::width - 1.0f; + float ndcY = 1.0f - 2.0f * my / Environment::height; + float aspect = (float)Environment::width / (float)Environment::height; + float tanHalfFov = tan(CAMERA_FOV_Y * 0.5f); + + float cosAzim = cos(cameraAzimuth), sinAzim = sin(cameraAzimuth); + float cosIncl = cos(cameraInclination), sinIncl = sin(cameraInclination); + + Eigen::Vector3f camRight(cosAzim, 0.f, sinAzim); + Eigen::Vector3f camForward(sinAzim * cosIncl, -sinIncl, -cosAzim * cosIncl); + Eigen::Vector3f camUp(sinAzim * sinIncl, cosIncl, -cosAzim * sinIncl); + const Eigen::Vector3f& playerPos = player ? player->position : Eigen::Vector3f::Zero(); + Eigen::Vector3f camPos = playerPos + Eigen::Vector3f(-sinAzim * cosIncl, sinIncl, cosAzim * cosIncl) * Environment::zoom; + + Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized(); + + 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 { + + // Check if we clicked on an NPC + Character* clickedNpc = raycastNpcs(camPos, rayDir); + if (clickedNpc && player) { + float distance = (player->position - clickedNpc->position).norm(); + int npcIndex = -1; + for (size_t i = 0; i < npcs.size(); ++i) { + if (npcs[i].get() == clickedNpc) { + npcIndex = static_cast(i); + break; + } + } + if (npcIndex != -1) { + if (distance <= clickedNpc->interactionRadius) { + std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl; + scriptEngine.callNpcInteractCallback(npcIndex); + } + else { + std::cout << "[CLICK] Too far from NPC (distance " << distance + << " > " << clickedNpc->interactionRadius << ")" << std::endl; + } + + if (clickedNpc->canAttack) + { + player->attackTarget = clickedNpc; + } + } + + } + 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; + + if (player->currentState == AnimationState::STAND || player->currentState == AnimationState::WALK) + { + 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; + } + void Location::handleUp(int64_t fingerId, int mx, int my) + { + + } + void Location::handleMotion(int64_t fingerId, int mx, int my) + { + if (rightMouseDown) { + int dx = mx - lastMouseX; + int dy = my - lastMouseY; + lastMouseX = mx; + lastMouseY = my; + + const float sensitivity = 0.005f; + cameraAzimuth += dx * sensitivity; + cameraInclination += dy * sensitivity; + + const float minInclination = M_PI * 30.f / 180.f; + const float maxInclination = M_PI * 0.5f; + cameraInclination = max(minInclination, min(maxInclination, cameraInclination)); + } + } + + +} // namespace ZL \ No newline at end of file diff --git a/src/Location.h b/src/Location.h index 73b4b86..e58b2bb 100644 --- a/src/Location.h +++ b/src/Location.h @@ -1 +1,80 @@ #pragma once + +#include "render/Renderer.h" +#include "Environment.h" +#include "render/TextureManager.h" +#include "items/GameObjectLoader.h" +#include "items/InteractiveObject.h" +#include "navigation/PathFinder.h" +#include "render/ShadowMap.h" + +#include "ScriptEngine.h" + +namespace ZL +{ + + class Location + { + public: + Location(Renderer& iRenderer, Inventory& iInventory); + + + std::shared_ptr roomTexture; + VertexRenderStruct roomMesh; + + std::unordered_map gameObjects; + + std::vector interactiveObjects; + + + std::unique_ptr player; + std::vector> npcs; + + float cameraAzimuth = 0.0f; + float cameraInclination = M_PI * 30.f / 180.f; + + PathFinder navigation; + + std::unique_ptr shadowMap; + Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity(); + InteractiveObject* targetInteractiveObject = nullptr; + + ScriptEngine scriptEngine; + + +#ifdef SHOW_PATH + std::vector debugNavMeshes; + void buildDebugNavMeshes(); + void drawDebugNavigation(); +#endif + bool rightMouseDown = false; + int lastMouseX = 0; + int lastMouseY = 0; + + + void setup(); + void setupNavigation(); + InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir); + Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f); + + void drawGame(); + void drawShadowDepthPass(); + void drawGameWithShadows(); + + bool setNavigationAreaAvailable(const std::string& areaName, bool available); + + void update(int64_t deltaMs); + + 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); + + + protected: + Renderer& renderer; + Inventory& inventory; + + + }; + +} // namespace ZL \ No newline at end of file diff --git a/src/ScriptEngine.cpp b/src/ScriptEngine.cpp index 80d4ff2..487a0ae 100644 --- a/src/ScriptEngine.cpp +++ b/src/ScriptEngine.cpp @@ -2,6 +2,7 @@ #include "Game.h" #include #include +#include "Location.h" #define SOL_ALL_SAFETIES_ON 1 #include @@ -15,7 +16,7 @@ namespace ZL { ScriptEngine::ScriptEngine() = default; ScriptEngine::~ScriptEngine() = default; - void ScriptEngine::init(Game* game) { + void ScriptEngine::init(Location* game, Inventory* inventory) { impl = std::make_unique(); sol::state& lua = impl->lua; @@ -49,13 +50,15 @@ namespace ZL { }); // pickup_item(object_name) - api.set_function("pickup_item", [game](const std::string& objectName) { + api.set_function("pickup_item", [game, inventory](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); + inventory->addItem(intObj.dropItem); // Deactivate object intObj.isActive = false; @@ -68,51 +71,18 @@ namespace ZL { }); - // 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"; - } - } - - game->menuManager.uiManager.setText("inventory_items_text", itemText); - }); - - // 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, + api.set_function("add_item", [game, inventory](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); + inventory->addItem(newItem); }); // remove_item(item_id) - api.set_function("remove_item", [game](const std::string& id) { + api.set_function("remove_item", [game, inventory](const std::string& id) { std::cout << "[script] remove_item: " << id << std::endl; - game->inventory.removeItem(id); + inventory->removeItem(id); }); // deactivate_interactive_object(object_name) @@ -129,30 +99,31 @@ namespace ZL { }); // get_inventory_count() - api.set_function("get_inventory_count", [game]() { - return game->inventory.getCount(); + api.set_function("get_inventory_count", [inventory]() { + return inventory->getCount(); }); // has_item(item_id) - api.set_function("has_item", [game](const std::string& id) { - return game->inventory.hasItem(id); + api.set_function("has_item", [inventory](const std::string& id) { + return inventory->hasItem(id); }); api.set_function("start_dialogue", [game](const std::string& dialogueId) { + /*----r if (!game->requestDialogueStart(dialogueId)) { std::cerr << "[script] start_dialogue failed for id: " << dialogueId << "\n"; - } + }*/ }); api.set_function("set_dialogue_flag", [game](const std::string& flag, int value) { - game->setDialogueFlag(flag, value); + //game->setDialogueFlag(flag, value); ---rrr }); api.set_function("get_dialogue_flag", [game](const std::string& flag) { - return game->getDialogueFlag(flag); + //return game->getDialogueFlag(flag); ---rrr }); api.set_function("set_navigation_area_available", @@ -164,7 +135,7 @@ namespace ZL { }); // receive_npc_gift(npc_index) - api.set_function("receive_npc_gift", [game](int npcIndex) { + api.set_function("receive_npc_gift", [game, inventory](int npcIndex) { std::cout << "[script] receive_npc_gift: npc index " << npcIndex << std::endl; auto& npcs = game->npcs; @@ -188,7 +159,7 @@ namespace ZL { return; } - game->inventory.addItem(npc->giftItem); + inventory->addItem(npc->giftItem); npc->giftReceived = true; std::cout << "[script] Received gift from " << npc->npcName << ": " @@ -284,6 +255,7 @@ namespace ZL { } } + /* void ScriptEngine::showInventory(Game* game) { std::cout << "[script] toggle_inventory called" << std::endl; @@ -309,6 +281,6 @@ namespace ZL { game->menuManager.uiManager.setText("inventory_items_text", itemText); } - } + }*/ } // namespace ZL diff --git a/src/ScriptEngine.h b/src/ScriptEngine.h index d5d08bb..b74bf06 100644 --- a/src/ScriptEngine.h +++ b/src/ScriptEngine.h @@ -4,7 +4,8 @@ namespace ZL { -class Game; +class Location; +class Inventory; class ScriptEngine { public: @@ -12,7 +13,7 @@ public: ~ScriptEngine(); // Must be called once, after the Game's NPCs are ready. - void init(Game* game); + void init(Location* game, Inventory* inventory); // Execute a Lua script file. Logs errors to stderr. void runScript(const std::string& path); @@ -21,7 +22,7 @@ public: void callActivateFunction(const std::string& functionName); - void showInventory(Game* game); + //void showInventory(Game* game); void callNpcInteractCallback(int npcIndex); private: